diff --git a/.gitignore b/.gitignore index 51b8c1f0f..52078bbf6 100644 --- a/.gitignore +++ b/.gitignore @@ -10,7 +10,6 @@ prebuilt ## doxygen Doxygen -!docs/cs-manual/Makefile ## ides/editors *~ @@ -22,6 +21,8 @@ Doxygen .project .settings .directory +.idea +cmake-build-* ## qt-creator CMakeLists.txt.user* @@ -34,15 +35,36 @@ resources ## binaries /esmtool -/mwiniimport -/omwlauncher /openmw /opencs /niftest +/bsatool +/openmw-cs +/openmw-essimporter +/openmw-iniimporter +/openmw-launcher +/openmw-wizard ## generated objects apps/openmw/config.hpp +apps/launcher/ui_contentselector.h +apps/launcher/ui_settingspage.h +apps/opencs/ui_contentselector.h +apps/opencs/ui_filedialog.h +apps/wizard/qrc_wizard.cxx +apps/wizard/ui_componentselectionpage.h +apps/wizard/ui_conclusionpage.h +apps/wizard/ui_existinginstallationpage.h +apps/wizard/ui_importpage.h +apps/wizard/ui_installationpage.h +apps/wizard/ui_installationtargetpage.h +apps/wizard/ui_intropage.h +apps/wizard/ui_languageselectionpage.h +apps/wizard/ui_methodselectionpage.h +components/ui_contentselector.h docs/mainpage.hpp +docs/Doxyfile +docs/DoxyfilePages moc_*.cxx *.cxx_parameters *qrc_launcher.cxx @@ -54,3 +76,6 @@ moc_*.cxx *ui_playpage.h *.[ao] *.so +gamecontrollerdb.txt +openmw.appdata.xml +venv/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..9ad6c4012 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "extern/breakpad"] + path = extern/breakpad + url = https://chromium.googlesource.com/breakpad/breakpad diff --git a/.travis.yml b/.travis.yml index e314d8e7b..dbcb00ff8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,7 @@ os: - linux # - osx +osx_image: xcode8.2 language: cpp sudo: required dist: trusty @@ -11,17 +12,37 @@ branches: - /openmw-.*$/ env: global: - # The next declaration is the encrypted COVERITY_SCAN_TOKEN, created - # via the "travis encrypt" command using the project repo's public key - - secure: "jybGzAdUbqt9vWR/GEnRd96BgAi/7Zd1+2HK68j/i/8+/1YH2XxLOy4Jv/DUBhBlJIkxs/Xv8dRcUlFOclZDHX1d/9Qnsqd3oUVkD7k1y7cTOWy9TBQaE/v/kZo3LpzA3xPwwthrb0BvqIbOfIELi5fS5s8ba85WFRg3AX70wWE=" + - macos_qt_formula=qt@5.5 + - secure: NZmvVuA0O9NJXVQ12tXQZHDJC2mbFgYNFcsicw0DgW1It2Nk5hxIkF0pfu4/Z59mhQuOPgRVjl5b0FKy2Axh0gkWc1DJEXGwNaiW5lpTMNWR1LJG5rxa8LrDUpFkycpbzfAFuTUZu5z3iYVv64XzELvBuqNGhPMu1LeBnrlech0jFNjkR9p5qtJGWb8zYcPMCC57rig8a9g1ABoVYS6UXjrKpx0946ZLRsE5ukc9pXsypGwPmOMyfzZkxxzIqFaxoE5JIEdaJTWba/6Za315ozYYIi/N35ROI1YAv5GHRe/Iw9XAa4vQpbDzjM7ZSsZdTvvQsSU598gD2xC6jFUKSrpW6GZKwM2x236fZLGnOk5Uw7DUbG+AwpcEmxBwoy9PjBl9ZF3tJykI0gROewCy8MODhdsVMKr1HGIMVBIJySm/RnNqtoDbYV8mYnSl5b8rwJiCajoiR8Zuv4CIfGneeH1a3DOQDPH/qkDsU6ilzF4ANsBlMUUpgY653KBMBmTlNuVZSH527tnD7Fg6JgHVuSQkTbRa1vSkR7Zcre604RZcAoaEdbX3bhVDasPPghU/I742L0RH3oQNlR09pPBDZ8kG7ydl4aPHwpCWnvXNM1vgxtGvnYLztwrse7IoaRXRYiMFmrso78WhMWUDKgvY4wV9aeUu0DtnMezZVIQwCKg= addons: + apt: + sources: + - sourceline: 'ppa:openmw/openmw' + - ubuntu-toolchain-r-test + - llvm-toolchain-precise-3.6 + packages: [ + # Dev + clang-3.6, libunshield-dev, libtinyxml-dev, + g++-6, + # Tests + libgtest-dev, google-mock, + # Boost + libboost-filesystem-dev, libboost-program-options-dev, libboost-system-dev, libboost-thread-dev, + # FFmpeg + libavcodec-dev, libavformat-dev, libavutil-dev, libswscale-dev, + # Audio & Video + libsdl2-dev, qtbase5-dev, libopenal-dev, + # The other ones from OpenMW ppa + libbullet-dev, libswresample-dev, libopenscenegraph-3.4-dev, libmygui-dev + ] + coverity_scan: project: - name: "OpenMW/openmw" + name: "TES3MP/openmw-tes3mp" description: "" - notification_email: scrawl@baseoftrash.de + notification_email: stas5978@gmail.com build_command_prepend: "cmake . -DBUILD_UNITTESTS=FALSE" - build_command: "make -j2" + build_command: "make -j3" branch_pattern: coverity_scan matrix: include: @@ -32,27 +53,24 @@ matrix: allow_failures: - env: ANALYZE="scan-build-3.6 --use-cc clang-3.6 --use-c++ clang++-3.6 " -before_install: - - if [ "${TRAVIS_OS_NAME}" = "linux" ]; then ./CI/before_install.linux.sh; fi - - if [ "${TRAVIS_OS_NAME}" = "osx" ]; then ./CI/before_install.osx.sh; fi -before_script: - - if [ "${TRAVIS_OS_NAME}" = "linux" ]; then ./CI/before_script.linux.sh; fi - - if [ "${TRAVIS_OS_NAME}" = "osx" ]; then ./CI/before_script.osx.sh; fi +before_install: + - ./CI/before_install.${TRAVIS_OS_NAME}.sh +before_script: ./CI/before_script.${TRAVIS_OS_NAME}.sh script: - cd ./build - if [ "$COVERITY_SCAN_BRANCH" != 1 ]; then ${ANALYZE}make -j3; fi - if [ "$COVERITY_SCAN_BRANCH" != 1 ] && [ "${TRAVIS_OS_NAME}" = "osx" ]; then make package; fi - if [ "$COVERITY_SCAN_BRANCH" != 1 ] && [ "${TRAVIS_OS_NAME}" = "linux" ]; then ./openmw_test_suite; fi - if [ "$COVERITY_SCAN_BRANCH" != 1 ] && [ "${TRAVIS_OS_NAME}" = "linux" ]; then cd .. && ./CI/check_tabs.sh; fi -notifications: - recipients: - - corrmage+travis-ci@gmail.com - email: - on_success: change - on_failure: always - irc: - channels: - - "chat.freenode.net#openmw" - on_success: change - on_failure: always - use_notice: true +#notifications: +# email: +# recipients: +# - corrmage+travis-ci@gmail.com +# on_success: change +# on_failure: always +# irc: +# channels: +# - "chat.freenode.net#openmw" +# on_success: change +# on_failure: always +# use_notice: true diff --git a/AUTHORS.md b/AUTHORS.md index 79cb87e64..d7e011cd8 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -14,15 +14,23 @@ Programmers Adam Hogan (aurix) Aesylwinn + aegis Aleksandar Jovanov Alex Haddad (rainChu) - Alex McKibben (WeirdSexy) + Alex McKibben + alexanderkjall Alexander Nadeau (wareya) Alexander Olofsson (Ace) + Allofich + AnyOldName3 + Aussiemon + Austin Salgat (Salgat) Artem Kotsynyak (greye) artemutin Arthur Moore (EmperorArthur) + Assumeru athile + Ben Shealy (bentsherman) Bret Curtis (psi29a) Britt Mathis (galdor557) cc9cii @@ -31,6 +39,7 @@ Programmers Cory F. Cohen (cfcohen) Cris Mihalache (Mirceam) darkf + devnexen Dieho Dmitry Shkurskiy (endorph) Douglas Diniz (Dgdiniz) @@ -44,6 +53,7 @@ Programmers eroen escondida Evgeniy Mineev (sandstranger) + Federico Guerra (FedeWar) Fil Krynicki (filkry) Gašper Sedej gugus/gus @@ -62,13 +72,18 @@ Programmers John Blomberg (fstp) Jordan Ayers Jordan Milne + Jules Blok (Armada651) Julien Voisin (jvoisin/ap0) Karl-Felix Glatzer (k1ll) Kevin Poitra (PuppyKevin) Koncord + Kurnevsky Evgeny (kurnevsky) Lars Söderberg (Lazaroth) lazydev + Leon Krieg (lkrieg) Leon Saunders (emoose) + logzero + lohikaarme Lukasz Gromanowski (lgro) Manuel Edelmann (vorenon) Marc Bouvier (CramitDeFrog) @@ -76,6 +91,7 @@ Programmers Mark Siewert (mark76) Marco Melletti (mellotanica) Marco Schulze + Martin Otto (MAtahualpa) Mateusz Kołaczek (PL_kolek) Mateusz Malisz (malice) megaton @@ -83,6 +99,7 @@ Programmers Michael Mc Donnell Michael Papageorgiou (werdanith) Michał Bień (Glorf) + Michał Moroz (dragonee) Miroslav Puda (pakanek) MiroslavR naclander @@ -92,26 +109,31 @@ Programmers Nikolay Kasyanov (corristo) nobrakal Nolan Poe (nopoe) + Oleg Chkan (mrcheko) Paul Cercueil (pcercuei) Paul McElroy (Greendogo) + Pi03k Pieter van der Kloet (pvdk) pkubik Radu-Marius Popovici (rpopovici) - rcutmore rdimesio riothamus + Rob Cutmore (rcutmore) Robert MacGregor (Ragora) Rohit Nirmal Roman Melnik (Kromgart) - Roman Proskuryakov (humbug) + Roman Proskuryakov (kpp) Sandy Carter (bwrsandman) Scott Howard Sebastian Wick (swick) Sergey Shambir + ShadowRadiance sir_herrbatka smbas Stefan Galowicz (bogglez) Stanislav Bobrov (Jiub) + stil-t + svaante Sylvain Thesnieres (Garvek) t6 terrorfisch @@ -123,19 +145,21 @@ Programmers vocollapse zelurker -Manual ------- +Documentation +------------- + Alejandro Sanchez (HiPhish) Bodillium + Bret Curtis (psi29a) Cramal - Alejandro Sanchez (HiPhish) + Ryan Tucker (Ravenwing) sir_herrbatka Packagers --------- Alexander Olofsson (Ace) - Windows - Bret Curtis (psi29a) - Ubuntu Linux + Bret Curtis (psi29a) - Debian and Ubuntu Linux Edmondo Tommasina (edmondo) - Gentoo Linux Julian Ospald (hasufell) - Gentoo Linux Karl-Felix Glatzer (k1ll) - Linux Binaries @@ -146,16 +170,16 @@ Packagers Public Relations and Translations --------------------------------- - Alex McKibben (WeirdSexy) - Podcaster Artem Kotsynyak (greye) - Russian News Writer + Dawid Lakomy (Vedyimyn) - Polish News Writer Jim Clauwaert (Zedd) - Public Outreach Julien Voisin (jvoisin/ap0) - French News Writer - Tom Koenderink (Okulo) - English News Writer Lukasz Gromanowski (lgro) - English News Writer + Martin Otto (Atahualpa) - Podcaster, Public Outreach, German Translator Mickey Lyle (raevol) - Release Manager Pithorn - Chinese News Writer sir_herrbatka - Polish News Writer - Dawid Lakomy (Vedyimyn) - Polish News Writer + Tom Koenderink (Okulo) - English News Writer Website ------- @@ -206,7 +230,6 @@ Inactive Contributors Nekochan pchan3 penguinroad - psi29a sergoz spyboot Star-Demon diff --git a/CHANGELOG.md b/CHANGELOG.md index de4a6fc24..87d826753 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,228 @@ +0.41.0 +------ + + Bug #1138: Casting water walking doesn't move the player out of the water + Bug #1931: Rocks from blocked passage in Bamz-Amschend, Radacs Forge can reset and cant be removed again. + Bug #2048: Almvisi and Divine Intervention display wrong spell effect + Bug #2054: Show effect-indicator for "instant effect" spells and potions + Bug #2150: Clockwork City door animation problem + Bug #2288: Playback of weapon idle animation not correct + Bug #2410: Stat-review window doesn't display starting spells, powers, or abilities + Bug #2493: Repairing occasionally very slow + Bug #2716: [OSG] Water surface is too transparent from some angles + Bug #2859: [MAC OS X] Cannot exit fullscreen once enabled + Bug #3091: Editor: will not save addon if global variable value type is null + Bug #3277: Editor: Non-functional nested tables in subviews need to be hidden instead of being disabled + Bug #3348: Disabled map markers show on minimap + Bug #3350: Extending selection to instances with same object results in duplicates. + Bug #3353: [Mod] Romance version 3.7 script failed + Bug #3376: [Mod] Vampire Embrace script fails to execute + Bug #3385: Banners don't animate in stormy weather as they do in the original game + Bug #3393: Akulakhan re-enabled after main quest + Bug #3427: Editor: OpenMW-CS instances won´t get deleted + Bug #3451: Feril Salmyn corpse isn't where it is supposed to be + Bug #3497: Zero-weight armor is displayed as "heavy" in inventory tooltip + Bug #3499: Idle animations don't always loop + Bug #3500: Spark showers at Sotha Sil do not appear until you look at the ceiling + Bug #3515: Editor: Moved objects in interior cells are teleported to exterior cells. + Bug #3520: Editor: OpenMW-CS cannot find project file when launching the game + Bug #3521: Armed NPCs don't use correct melee attacks + Bug #3535: Changing cell immediately after dying causes character to freeze. + Bug #3542: Unable to rest if unalerted slaughterfish are in the cell with you + Bug #3549: Blood effects occur even when a hit is resisted + Bug #3551: NPC Todwendy in german version can't interact + Bug #3552: Opening the journal when fonts are missing results in a crash + Bug #3555: SetInvisible command should not apply graphic effect + Bug #3561: Editor: changes from omwaddon are not loaded in [New Addon] mode + Bug #3562: Non-hostile NPCs can be disarmed by stealing their weapons via sneaking + Bug #3564: Editor: openmw-cs verification results + Bug #3568: Items that should be invisible are shown in the inventory + Bug #3574: Alchemy: Alembics and retorts are used in reverse + Bug #3575: Diaglog choices don't work in mw 0.40 + Bug #3576: Minor differences in AI reaction to hostile spell effects + Bug #3577: not local nolore dialog test + Bug #3578: Animation Replacer hangs after one cicle/step + Bug #3579: Bound Armor skillups and sounds + Bug #3583: Targetted GetCurrentAiPackage returns 0 + Bug #3584: Persuasion bug + Bug #3590: Vendor, Ilen Faveran, auto equips items from stock + Bug #3594: Weather doesn't seem to update correctly in Mournhold + Bug #3598: Saving doesn't save status of objects + Bug #3600: Screen goes black when trying to travel to Sadrith Mora + Bug #3608: Water ripples aren't created when walking on water + Bug #3626: Argonian NPCs swim like khajiits + Bug #3627: Cannot delete "Blessed touch" spell from spellbook + Bug #3634: An enchanted throwing weapon consumes charges from the stack in your inventory. (0.40.0) + Bug #3635: Levelled items in merchants are "re-rolled" (not bug 2952, see inside) + Feature #1118: AI combat: flee + Feature #1596: Editor: Render water + Feature #2042: Adding a non-portable Light to the inventory should cause the player to glow + Feature #3166: Editor: Instance editing mode - rotate sub mode + Feature #3167: Editor: Instance editing mode - scale sub mode + Feature #3420: ess-Importer: player control flags + Feature #3489: You shouldn't be be able to re-cast a bound equipment spell + Feature #3496: Zero-weight boots should play light boot footsteps + Feature #3516: Water Walking should give a "can't cast" message and fail when you are too deep + Feature #3519: Play audio and visual effects for all effects in a spell + Feature #3527: Double spell explosion scaling + Feature #3534: Play particle textures for spell effects + Feature #3539: Make NPCs use opponent's weapon range to decide whether to dodge + Feature #3540: Allow dodging for creatures with "biped" flag + Feature #3545: Drop shadow for items in menu + Feature #3558: Implement same spell range for "on touch" spells as original engine + Feature #3560: Allow using telekinesis with touch spells on objects + Task #3585: Some objects added by Morrowind Rebirth do not display properly their texture + +0.40.0 +------ + + Bug #1320: AiWander - Creatures in cells without pathgrids do not wander + Bug #1873: Death events are triggered at the beginning of the death animation + Bug #1996: Resting interrupts magic effects + Bug #2399: Vampires can rest in broad daylight and survive the experience + Bug #2604: Incorrect magicka recalculation + Bug #2721: Telekinesis extends interaction range where it shouldn't + Bug #2981: When waiting, NPCs can go where they wouldn't go normally. + Bug #3045: Esp files containing the letter '#' in the file name cannot be loaded on startup + Bug #3071: Slowfall does not stop momentum when jumping + Bug #3085: Plugins can not replace parent cell references with a cell reference of different type + Bug #3145: Bug with AI Cliff Racer. He will not attack you, unless you put in front of him. + Bug #3149: Editor: Weather tables were missing from regions + Bug #3201: Netch shoots over your head + Bug #3269: If you deselect a mod and try to load a save made inside a cell added by it, you end bellow the terrain in the grid 0/0 + Bug #3286: Editor: Script editor tab width + Bug #3329: Teleportation spells cause crash to desktop after build update from 0.37 to 0.38.0 + Bug #3331: Editor: Start Scripts table: Adding a script doesn't refresh the list of Start Scripts and allows to add a single script multiple times + Bug #3332: Editor: Scene view: Tool tips only occur when holding the left mouse button + Bug #3340: ESS-Importer does not separate item stacks + Bug #3342: Editor: Creation of pathgrids did not check if the pathgrid already existed + Bug #3346: "Talked to PC" is always 0 for "Hello" dialogue + Bug #3349: AITravel doesn't repeat + Bug #3370: NPCs wandering to invalid locations after training + Bug #3378: "StopCombat" command does not function in vanilla quest + Bug #3384: Battle at Nchurdamz - Larienna Macrina does not stop combat after killing Hrelvesuu + Bug #3388: Monster Respawn tied to Quicksave + Bug #3390: Strange visual effect in Dagoth Ur's chamber + Bug #3391: Inappropriate Blight weather behavior at end of main quest + Bug #3394: Replaced dialogue inherits some of its old data + Bug #3397: Actors that start the game dead always have the same death pose + Bug #3401: Sirollus Saccus sells not glass arrows + Bug #3402: Editor: Weapon data not being properly set + Bug #3405: Mulvisic Othril will not use her chitin throwing stars + Bug #3407: Tanisie Verethi will immediately detect the player + Bug #3408: Improper behavior of ashmire particles + Bug #3412: Ai Wander start time resets when saving/loading the game + Bug #3416: 1st person and 3rd person camera isn't converted from .ess correctly + Bug #3421: Idling long enough while paralyzed sometimes causes character to get stuck + Bug #3423: Sleep interruption inside dungeons too agressive + Bug #3424: Pickpocketing sometimes won't work + Bug #3432: AiFollow / AiEscort durations handled incorrectly + Bug #3434: Dead NPC's and Creatures still contribute to sneak skill increases + Bug #3437: Weather-conditioned dialogue should not play in interiors + Bug #3439: Effects cast by summon stick around after their death + Bug #3440: Parallax maps looks weird + Bug #3443: Class graphic for custom class should be Acrobat + Bug #3446: OpenMW segfaults when using Atrayonis's "Anthology Solstheim: Tomb of the Snow Prince" mod + Bug #3448: After dispelled, invisibility icon is still displayed + Bug #3453: First couple of seconds of NPC speech is muted + Bug #3455: Portable house mods lock player and npc movement up exiting house. + Bug #3456: Equipping an item will undo dispel of constant effect invisibility + Bug #3458: Constant effect restore health doesn't work during Wait + Bug #3466: It is possible to stack multiple scroll effects of the same type + Bug #3471: When two mods delete the same references, many references are not disabled by the engine. + Bug #3473: 3rd person camera can be glitched + Feature #1424: NPC "Face" function + Feature #2974: Editor: Multiple Deletion of Subrecords + Feature #3044: Editor: Render path grid v2 + Feature #3362: Editor: Configurable key bindings + Feature #3375: Make sun / moon reflections weather dependent + Feature #3386: Editor: Edit pathgrid + +0.39.0 +------ + + Bug #1384: Dark Brotherhood Assassin (and other scripted NPCs?) spawns beneath/inside solid objects + Bug #1544: "Drop" drops equipped item in a separate stack + Bug #1587: Collision detection glitches + Bug #1629: Container UI locks up in Vivec at Jeanne's + Bug #1771: Dark Brotherhood Assassin oddity in Eight Plates + Bug #1827: Unhandled NiTextureEffect in ex_dwrv_ruin30.nif + Bug #2089: When saving while swimming in water in an interior cell, you will be spawned under water on loading + Bug #2295: Internal texture not showing, nipixeldata + Bug #2363: Corpses don't disappear + Bug #2369: Respawns should be timed individually + Bug #2393: Сharacter is stuck in the tree + Bug #2444: [Mod] NPCs from Animated Morrowind appears not using proper animations + Bug #2467: Creatures do not respawn + Bug #2515: Ghosts in Ibar-Dad spawn stuck in walls + Bug #2610: FixMe script still needs to be implemented + Bug #2689: Riekling raider pig constantly screams while running + Bug #2719: Vivec don't put their hands on the knees with this replacer (Psymoniser Vivec God Replacement NPC Edition v1.0 + Bug #2737: Camera shaking when side stepping around object + Bug #2760: AI Combat Priority Problem - Use of restoration spell instead of attacking + Bug #2806: Stack overflow in LocalScripts::getNext + Bug #2807: Collision detection allows player to become stuck inside objects + Bug #2814: Stairs to Marandus have improper collision + Bug #2925: Ranes Ienith will not appear, breaking the Morag Tong and Thieves Guid questlines + Bug #3024: Editor: Creator bar in startscript subview does not accept script ID drops + Bug #3046: Sleep creature: Velk is spawned half-underground in the Thirr River Valley + Bug #3080: Calling aifollow without operant in local script every frame causes mechanics to overheat + log + Bug #3101: Regression: White guar does not move + Bug #3108: Game Freeze after Killing Diseased Rat in Foreign Quarter Tomb + Bug #3124: Bloodmoon Quest - Rite of the Wolf Giver (BM_WolfGiver) – Innocent victim won't turn werewolf + Bug #3125: Improper dialogue window behavior when talking to creatures + Bug #3130: Some wandering NPCs disappearing, cannot finish quests + Bug #3132: Editor: GMST ID named sMake Enchantment is instead named sMake when making new game from scratch + Bug #3133: OpenMW and the OpenCS are writting warnings about scripts that use the function GetDisabled. + Bug #3135: Journal entry for The Pigrim's Path missing name + Bug #3136: Dropped bow is displaced + Bug #3140: Editor: OpenMW-CS fails to open newly converted and saved omwaddon file. + Bug #3142: Duplicate Resist Magic message + Bug #3143: Azura missing her head + Bug #3146: Potion effect showing when ingredient effects are not known + Bug #3155: When executing chop attack with a spear, hands turn partly invisible + Bug #3161: Fast travel from Silt Strider or Boat Ride will break save files made afterwards + Bug #3163: Editor: Objects dropped to scene do not always save + Bug #3173: Game Crashes After Casting Recall Spell + Bug #3174: Constant effect enchantments play spell animation on dead bodies + Bug #3175: Spell effects do not wear down when caster dies + Bug #3176: NPCs appearing randomly far away from towns + Bug #3177: Submerged corpse floats ontop of water when it shouldn't (Widow Vabdas' Deed quest) + Bug #3184: Bacola Closcius in Balmora, South Wall Cornerclub spams magic effects if attacked + Bug #3207: Editor: New objects do not render + Bug #3212: Arrow of Ranged Silence + Bug #3213: Looking at Floor After Magical Transport + Bug #3220: The number of remaining ingredients in the alchemy window doesn't go down when failing to brew a potion + Bug #3222: Falling through the water in Vivec + Bug #3223: Crash at the beginning with MOD (The Symphony) + Bug #3228: Purple screen when leveling up. + Bug #3233: Infinite disposition via MWDialogue::Filter::testDisposition() glitch + Bug #3234: Armor mesh stuck on body in inventory menu + Bug #3235: Unlike vanilla, OpenMW don't allow statics and activators cast effects on the player. + Bug #3238: Not loading cells when using Poorly Placed Object Fix.esm + Bug #3248: Editor: Using the "Next Script" and "Previous Script" buttons changes the record status to "Modified" + Bug #3258: Woman biped skeleton + Bug #3259: No alternating punches + Bug #3262: Crash in class selection menu + Bug #3279: Load menu: Deleting a savegame makes scroll bar jump to the top + Bug #3326: Starting a new game, getting to class selection, then starting another new game temporarily assigns Acrobat class + Bug #3327: Stuck in table after loading when character was sneaking when quicksave + Feature #652: Editor: GMST verifier + Feature #929: Editor: Info record verifier + Feature #1279: Editor: Render cell border markers + Feature #2482: Background cell loading and caching of loaded cells + Feature #2484: Editor: point lighting + Feature #2801: Support NIF bump map textures in osg + Feature #2926: Editor: Optional line wrap in script editor wrap lines + Feature #3000: Editor: Reimplement 3D scene camera system + Feature #3035: Editor: Make scenes a drop target for referenceables + Feature #3043: Editor: Render cell markers v2 + Feature #3164: Editor: Instance Selection Menu + Feature #3165: Editor: Instance editing mode - move sub mode + Feature #3244: Allow changing water Level of Interiors behaving like exteriors + Feature #3250: Editor: Use "Enter" key instead of clicking "Create" button to confirm ID input in Creator Bar + Support #3179: Fatal error on startup + 0.38.0 ------ diff --git a/CI/before_install.linux.sh b/CI/before_install.linux.sh index 1c02bc8d9..f4b448900 100755 --- a/CI/before_install.linux.sh +++ b/CI/before_install.linux.sh @@ -1,21 +1,24 @@ #!/bin/sh +echo -n | openssl s_client -connect scan.coverity.com:443 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' | sudo tee -a /etc/ssl/certs/ca- +sudo ln -s /usr/bin/clang-3.6 /usr/local/bin/clang +sudo ln -s /usr/bin/clang++-3.6 /usr/local/bin/clang++ -if [ "${ANALYZE}" ]; then - echo "yes" | sudo add-apt-repository "deb http://llvm.org/apt/`lsb_release -sc`/ llvm-toolchain-`lsb_release -sc`-3.6 main" - wget -O - http://llvm.org/apt/llvm-snapshot.gpg.key|sudo apt-key add - -fi - -echo "yes" | sudo add-apt-repository "deb http://archive.ubuntu.com/ubuntu `lsb_release -sc` main universe restricted multiverse" -echo "yes" | sudo apt-add-repository ppa:openmw/openmw -sudo apt-get update -qq -sudo apt-get install -qq libgtest-dev google-mock -sudo apt-get install -qq libboost-filesystem-dev libboost-program-options-dev libboost-system-dev libboost-thread-dev -sudo apt-get install -qq libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev -sudo apt-get install -qq libbullet-dev libopenscenegraph-dev libmygui-dev libsdl2-dev libunshield-dev libtinyxml-dev libopenal-dev libqt4-dev -if [ "${ANALYZE}" ]; then sudo apt-get install -qq clang-3.6; fi +# build libgtest & libgtest_main sudo mkdir /usr/src/gtest/build cd /usr/src/gtest/build sudo cmake .. -DBUILD_SHARED_LIBS=1 sudo make -j4 sudo ln -s /usr/src/gtest/build/libgtest.so /usr/lib/libgtest.so sudo ln -s /usr/src/gtest/build/libgtest_main.so /usr/lib/libgtest_main.so + +cd ~/ +git clone https://github.com/TES3MP/RakNet +cd RakNet +cmake . -DRAKNET_ENABLE_DLL=OFF -DRAKNET_ENABLE_SAMPLES=OFF -DCMAKE_BUILD_TYPE=Release +mkdir ./lib +make -j3 install +cp ./Lib/RakNetLibStatic/libRakNetLibStatic.a ./lib +cd .. + +wget https://github.com/zdevito/terra/releases/download/release-2016-03-25/terra-Linux-x86_64-332a506.zip +unzip terra-Linux-x86_64-332a506.zip diff --git a/CI/before_install.osx.sh b/CI/before_install.osx.sh index 8bfe2b70f..49af86a3e 100755 --- a/CI/before_install.osx.sh +++ b/CI/before_install.osx.sh @@ -1,9 +1,11 @@ #!/bin/sh -export CXX=clang++ -export CC=clang - -brew tap openmw/openmw brew update -brew unlink boost -brew install openmw-mygui openmw-bullet openmw-sdl2 openmw-ffmpeg openmw/openmw/qt unshield + +brew rm cmake || true +brew rm pkgconfig || true +brew rm qt5 || true +brew install cmake pkgconfig $macos_qt_formula + +curl https://downloads.openmw.org/osx/dependencies/openmw-deps-0ecece4.zip -o ~/openmw-deps.zip +unzip ~/openmw-deps.zip -d /private/tmp/openmw-deps > /dev/null diff --git a/CI/before_script.linux.sh b/CI/before_script.linux.sh index 17667ad28..efdf28c6c 100755 --- a/CI/before_script.linux.sh +++ b/CI/before_script.linux.sh @@ -4,5 +4,14 @@ free -m mkdir build cd build export CODE_COVERAGE=1 -if [ "${CC}" = "clang" ]; then export CODE_COVERAGE=0; fi -${ANALYZE}cmake .. -DBUILD_WITH_CODE_COVERAGE=${CODE_COVERAGE} -DBUILD_UNITTESTS=1 -DCMAKE_INSTALL_PREFIX=/usr -DBINDIR=/usr/games -DCMAKE_BUILD_TYPE="RelWithDebInfo" -DUSE_SYSTEM_TINYXML=TRUE +export RAKNET_ROOT=~/RakNet +export Terra_ROOT=~/terra-Linux-x86_64-332a506 +export BUILD_SERVER=OFF +if [ "${CC}" = "clang" ]; then export CODE_COVERAGE=0; +else + export COMPILER_NAME=gcc + export CXX=g++-6 + export CC=gcc-6 + export BUILD_SERVER=ON +fi +${ANALYZE}cmake .. -DBUILD_OPENMW_MP=${BUILD_SERVER} -DBUILD_WITH_CODE_COVERAGE=${CODE_COVERAGE} -DBUILD_BSATOOL=OFF -DBUILD_ESMTOOL=OFF -DBUILD_ESSIMPORTER=OFF -DBUILD_LAUNCHER=OFF -DBUILD_MWINIIMPORTER=OFF -DBUILD_MYGUI_PLUGIN=OFF -DBUILD_OPENCS=OFF -DBUILD_WIZARD=OFF -DBUILD_BROWSER=OFF -DBUILD_UNITTESTS=1 -DCMAKE_INSTALL_PREFIX=/usr -DBINDIR=/usr/games -DCMAKE_BUILD_TYPE="None" -DUSE_SYSTEM_TINYXML=TRUE -DRakNet_LIBRARY_RELEASE=~/RakNet/lib/libRakNetLibStatic.a -DRakNet_LIBRARY_DEBUG=~/RakNet/lib/libRakNetLibStatic.a diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh old mode 100755 new mode 100644 index 5aa8d3e9d..aa8c565ba --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -1,5 +1,21 @@ #!/bin/bash +set -euo pipefail + +APPVEYOR=${APPVEYOR:-} +CI=${CI:-} +STEP=${STEP:-} + +VERBOSE="" +STRIP="" +SKIP_DOWNLOAD="" +SKIP_EXTRACT="" +KEEP="" +UNITY_BUILD="" +VS_VERSION="" +PLATFORM="" +CONFIGURATION="" + while [ $# -gt 0 ]; do ARGSTR=$1 shift @@ -16,10 +32,6 @@ while [ $# -gt 0 ]; do V ) VERBOSE=true ;; - v ) - VS_VERSION=$1 - shift ;; - d ) SKIP_DOWNLOAD=true ;; @@ -32,6 +44,10 @@ while [ $# -gt 0 ]; do u ) UNITY_BUILD=true ;; + v ) + VS_VERSION=$1 + shift ;; + p ) PLATFORM=$1 shift ;; @@ -78,9 +94,6 @@ done if [ -z $VERBOSE ]; then STRIP="> /dev/null 2>&1" fi -if [ -z $VS_VERSION ]; then - VS_VERSION="2013" -fi if [ -z $APPVEYOR ]; then echo "Running prebuild outside of Appveyor." @@ -90,9 +103,7 @@ if [ -z $APPVEYOR ]; then else echo "Running prebuild in Appveyor." - cd $APPVEYOR_BUILD_FOLDER - VERSION="$(cat README.md | grep Version: | awk '{ print $3; }')-$(git rev-parse --short HEAD)" - appveyor UpdateBuild -Version "$VERSION" > /dev/null & + cd "$APPVEYOR_BUILD_FOLDER" fi run_cmd() { @@ -105,7 +116,7 @@ run_cmd() { if [ $RET -ne 0 ]; then if [ -z $APPVEYOR ]; then - echo "Command $CMD failed, output can be found in `real_pwd`/output.log" + echo "Command $CMD failed, output can be found in $(real_pwd)/output.log" else echo echo "Command $CMD failed;" @@ -184,44 +195,58 @@ add_osg_dlls() { OSG_PLUGINS="$OSG_PLUGINS $@" } +QT_PLATFORMS="" +add_qt_platform_dlls() { + QT_PLATFORMS="$QT_PLATFORMS $@" +} + if [ -z $PLATFORM ]; then - PLATFORM=`uname -m` + PLATFORM="$(uname -m)" fi if [ -z $CONFIGURATION ]; then CONFIGURATION="Debug" fi +if [ -z $VS_VERSION ]; then + VS_VERSION="2013" +fi + case $VS_VERSION in - 14|2015 ) + 14|14.0|2015 ) GENERATOR="Visual Studio 14 2015" XP_TOOLSET="v140_xp" + TOOLSET="v140" + MSVC_VER="14" + MSVC_YEAR="2015" ;; -# 12|2013| - * ) + 12|12.0|2013 ) GENERATOR="Visual Studio 12 2013" XP_TOOLSET="v120_xp" + TOOLSET="v120" + MSVC_VER="12" + MSVC_YEAR="2013" ;; esac case $PLATFORM in x64|x86_64|x86-64|win64|Win64 ) - ARCHNAME=x86-64 - ARCHSUFFIX=64 - BITS=64 + ARCHNAME="x86-64" + ARCHSUFFIX="64" + BITS="64" BASE_OPTS="-G\"$GENERATOR Win64\"" add_cmake_opts "-G\"$GENERATOR Win64\"" ;; x32|x86|i686|i386|win32|Win32 ) - ARCHNAME=x86 - ARCHSUFFIX=86 - BITS=32 + ARCHNAME="x86" + ARCHSUFFIX="86" + BITS="32" - BASE_OPTS="-G\"$GENERATOR\" -T$XP_TOOLSET" - add_cmake_opts "-G\"$GENERATOR\"" -T$XP_TOOLSET + BASE_OPTS="-G\"$GENERATOR\"" + add_cmake_opts "-G\"$GENERATOR\"" ;; * ) @@ -230,35 +255,38 @@ case $PLATFORM in ;; esac -if ! [ -z $UNITY_BUILD ]; then - add_cmake_opts "-DOPENMW_UNITY_BUILD=True" -fi - case $CONFIGURATION in debug|Debug|DEBUG ) CONFIGURATION=Debug + BUILD_CONFIG=Debug ;; release|Release|RELEASE ) CONFIGURATION=Release + BUILD_CONFIG=Release ;; relwithdebinfo|RelWithDebInfo|RELWITHDEBINFO ) - CONFIGURATION=RelWithDebInfo + CONFIGURATION=Release + BUILD_CONFIG=RelWithDebInfo ;; esac +if ! [ -z $UNITY_BUILD ]; then + add_cmake_opts "-DOPENMW_UNITY_BUILD=True" +fi + echo -echo "==========================" -echo "Starting prebuild on win$BITS" -echo "==========================" +echo "===================================" +echo "Starting prebuild on MSVC${MSVC_YEAR} WIN${BITS}" +echo "===================================" echo # cd OpenMW/AppVeyor-test mkdir -p deps cd deps -DEPS="`pwd`" +DEPS="$(pwd)" if [ -z $SKIP_DOWNLOAD ]; then echo "Downloading dependency packages." @@ -266,162 +294,165 @@ if [ -z $SKIP_DOWNLOAD ]; then # Boost if [ -z $APPVEYOR ]; then - download "Boost 1.58.0" \ - http://sourceforge.net/projects/boost/files/boost-binaries/1.58.0/boost_1_58_0-msvc-12.0-$BITS.exe \ - boost-1.58.0-win$BITS.exe + download "Boost 1.61.0" \ + "http://sourceforge.net/projects/boost/files/boost-binaries/1.61.0/boost_1_61_0-msvc-${MSVC_VER}.0-${BITS}.exe" \ + "boost-1.61.0-msvc${MSVC_YEAR}-win${BITS}.exe" fi # Bullet - download "Bullet 2.83.5" \ - http://www.lysator.liu.se/~ace/OpenMW/deps/Bullet-2.83.5-win$BITS.7z \ - Bullet-2.83.5-win$BITS.7z + download "Bullet 2.86" \ + "http://www.lysator.liu.se/~ace/OpenMW/deps/Bullet-2.86-msvc${MSVC_YEAR}-win${BITS}.7z" \ + "Bullet-2.86-msvc${MSVC_YEAR}-win${BITS}.7z" # FFmpeg - download "FFmpeg 2.5.2" \ - http://ffmpeg.zeranoe.com/builds/win$BITS/shared/ffmpeg-2.5.2-win$BITS-shared.7z \ - ffmpeg$BITS-2.5.2.7z \ - http://ffmpeg.zeranoe.com/builds/win$BITS/dev/ffmpeg-2.5.2-win$BITS-dev.7z \ - ffmpeg$BITS-2.5.2-dev.7z + download "FFmpeg 3.0.1" \ + "http://ffmpeg.zeranoe.com/builds/win${BITS}/shared/ffmpeg-3.0.1-win${BITS}-shared.7z" \ + "ffmpeg-3.0.1-win${BITS}.7z" \ + "http://ffmpeg.zeranoe.com/builds/win${BITS}/dev/ffmpeg-3.0.1-win${BITS}-dev.7z" \ + "ffmpeg-3.0.1-dev-win${BITS}.7z" # MyGUI - download "MyGUI 3.2.2" \ - http://www.lysator.liu.se/~ace/OpenMW/deps/MyGUI-3.2.2-win$BITS.7z \ - MyGUI-3.2.2-win$BITS.7z + download "MyGUI 3.2.3-git" \ + "http://www.lysator.liu.se/~ace/OpenMW/deps/MyGUI-3.2.3-git-msvc${MSVC_YEAR}-win${BITS}.7z" \ + "MyGUI-3.2.3-git-msvc${MSVC_YEAR}-win${BITS}.7z" # OpenAL - download "OpenAL-Soft 1.16.0" \ - http://kcat.strangesoft.net/openal-binaries/openal-soft-1.16.0-bin.zip \ - OpenAL-Soft-1.16.0.zip + download "OpenAL-Soft 1.17.2" \ + "http://kcat.strangesoft.net/openal-binaries/openal-soft-1.17.2-bin.zip" \ + "OpenAL-Soft-1.17.2.zip" # OSG - download "OpenSceneGraph 3.3.8" \ - http://www.lysator.liu.se/~ace/OpenMW/deps/OSG-3.3.8-win$BITS.7z \ - OSG-3.3.8-win$BITS.7z + download "OpenSceneGraph 3.4.0-scrawl" \ + "http://www.lysator.liu.se/~ace/OpenMW/deps/OSG-3.4.0-scrawl-msvc${MSVC_YEAR}-win${BITS}.7z" \ + "OSG-3.4.0-scrawl-msvc${MSVC_YEAR}-win${BITS}.7z" # Qt if [ -z $APPVEYOR ]; then - download "Qt 4.8.6" \ - http://sourceforge.net/projects/qt64ng/files/qt/$ARCHNAME/4.8.6/msvc2013/qt-4.8.6-x$ARCHSUFFIX-msvc2013.7z \ - qt$BITS-4.8.6.7z + if [ $BITS == "64" ]; then + QT_SUFFIX="_64" + else + QT_SUFFIX="" + fi + + download "Qt 5.7.2" \ + "http://download.qt.io/official_releases/qt/5.7/5.7.0/qt-opensource-windows-x86-msvc${MSVC_YEAR}${QT_SUFFIX}-5.7.0.exe" \ + "qt-5.7.0-msvc${MSVC_YEAR}-win${BITS}.exe" \ + "http://www.lysator.liu.se/~ace/OpenMW/deps/qt-5-install.qs" \ + "qt-5-install.qs" fi # SDL2 - download "SDL 2.0.3" \ - https://www.libsdl.org/release/SDL2-devel-2.0.3-VC.zip \ - SDL2-2.0.3.zip + download "SDL 2.0.4" \ + "https://www.libsdl.org/release/SDL2-devel-2.0.4-VC.zip" \ + "SDL2-2.0.4.zip" fi cd .. #/.. # Set up dependencies +BUILD_DIR="MSVC${MSVC_YEAR}_${BITS}" if [ -z $KEEP ]; then echo - printf "Preparing build directory... " - - rm -rf Build_$BITS - mkdir -p Build_$BITS/deps + echo "(Re)Creating build directory." - echo Done. + rm -rf "$BUILD_DIR" fi -mkdir -p Build_$BITS/deps -cd Build_$BITS/deps -DEPS_INSTALL=`pwd` +mkdir -p "${BUILD_DIR}/deps" +cd "${BUILD_DIR}/deps" + +DEPS_INSTALL="$(pwd)" cd $DEPS echo -echo "Extracting dependencies..." +echo "Extracting dependencies, this might take a while..." +echo "---------------------------------------------------" +echo # Boost -printf "Boost 1.58.0... " +if [ -z $APPVEYOR ]; then + printf "Boost 1.61.0... " +else + if [ $MSVC_VER -eq 12 ]; then + printf "Boost 1.58.0 AppVeyor... " + else + printf "Boost 1.60.0 AppVeyor... " + fi +fi { if [ -z $APPVEYOR ]; then cd $DEPS_INSTALL - BOOST_SDK="`real_pwd`/Boost" + BOOST_SDK="$(real_pwd)/Boost" - if [ -d Boost ] && grep "BOOST_VERSION 105800" Boost/boost/version.hpp > /dev/null; then + if [ -d Boost ] && grep "BOOST_VERSION 106100" Boost/boost/version.hpp > /dev/null; then printf "Exists. " elif [ -z $SKIP_EXTRACT ]; then rm -rf Boost - $DEPS/boost-1.58.0-win$BITS.exe //dir="$(echo $BOOST_SDK | sed s,/,\\\\,g)" //verysilent + "${DEPS}/boost-1.61.0-msvc${MSVC_YEAR}-win${BITS}.exe" //dir="$(echo $BOOST_SDK | sed s,/,\\\\,g)" //verysilent fi add_cmake_opts -DBOOST_ROOT="$BOOST_SDK" \ - -DBOOST_LIBRARYDIR="$BOOST_SDK/lib$BITS-msvc-12.0" + -DBOOST_LIBRARYDIR="${BOOST_SDK}/lib${BITS}-msvc-${MSVC_VER}.0" echo Done. else # Appveyor unstable has all the boost we need already - BOOST_SDK="c:/Libraries/boost" + if [ $MSVC_VER -eq 12 ]; then + BOOST_SDK="c:/Libraries/boost_1_58_0" + else + BOOST_SDK="c:/Libraries/boost_1_60_0" + fi add_cmake_opts -DBOOST_ROOT="$BOOST_SDK" \ - -DBOOST_LIBRARYDIR="$BOOST_SDK/lib$BITS-msvc-12.0" + -DBOOST_LIBRARYDIR="${BOOST_SDK}/lib${BITS}-msvc-${MSVC_VER}.0" - echo AppVeyor. + echo Done. fi } cd $DEPS +echo # Bullet -printf "Bullet 2.83.5... " +printf "Bullet 2.86... " { cd $DEPS_INSTALL if [ -d Bullet ]; then - printf "Exists. (No version checking) " + printf -- "Exists. (No version checking) " elif [ -z $SKIP_EXTRACT ]; then rm -rf Bullet - eval 7z x -y $DEPS/Bullet-2.83.5-win$BITS.7z $STRIP - mv Bullet-2.83.5-win$BITS Bullet + eval 7z x -y "${DEPS}/Bullet-2.86-msvc${MSVC_YEAR}-win${BITS}.7z" $STRIP + mv "Bullet-2.86-msvc${MSVC_YEAR}-win${BITS}" Bullet fi - BULLET_SDK="`real_pwd`/Bullet" - add_cmake_opts -DBULLET_INCLUDE_DIR="$BULLET_SDK/include/bullet" \ - -DBULLET_COLLISION_LIBRARY="$BULLET_SDK/lib/BulletCollision.lib" \ - -DBULLET_COLLISION_LIBRARY_DEBUG="$BULLET_SDK/lib/BulletCollision_Debug.lib" \ - -DBULLET_MATH_LIBRARY="$BULLET_SDK/lib/LinearMath.lib" \ - -DBULLET_MATH_LIBRARY_DEBUG="$BULLET_SDK/lib/LinearMath_Debug.lib" + export BULLET_ROOT="$(real_pwd)/Bullet" echo Done. } cd $DEPS +echo # FFmpeg -printf "FFmpeg 2.5.2... " +printf "FFmpeg 3.0.1... " { cd $DEPS_INSTALL - if [ -d FFmpeg ] && grep "FFmpeg version: 2.5.2" FFmpeg/README.txt > /dev/null; then + if [ -d FFmpeg ] && grep "FFmpeg version: 3.0.1" FFmpeg/README.txt > /dev/null; then printf "Exists. " elif [ -z $SKIP_EXTRACT ]; then rm -rf FFmpeg - eval 7z x -y $DEPS/ffmpeg$BITS-2.5.2.7z $STRIP - eval 7z x -y $DEPS/ffmpeg$BITS-2.5.2-dev.7z $STRIP + eval 7z x -y "${DEPS}/ffmpeg-3.0.1-win${BITS}.7z" $STRIP + eval 7z x -y "${DEPS}/ffmpeg-3.0.1-dev-win${BITS}.7z" $STRIP - mv ffmpeg-2.5.2-win$BITS-shared FFmpeg - cp -r ffmpeg-2.5.2-win$BITS-dev/* FFmpeg/ - rm -rf ffmpeg-2.5.2-win$BITS-dev + mv "ffmpeg-3.0.1-win${BITS}-shared" FFmpeg + cp -r "ffmpeg-3.0.1-win${BITS}-dev/"* FFmpeg/ + rm -rf "ffmpeg-3.0.1-win${BITS}-dev" fi - FFMPEG_SDK="`real_pwd`/FFmpeg" - add_cmake_opts -DAVCODEC_INCLUDE_DIRS="$FFMPEG_SDK/include" \ - -DAVCODEC_LIBRARIES="$FFMPEG_SDK/lib/avcodec.lib" \ - -DAVDEVICE_INCLUDE_DIRS="$FFMPEG_SDK/include" \ - -DAVDEVICE_LIBRARIES="$FFMPEG_SDK/lib/avdevice.lib" \ - -DAVFORMAT_INCLUDE_DIRS="$FFMPEG_SDK/include" \ - -DAVFORMAT_LIBRARIES="$FFMPEG_SDK/lib/avformat.lib" \ - -DAVUTIL_INCLUDE_DIRS="$FFMPEG_SDK/include" \ - -DAVUTIL_LIBRARIES="$FFMPEG_SDK/lib/avutil.lib" \ - -DPOSTPROC_INCLUDE_DIRS="$FFMPEG_SDK/include" \ - -DPOSTPROC_LIBRARIES="$FFMPEG_SDK/lib/postproc.lib" \ - -DSWRESAMPLE_INCLUDE_DIRS="$FFMPEG_SDK/include" \ - -DSWRESAMPLE_LIBRARIES="$FFMPEG_SDK/lib/swresample.lib" \ - -DSWSCALE_INCLUDE_DIRS="$FFMPEG_SDK/include" \ - -DSWSCALE_LIBRARIES="$FFMPEG_SDK/lib/swscale.lib" - - add_runtime_dlls `pwd`/FFmpeg/bin/{avcodec-56,avformat-56,avutil-54,swresample-1,swscale-3}.dll + export FFMPEG_HOME="$(real_pwd)/FFmpeg" + add_runtime_dlls "$(pwd)/FFmpeg/bin/"{avcodec-57,avformat-57,avutil-55,swresample-2,swscale-4}.dll if [ $BITS -eq 32 ]; then add_cmake_opts "-DCMAKE_EXE_LINKER_FLAGS=\"/machine:X86 /safeseh:no\"" @@ -430,78 +461,79 @@ printf "FFmpeg 2.5.2... " echo Done. } cd $DEPS +echo # MyGUI -printf "MyGUI 3.2.2... " +printf "MyGUI 3.2.3-git... " { cd $DEPS_INSTALL if [ -d MyGUI ] && \ grep "MYGUI_VERSION_MAJOR 3" MyGUI/include/MYGUI/MyGUI_Prerequest.h > /dev/null && \ grep "MYGUI_VERSION_MINOR 2" MyGUI/include/MYGUI/MyGUI_Prerequest.h > /dev/null && \ - grep "MYGUI_VERSION_PATCH 2" MyGUI/include/MYGUI/MyGUI_Prerequest.h > /dev/null + grep "MYGUI_VERSION_PATCH 3" MyGUI/include/MYGUI/MyGUI_Prerequest.h > /dev/null then printf "Exists. " elif [ -z $SKIP_EXTRACT ]; then rm -rf MyGUI - eval 7z x -y $DEPS/MyGUI-3.2.2-win$BITS.7z $STRIP - mv MyGUI-3.2.2-win$BITS MyGUI + eval 7z x -y "${DEPS}/MyGUI-3.2.3-git-msvc${MSVC_YEAR}-win${BITS}.7z" $STRIP + mv "MyGUI-3.2.3-git-msvc${MSVC_YEAR}-win${BITS}" MyGUI fi - MYGUI_SDK="`real_pwd`/MyGUI" - - add_cmake_opts -DMYGUISDK="$MYGUI_SDK" \ - -DMYGUI_INCLUDE_DIRS="$MYGUI_SDK/include/MYGUI" \ - -DMYGUI_PREQUEST_FILE="$MYGUI_SDK/include/MYGUI/MyGUI_Prerequest.h" + export MYGUI_HOME="$(real_pwd)/MyGUI" if [ $CONFIGURATION == "Debug" ]; then SUFFIX="_d" else SUFFIX="" fi - add_runtime_dlls `pwd`/MyGUI/bin/$CONFIGURATION/MyGUIEngine$SUFFIX.dll + add_runtime_dlls "$(pwd)/MyGUI/bin/${CONFIGURATION}/MyGUIEngine${SUFFIX}.dll" echo Done. } cd $DEPS +echo # OpenAL -printf "OpenAL-Soft 1.16.0... " +printf "OpenAL-Soft 1.17.2... " { - if [ -d openal-soft-1.16.0-bin ]; then + if [ -d openal-soft-1.17.2-bin ]; then printf "Exists. " elif [ -z $SKIP_EXTRACT ]; then - rm -rf openal-soft-1.16.0-bin - eval 7z x -y OpenAL-Soft-1.16.0.zip $STRIP + rm -rf openal-soft-1.17.2-bin + eval 7z x -y OpenAL-Soft-1.17.2.zip $STRIP fi - OPENAL_SDK="`real_pwd`/openal-soft-1.16.0-bin" + OPENAL_SDK="$(real_pwd)/openal-soft-1.17.2-bin" + + add_cmake_opts -DOPENAL_INCLUDE_DIR="${OPENAL_SDK}/include/AL" \ + -DOPENAL_LIBRARY="${OPENAL_SDK}/libs/Win${BITS}/OpenAL32.lib" - add_cmake_opts -DOPENAL_INCLUDE_DIR="$OPENAL_SDK/include/AL" \ - -DOPENAL_LIBRARY="$OPENAL_SDK/libs/Win$BITS/OpenAL32.lib" + add_runtime_dlls "$(pwd)/openal-soft-1.17.2-bin/bin/WIN${BITS}/soft_oal.dll:OpenAL32.dll" echo Done. } cd $DEPS +echo # OSG -printf "OSG 3.3.8... " +printf "OSG 3.4.0-scrawl... " { cd $DEPS_INSTALL if [ -d OSG ] && \ grep "OPENSCENEGRAPH_MAJOR_VERSION 3" OSG/include/osg/Version > /dev/null && \ - grep "OPENSCENEGRAPH_MINOR_VERSION 3" OSG/include/osg/Version > /dev/null && \ - grep "OPENSCENEGRAPH_PATCH_VERSION 8" OSG/include/osg/Version > /dev/null + grep "OPENSCENEGRAPH_MINOR_VERSION 4" OSG/include/osg/Version > /dev/null && \ + grep "OPENSCENEGRAPH_PATCH_VERSION 0" OSG/include/osg/Version > /dev/null then printf "Exists. " elif [ -z $SKIP_EXTRACT ]; then rm -rf OSG - eval 7z x -y $DEPS/OSG-3.3.8-win$BITS.7z $STRIP - mv OSG-3.3.8-win$BITS OSG + eval 7z x -y "${DEPS}/OSG-3.4.0-scrawl-msvc${MSVC_YEAR}-win${BITS}.7z" $STRIP + mv "OSG-3.4.0-scrawl-msvc${MSVC_YEAR}-win${BITS}" OSG fi - OSG_SDK="`real_pwd`/OSG" + OSG_SDK="$(real_pwd)/OSG" add_cmake_opts -DOSG_DIR="$OSG_SDK" @@ -511,85 +543,101 @@ printf "OSG 3.3.8... " SUFFIX="" fi - add_runtime_dlls `pwd`/OSG/bin/{OpenThreads,zlib}$SUFFIX.dll \ - `pwd`/OSG/bin/osg{,Animation,DB,FX,GA,Particle,Qt,Text,Util,Viewer}$SUFFIX.dll + add_runtime_dlls "$(pwd)/OSG/bin/"{OpenThreads,zlib,libpng*}${SUFFIX}.dll \ + "$(pwd)/OSG/bin/osg"{,Animation,DB,FX,GA,Particle,Text,Util,Viewer}${SUFFIX}.dll - add_osg_dlls `pwd`/OSG/bin/osgPlugins-3.3.8/osgdb_{bmp,dds,gif,jpeg,png,tga}$SUFFIX.dll + add_osg_dlls "$(pwd)/OSG/bin/osgPlugins-3.4.0/osgdb_"{bmp,dds,jpeg,osg,png,tga}${SUFFIX}.dll + add_osg_dlls "$(pwd)/OSG/bin/osgPlugins-3.4.0/osgdb_serializers_osg"{,animation,fx,ga,particle,text,util,viewer}${SUFFIX}.dll echo Done. } cd $DEPS +echo # Qt if [ -z $APPVEYOR ]; then - printf "Qt 4.8.6... " + printf "Qt 5.7.0... " else - printf "Qt 5.4... " + printf "Qt 5.7 AppVeyor... " fi { + if [ $BITS -eq 64 ]; then + SUFFIX="_64" + else + SUFFIX="" + fi + if [ -z $APPVEYOR ]; then cd $DEPS_INSTALL - QT_SDK="`real_pwd`/Qt" + QT_SDK="$(real_pwd)/Qt/5.7/msvc${MSVC_YEAR}${SUFFIX}" - if [ -d Qt ] && head -n2 Qt/BUILDINFO.txt | grep "4.8.6" > /dev/null; then + if [ -d Qt ] && head -n2 Qt/InstallationLog.txt | grep "5.7.0" > /dev/null; then printf "Exists. " elif [ -z $SKIP_EXTRACT ]; then rm -rf Qt - eval 7z x -y $DEPS/qt$BITS-4.8.6.7z $STRIP - mv qt-4.8.6-* Qt - cd Qt - eval ./qtbinpatcher.exe $STRIP + cp "${DEPS}/qt-5-install.qs" qt-install.qs + + + sed -i "s|INSTALL_DIR|$(real_pwd)/Qt|" qt-install.qs + sed -i "s/qt.VERSION.winBITS_msvcYEAR/qt.57.win${BITS}_msvc${MSVC_YEAR}${SUFFIX}/" qt-install.qs + + printf -- "(Installation might take a while) " + "${DEPS}/qt-5.7.0-msvc${MSVC_YEAR}-win${BITS}.exe" --script qt-install.qs --silent + + mv qt-install.qs Qt/ + + echo Done. + printf " Cleaning up extraneous data... " + rm -r "$(real_pwd)/Qt/"{dist,Docs,Examples,Tools,vcredist,components.xml,MaintenanceTool.dat,MaintenanceTool.exe,MaintenanceTool.ini,network.xml,qt-install.qs} fi cd $QT_SDK - add_cmake_opts -DDESIRED_QT_VERSION=4 \ - -DQT_QMAKE_EXECUTABLE="$QT_SDK/bin/qmake.exe" + add_cmake_opts -DDESIRED_QT_VERSION=5 \ + -DQT_QMAKE_EXECUTABLE="${QT_SDK}/bin/qmake.exe" \ + -DCMAKE_PREFIX_PATH="$QT_SDK" if [ $CONFIGURATION == "Debug" ]; then - SUFFIX="d4" + SUFFIX="d" else - SUFFIX="4" + SUFFIX="" fi - add_runtime_dlls `pwd`/bin/Qt{Core,Gui,Network,OpenGL}$SUFFIX.dll + add_runtime_dlls "$(pwd)/bin/lib"{EGL,GLESv2}${SUFFIX}.dll \ + "$(pwd)/bin/Qt5"{Core,Gui,Network,OpenGL,Widgets}${SUFFIX}.dll + add_qt_platform_dlls "$(pwd)/plugins/platforms/qwindows${SUFFIX}.dll" echo Done. else - if [ $BITS -eq 32 ]; then - QT_SDK="C:/Qt/5.4/msvc2013_opengl" - else - QT_SDK="C:/Qt/5.4/msvc2013_64_opengl" - fi + QT_SDK="C:/Qt/5.7/msvc${MSVC_YEAR}${SUFFIX}" add_cmake_opts -DDESIRED_QT_VERSION=5 \ - -DQT_QMAKE_EXECUTABLE="$QT_SDK/bin/qmake.exe" \ + -DQT_QMAKE_EXECUTABLE="${QT_SDK}/bin/qmake.exe" \ -DCMAKE_PREFIX_PATH="$QT_SDK" - echo AppVeyor. + echo Done. fi } cd $DEPS +echo # SDL2 -printf "SDL 2.0.3... " +printf "SDL 2.0.4... " { - if [ -d SDL2-2.0.3 ]; then + if [ -d SDL2-2.0.4 ]; then printf "Exists. " elif [ -z $SKIP_EXTRACT ]; then - rm -rf SDL2-2.0.3 - eval 7z x -y SDL2-2.0.3.zip $STRIP + rm -rf SDL2-2.0.4 + eval 7z x -y SDL2-2.0.4.zip $STRIP fi - SDL_SDK="`real_pwd`/SDL2-2.0.3" - add_cmake_opts -DSDL2_INCLUDE_DIR="$SDL_SDK/include" \ - -DSDL2MAIN_LIBRARY="$SDL_SDK/lib/x$ARCHSUFFIX/SDL2main.lib" \ - -DSDL2_LIBRARY_PATH="$SDL_SDK/lib/x$ARCHSUFFIX/SDL2.lib" + export SDL2DIR="$(real_pwd)/SDL2-2.0.4" - add_runtime_dlls `pwd`/SDL2-2.0.3/lib/x$ARCHSUFFIX/SDL2.dll + add_runtime_dlls "$(pwd)/SDL2-2.0.4/lib/x${ARCHSUFFIX}/SDL2.dll" echo Done. } +echo cd $DEPS_INSTALL/.. @@ -602,12 +650,10 @@ add_cmake_opts -DBUILD_BSATOOL=no \ -DBUILD_MYGUI_PLUGIN=no \ -DOPENMW_MP_BUILD=on -if [ -z $CI ]; then - echo " (Outside of CI, doing full build.)" -else +if [ ! -z $CI ]; then case $STEP in components ) - echo " Subproject: Components." + echo " Building subproject: Components." add_cmake_opts -DBUILD_ESSIMPORTER=no \ -DBUILD_LAUNCHER=no \ -DBUILD_MWINIIMPORTER=no \ @@ -615,64 +661,98 @@ else -DBUILD_OPENMW=no \ -DBUILD_WIZARD=no ;; + openmw ) - echo " Subproject: OpenMW." + echo " Building subproject: OpenMW." add_cmake_opts -DBUILD_ESSIMPORTER=no \ -DBUILD_LAUNCHER=no \ -DBUILD_MWINIIMPORTER=no \ -DBUILD_OPENCS=no \ -DBUILD_WIZARD=no ;; + opencs ) - echo " Subproject: OpenCS." + echo " Building subproject: OpenCS." add_cmake_opts -DBUILD_ESSIMPORTER=no \ -DBUILD_LAUNCHER=no \ -DBUILD_MWINIIMPORTER=no \ -DBUILD_OPENMW=no \ -DBUILD_WIZARD=no ;; + misc ) - echo " Subproject: Misc." + echo " Building subprojects: Misc." add_cmake_opts -DBUILD_OPENCS=no \ -DBUILD_OPENMW=no ;; - * ) - echo " Building everything." - ;; esac fi +# NOTE: Disable this when/if we want to run test cases +if [ -z $CI ]; then + echo "- Copying Runtime DLLs..." + mkdir -p $BUILD_CONFIG + for DLL in $RUNTIME_DLLS; do + TARGET="$(basename "$DLL")" + if [[ "$DLL" == *":"* ]]; then + IFS=':'; SPLIT=( ${DLL} ); unset IFS + + DLL=${SPLIT[0]} + TARGET=${SPLIT[1]} + fi + + echo " ${TARGET}." + cp "$DLL" "$BUILD_CONFIG/$TARGET" + done + echo + + echo "- OSG Plugin DLLs..." + mkdir -p $BUILD_CONFIG/osgPlugins-3.4.0 + for DLL in $OSG_PLUGINS; do + echo " $(basename $DLL)." + cp "$DLL" $BUILD_CONFIG/osgPlugins-3.4.0 + done + echo + + echo "- Qt Platform DLLs..." + mkdir -p ${BUILD_CONFIG}/platforms + for DLL in $QT_PLATFORMS; do + echo " $(basename $DLL)" + cp "$DLL" "${BUILD_CONFIG}/platforms" + done + echo +fi + if [ -z $VERBOSE ]; then - printf " Configuring... " + printf -- "- Configuring... " else - echo " cmake .. $CMAKE_OPTS" + echo "- cmake .. $CMAKE_OPTS" fi run_cmd cmake .. $CMAKE_OPTS RET=$? if [ -z $VERBOSE ]; then - if [ $RET -eq 0 ]; then echo Done. - else echo Failed.; fi + if [ $RET -eq 0 ]; then + echo Done. + else + echo Failed. + fi fi -echo - -# NOTE: Disable this when/if we want to run test cases if [ -z $CI ]; then - echo "Copying Runtime DLLs..." - mkdir -p $CONFIGURATION - for DLL in $RUNTIME_DLLS; do - echo " `basename $DLL`." - cp "$DLL" $CONFIGURATION/ - done - echo "OSG Plugin DLLs..." - mkdir -p $CONFIGURATION/osgPlugins-3.3.8 - for DLL in $OSG_PLUGINS; do - echo " `basename $DLL`." - cp "$DLL" $CONFIGURATION/osgPlugins-3.3.8 - done + echo "- Copying Runtime Resources/Config Files" + echo " gamecontrollerdb.txt" + cp gamecontrollerdb.txt $BUILD_CONFIG/gamecontrollerdb.txt + echo " openmw.cfg" + cp openmw.cfg.install $BUILD_CONFIG/openmw.cfg + echo " openmw-cs.cfg" + cp openmw-cs.cfg $BUILD_CONFIG/openmw-cs.cfg + echo " settings-default.cfg" + cp settings-default.cfg $BUILD_CONFIG/settings-default.cfg + echo " resources/" + cp -r resources $BUILD_CONFIG/resources echo fi -exit $RET +exit $RET \ No newline at end of file diff --git a/CI/before_script.osx.sh b/CI/before_script.osx.sh index 772284f91..f3d0f716b 100755 --- a/CI/before_script.osx.sh +++ b/CI/before_script.osx.sh @@ -1,5 +1,22 @@ #!/bin/sh +export CXX=clang++ +export CC=clang + +DEPENDENCIES_ROOT="/private/tmp/openmw-deps/openmw-deps" +QT_PATH=`brew --prefix $macos_qt_formula` + mkdir build cd build -cmake -DCMAKE_FRAMEWORK_PATH="/usr/local/lib/macosx/Release" -DCMAKE_EXE_LINKER_FLAGS="-F/usr/local/lib/macosx/Release" -DCMAKE_CXX_FLAGS="-stdlib=libstdc++" -DCMAKE_BUILD_TYPE=Debug -DBUILD_MYGUI_PLUGIN=OFF -G"Unix Makefiles" .. + +cmake \ +-D CMAKE_PREFIX_PATH="$DEPENDENCIES_ROOT;$QT_PATH" \ +-D CMAKE_OSX_DEPLOYMENT_TARGET="10.8" \ +-D CMAKE_OSX_SYSROOT="macosx10.12" \ +-D CMAKE_BUILD_TYPE=Debug \ +-D OPENMW_OSX_DEPLOYMENT=TRUE \ +-D DESIRED_QT_VERSION=5 \ +-D BUILD_ESMTOOL=FALSE \ +-D BUILD_MYGUI_PLUGIN=FALSE \ +-G"Unix Makefiles" \ +.. diff --git a/CI/build.msvc.sh b/CI/build.msvc.sh index 731c51eda..f8d5a2f24 100644 --- a/CI/build.msvc.sh +++ b/CI/build.msvc.sh @@ -1,5 +1,13 @@ #!/bin/bash +APPVEYOR="" +CI="" + +PACKAGE="" +PLATFORM="" +CONFIGURATION="" +VS_VERSION="" + if [ -z $PLATFORM ]; then PLATFORM=`uname -m` fi @@ -8,39 +16,63 @@ if [ -z $CONFIGURATION ]; then CONFIGURATION="Debug" fi +case $VS_VERSION in + 14|14.0|2015 ) + GENERATOR="Visual Studio 14 2015" + MSVC_YEAR="2015" + MSVC_VER="14.0" + ;; + +# 12|2013| + * ) + GENERATOR="Visual Studio 12 2013" + MSVC_YEAR="2013" + MVSC_VER="12.0" + ;; +esac + case $PLATFORM in + x64|x86_64|x86-64|win64|Win64 ) + BITS=64 + ;; + x32|x86|i686|i386|win32|Win32 ) BITS=32 - PLATFORM=Win32 ;; +esac - x64|x86_64|x86-64|win64|Win64 ) - BITS=64 - PLATFORM=x64 +case $CONFIGURATION in + debug|Debug|DEBUG ) + CONFIGURATION=Debug ;; - * ) - echo "Unknown platform $PLATFORM." - exit 1 ;; + release|Release|RELEASE ) + CONFIGURATION=Release + ;; + + relwithdebinfo|RelWithDebInfo|RELWITHDEBINFO ) + CONFIGURATION=RelWithDebInfo + ;; esac if [ -z $APPVEYOR ]; then - echo "Running $BITS-bit $CONFIGURATION build outside of Appveyor." + echo "Running ${BITS}-bit MSVC${MSVC_YEAR} ${CONFIGURATION} build outside of Appveyor." DIR=$(echo "$0" | sed "s,\\\\,/,g" | sed "s,\(.\):,/\\1,") cd $(dirname "$DIR")/.. else - echo "Running $BITS-bit $CONFIGURATION build in Appveyor." + echo "Running ${BITS}-bit MSVC${MSVC_YEAR} ${CONFIGURATION} build in Appveyor." cd $APPVEYOR_BUILD_FOLDER fi -cd build_$BITS +BUILD_DIR="MSVC${MSVC_YEAR}_${BITS}" +cd ${BUILD_DIR} which msbuild > /dev/null if [ $? -ne 0 ]; then msbuild() { - /c/Program\ Files\ \(x86\)/MSBuild/12.0/Bin/MSBuild.exe "$@" + /c/Program\ Files\ \(x86\)/MSBuild/${MSVC_VER}/Bin/MSBuild.exe "$@" } fi diff --git a/CMakeLists.txt b/CMakeLists.txt index 52970cfb5..cc85da9ad 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,7 +25,7 @@ endif() message(STATUS "Configuring OpenMW...") set(OPENMW_VERSION_MAJOR 0) -set(OPENMW_VERSION_MINOR 38) +set(OPENMW_VERSION_MINOR 41) set(OPENMW_VERSION_RELEASE 0) set(OPENMW_VERSION_COMMITHASH "") @@ -46,7 +46,7 @@ if(EXISTS ${PROJECT_SOURCE_DIR}/.git) else(NOT EXISTS ${PROJECT_SOURCE_DIR}/.git/shallow) message(STATUS "Shallow Git clone detected, not attempting to retrieve version info") endif(NOT EXISTS ${PROJECT_SOURCE_DIR}/.git/shallow) -endif(EXISTS ${PROJECT_SOURCE_DIR}/.git) +endif(EXISTS ${PROJECT_SOURCE_DIR}/.git) # Macros include(OpenMWMacros) @@ -65,9 +65,11 @@ option(OPENMW_UNITY_BUILD "Use fewer compilation units to speed up compile time" # Apps and tools option(BUILD_OPENMW "build OpenMW" ON) +option(BUILD_OPENMW_MP "build OpenMW-MP" ON) option(BUILD_BSATOOL "build BSA extractor" ON) option(BUILD_ESMTOOL "build ESM inspector" ON) option(BUILD_LAUNCHER "build Launcher" ON) +option(BUILD_BROWSER "build tes3mp Server Browser" ON) option(BUILD_MWINIIMPORTER "build MWiniImporter" ON) option(BUILD_ESSIMPORTER "build ESS (Morrowind save game) importer" ON) option(BUILD_OPENCS "build OpenMW Construction Set" ON) @@ -76,6 +78,14 @@ option(BUILD_WITH_CODE_COVERAGE "Enable code coverage with gconv" OFF) option(BUILD_UNITTESTS "Enable Unittests with Google C++ Unittest" OFF) option(BUILD_NIFTEST "build nif file tester" OFF) option(BUILD_MYGUI_PLUGIN "build MyGUI plugin for OpenMW resources, to use with MyGUI tools" ON) +option(BUILD_DOCS "build documentation." OFF ) + +# what is necessary to build documentation +IF( BUILD_DOCS ) + # Builds the documentation. + FIND_PACKAGE( Sphinx REQUIRED ) + FIND_PACKAGE( Doxygen REQUIRED ) +ENDIF() # OS X deployment option(OPENMW_OSX_DEPLOYMENT OFF) @@ -88,7 +98,7 @@ endif() # Set up common paths if (APPLE) set(MORROWIND_DATA_FILES "./data" CACHE PATH "location of Morrowind data files") - set(OPENMW_RESOURCE_FILES "./resources" CACHE PATH "location of OpenMW resources files") + set(OPENMW_RESOURCE_FILES "../Resources/resources" CACHE PATH "location of OpenMW resources files") elseif(UNIX) # Paths SET(BINDIR "${CMAKE_INSTALL_PREFIX}/bin" CACHE PATH "Where to install binaries") @@ -116,7 +126,10 @@ if (WIN32) option(USE_DEBUG_CONSOLE "whether a debug console should be enabled for debug builds, if false debug output is redirected to Visual Studio output" ON) endif() -if (NOT BUILD_LAUNCHER AND NOT BUILD_OPENCS AND NOT BUILD_WIZARD) +find_package(RakNet REQUIRED) +include_directories(${RakNet_INCLUDES}) + +if (NOT BUILD_LAUNCHER AND NOT BUILD_BROWSER AND NOT BUILD_OPENCS AND NOT BUILD_WIZARD) set(USE_QT FALSE) else() set(USE_QT TRUE) @@ -124,7 +137,7 @@ endif() # Dependencies if (USE_QT) - set(DESIRED_QT_VERSION 4 CACHE STRING "The QT version OpenMW should use (4 or 5)") + set(DESIRED_QT_VERSION 5 CACHE STRING "The QT version OpenMW should use (4 or 5)") set_property(CACHE DESIRED_QT_VERSION PROPERTY STRINGS 4 5) message(STATUS "Using Qt${DESIRED_QT_VERSION}") @@ -140,41 +153,31 @@ if (USE_QT) endif() endif() -if (USE_QT AND DESIRED_QT_VERSION MATCHES 5) - # 2.8.11+ is required to make Qt5 happy and allow linking QtMain on Windows. - cmake_minimum_required(VERSION 2.8.11) +if (APPLE) + # OS X build process relies on this fix: https://github.com/Kitware/CMake/commit/3df5147043d83aa09acd5c9ce31d5c602efb99db + cmake_minimum_required(VERSION 3.1.0) +elseif (USE_QT AND DESIRED_QT_VERSION MATCHES 5) + # 2.8.11+ is required to make Qt5 happy and allow linking QtMain on Windows. + cmake_minimum_required(VERSION 2.8.11) else() - # We probably support older versions than this. - cmake_minimum_required(VERSION 2.6) + # We probably support older versions than this. + cmake_minimum_required(VERSION 2.6) endif() -# Sound setup -unset(FFMPEG_LIBRARIES CACHE) - -find_package(FFmpeg REQUIRED) - -set (FFMPEG_LIBRARIES ${FFMPEG_LIBRARIES} ${SWSCALE_LIBRARY} ${SWRESAMPLE_LIBRARIES}) - -if ( NOT AVCODEC_FOUND OR NOT AVFORMAT_FOUND OR NOT AVUTIL_FOUND OR NOT SWSCALE_FOUND OR NOT SWRESAMPLE_FOUND) - message(FATAL_ERROR "FFmpeg component required, but not found!") -endif() -# Required for building the FFmpeg headers -add_definitions(-D__STDC_CONSTANT_MACROS) +IF(BUILD_OPENMW OR BUILD_OPENCS) + # Sound setup + find_package(FFmpeg REQUIRED COMPONENTS AVCODEC AVFORMAT AVUTIL SWSCALE SWRESAMPLE) + # Required for building the FFmpeg headers + add_definitions(-D__STDC_CONSTANT_MACROS) # TinyXML option(USE_SYSTEM_TINYXML "Use system TinyXML library instead of internal." OFF) -if(USE_SYSTEM_TINYXML) - find_library(TINYXML_LIBRARIES tinyxml) - find_path(TINYXML_INCLUDE_DIR tinyxml.h) - message(STATUS "Found TinyXML: ${TINYXML_LIBRARIES} ${TINYXML_INCLUDE_DIR}") +if (USE_SYSTEM_TINYXML) + find_package(TinyXML REQUIRED) add_definitions (-DTIXML_USE_STL) - if(TINYXML_LIBRARIES AND TINYXML_INCLUDE_DIR) - include_directories(${TINYXML_INCLUDE_DIR}) - message(STATUS "Using system TinyXML library.") - else() - message(FATAL_ERROR "Detection of system TinyXML incomplete.") - endif() + include_directories(SYSTEM ${TinyXML_INCLUDE_DIRS}) endif() +ENDIF(BUILD_OPENMW OR BUILD_OPENCS) # Platform specific if (WIN32) @@ -190,9 +193,10 @@ if (WIN32) add_definitions(-DNOMINMAX -DWIN32_LEAN_AND_MEAN) endif() -if (ANDROID) - set(OPENGL_ES TRUE CACHE BOOL "enable opengl es support for android" FORCE) -endif (ANDROID) +if (NOT WIN32 AND BUILD_WIZARD) # windows users can just run the morrowind installer + find_package(LIBUNSHIELD REQUIRED) # required only for non win32 when building openmw-wizard + set(OPENMW_USE_UNSHIELD TRUE) +endif() option(OPENGL_ES "enable opengl es support" FALSE ) @@ -213,108 +217,77 @@ if(NOT HAVE_STDINT_H) message(FATAL_ERROR "stdint.h was not found" ) endif() -include (CheckIncludeFileCXX) -check_include_file_cxx(unordered_map HAVE_UNORDERED_MAP) -if (HAVE_UNORDERED_MAP) - add_definitions(-DHAVE_UNORDERED_MAP) -endif () - - -set(BOOST_COMPONENTS system filesystem program_options thread) -if(WIN32) - set(BOOST_COMPONENTS ${BOOST_COMPONENTS} locale) -endif(WIN32) - -IF(BOOST_STATIC) - set(Boost_USE_STATIC_LIBS ON) -endif() - -if (USE_QT) - set (OSG_QT osgQt) -endif() - -find_package(OpenSceneGraph 3.2.0 REQUIRED osgDB osgViewer osgText osgGA osgAnimation osgParticle ${OSG_QT} osgUtil osgFX) - -include_directories(${OPENSCENEGRAPH_INCLUDE_DIRS}) - -if(OSG_STATIC) - macro(use_static_osg_plugin_library PLUGIN_NAME) - set(PLUGIN_NAME_DBG ${PLUGIN_NAME}d ${PLUGIN_NAME}D ${PLUGIN_NAME}_d ${PLUGIN_NAME}_D ${PLUGIN_NAME}_debug ${PLUGIN_NAME}) - - # For now, users wishing to do a static build will need to pass the path to where the plugins reside - # More clever logic would need to deduce the path, probably installed under /lib/osgPlugins- - find_library(${PLUGIN_NAME}_LIBRARY_REL NAMES ${PLUGIN_NAME} HINTS ${OSG_PLUGIN_LIB_SEARCH_PATH}) - find_library(${PLUGIN_NAME}_LIBRARY_DBG NAMES ${PLUGIN_NAME_DBG} HINTS ${OSG_PLUGIN_LIB_SEARCH_PATH}) - make_library_set(${PLUGIN_NAME}_LIBRARY) - if("${${PLUGIN_NAME}_LIBRARY}" STREQUAL "") - message(FATAL_ERROR "Unable to find static OpenSceneGraph plugin: ${PLUGIN_NAME}") - endif() +IF(BUILD_OPENMW OR BUILD_OPENCS) - set(OPENSCENEGRAPH_LIBRARIES ${OPENSCENEGRAPH_LIBRARIES} ${${PLUGIN_NAME}_LIBRARY}) - endmacro() + find_package(OpenSceneGraph 3.3.4 REQUIRED osgDB osgViewer osgText osgGA osgAnimation osgParticle osgUtil osgFX) - macro(use_static_osg_plugin_dep DEPENDENCY) - find_package(${DEPENDENCY} REQUIRED) + set(USED_OSG_PLUGINS + osgdb_bmp + osgdb_dds + osgdb_jpeg + osgdb_osg + osgdb_png + osgdb_serializers_osg + osgdb_tga + ) + + get_filename_component(OSG_LIB_DIR ${OSGDB_LIBRARY} DIRECTORY) + set(OSGPlugins_LIB_DIR "${OSG_LIB_DIR}/osgPlugins-${OPENSCENEGRAPH_VERSION}") + + if(OSG_STATIC) + add_definitions(-DOSG_LIBRARY_STATIC) + + find_package(OSGPlugins REQUIRED COMPONENTS ${USED_OSG_PLUGINS}) + list(APPEND OPENSCENEGRAPH_LIBRARIES ${OSGPlugins_LIBRARIES}) + endif() - set(OPENSCENEGRAPH_LIBRARIES ${OPENSCENEGRAPH_LIBRARIES} ${${DEPENDENCY}_LIBRARIES}) - endmacro() + if(QT_STATIC) + if(WIN32) + if(DESIRED_QT_VERSION MATCHES 4) + # QtCore needs WSAAsyncSelect from Ws2_32.lib + set(QT_QTCORE_LIBRARY ${QT_QTCORE_LIBRARY} Ws2_32.lib) + message("QT_QTCORE_LIBRARY: ${QT_QTCORE_LIBRARY}") + endif() + endif() + endif() - add_definitions(-DOSG_LIBRARY_STATIC) + set(REQUIRED_BULLET_VERSION 286) # Bullet 286 required due to runtime bugfixes for btCapsuleShape + if (DEFINED ENV{TRAVIS_BRANCH} OR DEFINED ENV{APPVEYOR}) + set(REQUIRED_BULLET_VERSION 283) # but for build testing, 283 is fine + endif() - set(PLUGIN_LIST - osgdb_png # depends on libpng, zlib - osgdb_tga - osgdb_dds - osgdb_jpeg # depends on libjpeg - ) + find_package(MyGUI 3.2.1 REQUIRED) + find_package(SDL2 REQUIRED) + find_package(OpenAL REQUIRED) + find_package(Bullet ${REQUIRED_BULLET_VERSION} REQUIRED COMPONENTS BulletCollision LinearMath) - foreach(PLUGIN ${PLUGIN_LIST}) - use_static_osg_plugin_library(${PLUGIN}) - endforeach() +ENDIF(BUILD_OPENMW OR BUILD_OPENCS) - # OSG static plugins need to linked against their respective dependencies - set(PLUGIN_DEPS_LIST - PNG # needed by osgdb_png - ZLIB # needed by osgdb_png - JPEG # needed by osgdb_jpeg - ) +include_directories(${OPENSCENEGRAPH_INCLUDE_DIRS}) - foreach(DEPENDENCY ${PLUGIN_DEPS_LIST}) - use_static_osg_plugin_dep(${DEPENDENCY}) - endforeach() -endif() -if(QT_STATIC) - if(WIN32) - if(DESIRED_QT_VERSION MATCHES 4) - # QtCore needs WSAAsyncSelect from Ws2_32.lib - set(QT_QTCORE_LIBRARY ${QT_QTCORE_LIBRARY} Ws2_32.lib) - message("QT_QTCORE_LIBRARY: ${QT_QTCORE_LIBRARY}") - endif() - endif() -endif() +set(BOOST_COMPONENTS system filesystem program_options) +if(WIN32) + set(BOOST_COMPONENTS ${BOOST_COMPONENTS} locale) +endif(WIN32) -find_package(MyGUI REQUIRED) -if (${MYGUI_VERSION} VERSION_LESS "3.2.1") - message(FATAL_ERROR "OpenMW requires MyGUI 3.2.1 or later, please install the latest version from http://mygui.info") +IF(BOOST_STATIC) + set(Boost_USE_STATIC_LIBS ON) endif() find_package(Boost REQUIRED COMPONENTS ${BOOST_COMPONENTS}) -find_package(SDL2 REQUIRED) -find_package(OpenAL REQUIRED) -find_package(Bullet REQUIRED) include_directories("." SYSTEM ${SDL2_INCLUDE_DIR} ${Boost_INCLUDE_DIR} - ${MYGUI_INCLUDE_DIRS} + ${MyGUI_INCLUDE_DIRS} ${OPENAL_INCLUDE_DIR} - ${BULLET_INCLUDE_DIRS} + ${Bullet_INCLUDE_DIRS} ) -link_directories(${SDL2_LIBRARY_DIRS} ${Boost_LIBRARY_DIRS} ${MYGUI_LIB_DIR}) +link_directories(${SDL2_LIBRARY_DIRS} ${Boost_LIBRARY_DIRS}) if(MYGUI_STATIC) add_definitions(-DMYGUI_STATIC) @@ -331,6 +304,11 @@ endif (APPLE) # Set up DEBUG define set_directory_properties(PROPERTIES COMPILE_DEFINITIONS_DEBUG DEBUG=1) +if (NOT APPLE) + set(OPENMW_MYGUI_FILES_ROOT ${OpenMW_BINARY_DIR}) + set(OPENMW_SHADERS_ROOT ${OpenMW_BINARY_DIR}) +endif () + add_subdirectory(files/) # Specify build paths @@ -349,14 +327,24 @@ endif (APPLE) # Other files +configure_file(${OpenMW_SOURCE_DIR}/files/tes3mp/tes3mp-client-default.cfg + "${OpenMW_BINARY_DIR}/tes3mp-client-default.cfg") + +configure_file(${OpenMW_SOURCE_DIR}/files/tes3mp/tes3mp-server-default.cfg + "${OpenMW_BINARY_DIR}/tes3mp-server-default.cfg") + configure_file(${OpenMW_SOURCE_DIR}/files/settings-default.cfg "${OpenMW_BINARY_DIR}/settings-default.cfg") -configure_file(${OpenMW_SOURCE_DIR}/files/openmw.cfg.local - "${OpenMW_BINARY_DIR}/openmw.cfg") - -configure_file(${OpenMW_SOURCE_DIR}/files/openmw.cfg - "${OpenMW_BINARY_DIR}/openmw.cfg.install") +if (NOT APPLE) + configure_file(${OpenMW_SOURCE_DIR}/files/openmw.cfg.local + "${OpenMW_BINARY_DIR}/openmw.cfg") + configure_file(${OpenMW_SOURCE_DIR}/files/openmw.cfg + "${OpenMW_BINARY_DIR}/openmw.cfg.install") +else () + configure_file(${OpenMW_SOURCE_DIR}/files/openmw.cfg + "${OpenMW_BINARY_DIR}/openmw.cfg") +endif () configure_file(${OpenMW_SOURCE_DIR}/files/openmw-cs.cfg "${OpenMW_BINARY_DIR}/openmw-cs.cfg") @@ -378,21 +366,17 @@ endif() # CXX Compiler settings if (CMAKE_CXX_COMPILER_ID STREQUAL GNU OR CMAKE_CXX_COMPILER_ID STREQUAL Clang) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wno-unused-parameter -std=c++98 -pedantic -Wno-long-long") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wundef -Wno-unused-parameter -std=c++98 -pedantic -Wno-long-long -Wno-variadic-macros") if (CMAKE_CXX_COMPILER_ID STREQUAL Clang AND NOT APPLE) - execute_process(COMMAND ${CMAKE_C_COMPILER} --version OUTPUT_VARIABLE CLANG_VERSION) - string(REGEX REPLACE ".*version ([0-9\\.]*).*" "\\1" CLANG_VERSION ${CLANG_VERSION}) - if ("${CLANG_VERSION}" VERSION_GREATER 3.6 OR "${CLANG_VERSION}" VERSION_EQUAL 3.6) + if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 3.6 OR CMAKE_CXX_COMPILER_VERSION VERSION_EQUAL 3.6) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-potentially-evaluated-expression") - endif ("${CLANG_VERSION}" VERSION_GREATER 3.6 OR "${CLANG_VERSION}" VERSION_EQUAL 3.6) - endif(CMAKE_CXX_COMPILER_ID STREQUAL Clang AND NOT APPLE) + endif () + endif() - execute_process(COMMAND ${CMAKE_C_COMPILER} -dumpversion - OUTPUT_VARIABLE GCC_VERSION) - if (CMAKE_CXX_COMPILER_ID STREQUAL GNU AND "${GCC_VERSION}" VERSION_GREATER 4.6 OR "${GCC_VERSION}" VERSION_EQUAL 4.6) + if (CMAKE_CXX_COMPILER_ID STREQUAL GNU AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 4.6 OR CMAKE_CXX_COMPILER_VERSION VERSION_EQUAL 4.6) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-but-set-parameter") - endif(CMAKE_CXX_COMPILER_ID STREQUAL GNU AND "${GCC_VERSION}" VERSION_GREATER 4.6 OR "${GCC_VERSION}" VERSION_EQUAL 4.6) + endif() elseif (MSVC) # Enable link-time code generation globally for all linking if (OPENMW_LTO_BUILD) @@ -415,6 +399,9 @@ IF(NOT WIN32 AND NOT APPLE) IF(BUILD_LAUNCHER) INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/openmw-launcher" DESTINATION "${BINDIR}" ) ENDIF(BUILD_LAUNCHER) + IF(BUILD_BROWSER) + INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/openmw-browser" DESTINATION "${BINDIR}" ) + ENDIF(BUILD_BROWSER) IF(BUILD_BSATOOL) INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/bsatool" DESTINATION "${BINDIR}" ) ENDIF(BUILD_BSATOOL) @@ -458,6 +445,11 @@ IF(NOT WIN32 AND NOT APPLE) INSTALL(FILES "${OpenMW_BINARY_DIR}/resources/version" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw") INSTALL(FILES "${OpenMW_BINARY_DIR}/gamecontrollerdb.txt" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw") + INSTALL(FILES "${OpenMW_BINARY_DIR}/tes3mp-client-default" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw") + INSTALL(FILES "${OpenMW_BINARY_DIR}/tes3mp-client.install" DESTINATION "${SYSCONFDIR}" RENAME "tes3mp-client.cfg" COMPONENT "openmw") + INSTALL(FILES "${OpenMW_BINARY_DIR}/tes3mp-server-default" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw-mp") + INSTALL(FILES "${OpenMW_BINARY_DIR}/tes3mp-server.install" DESTINATION "${SYSCONFDIR}" RENAME "tes3mp-server.cfg" COMPONENT "openmw-mp") + IF(BUILD_OPENCS) INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw-cs.cfg" DESTINATION "${SYSCONFDIR}" COMPONENT "opencs") ENDIF(BUILD_OPENCS) @@ -468,8 +460,10 @@ IF(NOT WIN32 AND NOT APPLE) ENDIF(NOT WIN32 AND NOT APPLE) if(WIN32) - FILE(GLOB dll_files "${OpenMW_BINARY_DIR}/Release/*.dll") - INSTALL(FILES ${dll_files} DESTINATION ".") + FILE(GLOB dll_files_debug "${OpenMW_BINARY_DIR}/Debug/*.dll") + FILE(GLOB dll_files_release "${OpenMW_BINARY_DIR}/Release/*.dll") + INSTALL(FILES ${dll_files_debug} DESTINATION "." CONFIGURATIONS Debug) + INSTALL(FILES ${dll_files_release} DESTINATION "." CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel) INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw.cfg.install" DESTINATION "." RENAME "openmw.cfg") INSTALL(FILES "${OpenMW_SOURCE_DIR}/CHANGELOG.md" DESTINATION "." RENAME "CHANGELOG.txt") INSTALL(FILES "${OpenMW_SOURCE_DIR}/README.md" DESTINATION "." RENAME "README.txt") @@ -477,33 +471,25 @@ if(WIN32) "${OpenMW_SOURCE_DIR}/Docs/license/GPL3.txt" "${OpenMW_SOURCE_DIR}/Docs/license/DejaVu Font License.txt" "${OpenMW_BINARY_DIR}/settings-default.cfg" + "${OpenMW_BINARY_DIR}/tes3mp-client-default.cfg" "${OpenMW_BINARY_DIR}/gamecontrollerdb.txt" - "${OpenMW_BINARY_DIR}/Release/openmw.exe" DESTINATION ".") - IF(BUILD_LAUNCHER) - INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/Release/openmw-launcher.exe" DESTINATION ".") - ENDIF(BUILD_LAUNCHER) - IF(BUILD_MWINIIMPORTER) - INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/Release/openmw-iniimporter.exe" DESTINATION ".") - ENDIF(BUILD_MWINIIMPORTER) - IF(BUILD_ESSIMPORTER) - INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/Release/openmw-essimporter.exe" DESTINATION ".") - ENDIF(BUILD_ESSIMPORTER) - IF(BUILD_OPENCS) - INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/Release/openmw-cs.exe" DESTINATION ".") - INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw-cs.cfg" DESTINATION ".") - ENDIF(BUILD_OPENCS) - IF(BUILD_WIZARD) - INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/Release/openmw-wizard.exe" DESTINATION ".") - ENDIF(BUILD_WIZARD) if(BUILD_MYGUI_PLUGIN) - INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/Release/Plugin_MyGUI_OpenMW_Resources.dll" DESTINATION ".") + INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/Debug/Plugin_MyGUI_OpenMW_Resources.dll" DESTINATION "." CONFIGURATIONS Debug) + INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/Release/Plugin_MyGUI_OpenMW_Resources.dll" DESTINATION "." CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel) ENDIF(BUILD_MYGUI_PLUGIN) + IF(DESIRED_QT_VERSION MATCHES 5) + INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/Debug/platforms" DESTINATION "." CONFIGURATIONS Debug) + INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/Release/platforms" DESTINATION "." CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel) + ENDIF() + INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/resources" DESTINATION ".") - FILE(GLOB plugin_dir "${OpenMW_BINARY_DIR}/Release/osgPlugins-*") - INSTALL(DIRECTORY ${plugin_dir} DESTINATION ".") + FILE(GLOB plugin_dir_debug "${OpenMW_BINARY_DIR}/Debug/osgPlugins-*") + FILE(GLOB plugin_dir_release "${OpenMW_BINARY_DIR}/Release/osgPlugins-*") + INSTALL(DIRECTORY ${plugin_dir_debug} DESTINATION "." CONFIGURATIONS Debug) + INSTALL(DIRECTORY ${plugin_dir_release} DESTINATION "." CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel) SET(CPACK_GENERATOR "NSIS") SET(CPACK_PACKAGE_NAME "OpenMW") @@ -516,6 +502,9 @@ if(WIN32) IF(BUILD_LAUNCHER) SET(CPACK_PACKAGE_EXECUTABLES "${CPACK_PACKAGE_EXECUTABLES};openmw-launcher;OpenMW Launcher") ENDIF(BUILD_LAUNCHER) + IF(BUILD_BROWSER) + SET(CPACK_PACKAGE_EXECUTABLES "${CPACK_PACKAGE_EXECUTABLES};tes3mp-browser;tes3mp Launcher") + ENDIF(BUILD_BROWSER) IF(BUILD_OPENCS) SET(CPACK_PACKAGE_EXECUTABLES "${CPACK_PACKAGE_EXECUTABLES};openmw-cs;OpenMW Construction Set") ENDIF(BUILD_OPENCS) @@ -534,8 +523,8 @@ if(WIN32) SET(CPACK_NSIS_HELP_LINK "http:\\\\\\\\www.openmw.org") SET(CPACK_NSIS_URL_INFO_ABOUT "http:\\\\\\\\www.openmw.org") SET(CPACK_NSIS_INSTALLED_ICON_NAME "openmw-launcher.exe") - SET(CPACK_NSIS_MUI_ICON "${OpenMW_SOURCE_DIR}/files/windows/openmw.ico") - SET(CPACK_NSIS_MUI_UNIICON "${OpenMW_SOURCE_DIR}/files/windows/openmw.ico") + SET(CPACK_NSIS_MUI_ICON "${OpenMW_SOURCE_DIR}/files/tes3mp/tes3mp.ico") + SET(CPACK_NSIS_MUI_UNIICON "${OpenMW_SOURCE_DIR}/files/tes3mp/tes3mp.ico") SET(CPACK_PACKAGE_ICON "${OpenMW_SOURCE_DIR}\\\\files\\\\openmw.bmp") SET(VCREDIST32 "${OpenMW_BINARY_DIR}/vcredist_x86.exe") @@ -565,8 +554,13 @@ if(WIN32) endif(WIN32) # Extern +IF(BUILD_OPENMW OR BUILD_OPENCS) add_subdirectory (extern/osg-ffmpeg-videoplayer) add_subdirectory (extern/oics) +if (BUILD_OPENCS) + add_subdirectory (extern/osgQt) +endif() +ENDIF(BUILD_OPENMW OR BUILD_OPENCS) # Components add_subdirectory (components) @@ -577,6 +571,10 @@ add_subdirectory (components) #endif() # Apps and tools +if (BUILD_OPENMW_MP) + add_subdirectory( apps/openmw-mp ) +endif() + if (BUILD_OPENMW) add_subdirectory( apps/openmw ) endif() @@ -593,6 +591,10 @@ if (BUILD_LAUNCHER) add_subdirectory( apps/launcher ) endif() +if (BUILD_BROWSER) + add_subdirectory( apps/browser ) +endif() + if (BUILD_MWINIIMPORTER) add_subdirectory( apps/mwiniimporter ) endif() @@ -631,20 +633,20 @@ if (WIN32) endforeach( OUTPUTCONFIG ) if (USE_DEBUG_CONSOLE AND BUILD_OPENMW) - set_target_properties(openmw PROPERTIES LINK_FLAGS_DEBUG "/SUBSYSTEM:CONSOLE") - set_target_properties(openmw PROPERTIES LINK_FLAGS_RELWITHDEBINFO "/SUBSYSTEM:CONSOLE") - set_target_properties(openmw PROPERTIES COMPILE_DEFINITIONS_DEBUG "_CONSOLE") + set_target_properties(tes3mp PROPERTIES LINK_FLAGS_DEBUG "/SUBSYSTEM:CONSOLE") + set_target_properties(tes3mp PROPERTIES LINK_FLAGS_RELWITHDEBINFO "/SUBSYSTEM:CONSOLE") + set_target_properties(tes3mp PROPERTIES COMPILE_DEFINITIONS_DEBUG "_CONSOLE") elseif (BUILD_OPENMW) # Turn off debug console, debug output will be written to visual studio output instead - set_target_properties(openmw PROPERTIES LINK_FLAGS_DEBUG "/SUBSYSTEM:WINDOWS") - set_target_properties(openmw PROPERTIES LINK_FLAGS_RELWITHDEBINFO "/SUBSYSTEM:WINDOWS") + set_target_properties(tes3mp PROPERTIES LINK_FLAGS_DEBUG "/SUBSYSTEM:WINDOWS") + set_target_properties(tes3mp PROPERTIES LINK_FLAGS_RELWITHDEBINFO "/SUBSYSTEM:WINDOWS") endif() if (BUILD_OPENMW) # Release builds use the debug console - set_target_properties(openmw PROPERTIES LINK_FLAGS_RELEASE "/SUBSYSTEM:CONSOLE") - set_target_properties(openmw PROPERTIES COMPILE_DEFINITIONS_RELEASE "_CONSOLE") - set_target_properties(openmw PROPERTIES LINK_FLAGS_MINSIZEREL "/SUBSYSTEM:CONSOLE") + set_target_properties(tes3mp PROPERTIES LINK_FLAGS_RELEASE "/SUBSYSTEM:CONSOLE") + set_target_properties(tes3mp PROPERTIES COMPILE_DEFINITIONS_RELEASE "_CONSOLE") + set_target_properties(tes3mp PROPERTIES LINK_FLAGS_MINSIZEREL "/SUBSYSTEM:CONSOLE") endif() # Play a bit with the warning levels @@ -667,9 +669,15 @@ if (WIN32) 4987 # nonstandard extension used (triggered by setjmp.h) 4996 # Function was declared deprecated + # caused by OSG + 4589 # Constructor of abstract class 'osg::Operation' ignores initializer for virtual base class 'osg::Referenced' (False warning) + # caused by boost 4191 # 'type cast' : unsafe conversion (1.56, thread_primitives.hpp, normally off) + # caused by MyGUI + 4275 # non dll-interface class 'std::exception' used as base for dll-interface class 'MyGUI::Exception' + # OpenMW specific warnings 4099 # Type mismatch, declared class or struct is defined with other type 4100 # Unreferenced formal parameter (-Wunused-parameter) @@ -683,19 +691,25 @@ if (WIN32) 4309 # Variable overflow, trying to store 128 in a signed char for example 4351 # New behavior: elements of array 'array' will be default initialized (desired behavior) 4355 # Using 'this' in member initialization list + 4464 # relative include path contains '..' 4505 # Unreferenced local function has been removed 4701 # Potentially uninitialized local variable used 4702 # Unreachable code + 4714 # function 'QString QString::trimmed(void) &&' marked as __forceinline not inlined 4800 # Boolean optimization warning, e.g. myBool = (myInt != 0) instead of myBool = myInt ) + if (MSVC_VERSION GREATER 1800) + set(WARNINGS_DISABLE ${WARNINGS_DISABLE} 5026 5027 + 5031 # #pragma warning(pop): likely mismatch, popping warning state pushed in different file (config_begin.hpp, config_end.hpp) + ) + endif() + foreach(d ${WARNINGS_DISABLE}) set(WARNINGS "${WARNINGS} /wd${d}") endforeach(d) set_target_properties(components PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") - # oics uses tinyxml, which has an initialized but unused variable - set_target_properties(oics PROPERTIES COMPILE_FLAGS "${WARNINGS} /wd4189 ${MT_BUILD}") set_target_properties(osg-ffmpeg-videoplayer PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") if (BUILD_BSATOOL) @@ -714,6 +728,10 @@ if (WIN32) set_target_properties(openmw-launcher PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") endif() + if (BUILD_BROWSER) + set_target_properties(tes3mp-browser PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") + endif() + if (BUILD_MWINIIMPORTER) set_target_properties(openmw-iniimporter PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") endif() @@ -723,7 +741,12 @@ if (WIN32) endif() if (BUILD_OPENMW) - set_target_properties(openmw PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") + # Very specific issue this, only needed on 32-bit VS2015 during unity builds. + if (MSVC_VERSION GREATER 1800 AND CMAKE_SIZEOF_VOID_P EQUAL 4 AND OPENMW_UNITY_BUILD) + set_target_properties(tes3mp PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD} /bigobj") + else() + set_target_properties(tes3mp PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") + endif() endif() if (BUILD_WIZARD) @@ -732,8 +755,8 @@ if (WIN32) endif(MSVC) # TODO: At some point release builds should not use the console but rather write to a log file - #set_target_properties(openmw PROPERTIES LINK_FLAGS_RELEASE "/SUBSYSTEM:WINDOWS") - #set_target_properties(openmw PROPERTIES LINK_FLAGS_MINSIZEREL "/SUBSYSTEM:WINDOWS") + #set_target_properties(tes3mp PROPERTIES LINK_FLAGS_RELEASE "/SUBSYSTEM:WINDOWS") + #set_target_properties(tes3mp PROPERTIES LINK_FLAGS_MINSIZEREL "/SUBSYSTEM:WINDOWS") endif() # Apple bundling @@ -750,14 +773,7 @@ if (APPLE) configure_file("${QT_COCOA_PLUGIN_PATH}" "${OPENCS_BUNDLE_NAME}/Contents/MacOS/${QT_COCOA_PLUGIN_GROUP}/${QT_COCOA_PLUGIN_NAME}" COPYONLY) endif () - set(INSTALL_SUBDIR OpenMW) - - install(DIRECTORY "${APP_BUNDLE_DIR}" USE_SOURCE_PERMISSIONS DESTINATION "${INSTALL_SUBDIR}" COMPONENT Runtime) - install(DIRECTORY "${OpenMW_BINARY_DIR}/resources" DESTINATION "${INSTALL_SUBDIR}" COMPONENT Runtime) - install(FILES "${OpenMW_BINARY_DIR}/openmw.cfg.install" RENAME "openmw.cfg" DESTINATION "${INSTALL_SUBDIR}" COMPONENT Runtime) - install(FILES "${OpenMW_BINARY_DIR}/settings-default.cfg" DESTINATION "${INSTALL_SUBDIR}" COMPONENT Runtime) - install(FILES "${OpenMW_BINARY_DIR}/gamecontrollerdb.txt" DESTINATION "${INSTALL_SUBDIR}" COMPONENT Runtime) - install(FILES "${OpenMW_BINARY_DIR}/openmw-cs.cfg" DESTINATION "${INSTALL_SUBDIR}" COMPONENT Runtime) + install(DIRECTORY "${APP_BUNDLE_DIR}" USE_SOURCE_PERMISSIONS DESTINATION "." COMPONENT Runtime) set(CPACK_GENERATOR "DragNDrop") set(CPACK_PACKAGE_VERSION ${OPENMW_VERSION}) @@ -765,8 +781,8 @@ if (APPLE) set(CPACK_PACKAGE_VERSION_MINOR ${OPENMW_VERSION_MINOR}) set(CPACK_PACKAGE_VERSION_PATCH ${OPENMW_VERSION_RELEASE}) - set(INSTALLED_OPENMW_APP "\${CMAKE_INSTALL_PREFIX}/${INSTALL_SUBDIR}/${APP_BUNDLE_NAME}") - set(INSTALLED_OPENCS_APP "\${CMAKE_INSTALL_PREFIX}/${INSTALL_SUBDIR}/${OPENCS_BUNDLE_NAME}") + set(INSTALLED_OPENMW_APP "\${CMAKE_INSTALL_PREFIX}/${APP_BUNDLE_NAME}") + set(INSTALLED_OPENCS_APP "\${CMAKE_INSTALL_PREFIX}/${OPENCS_BUNDLE_NAME}") install(CODE " set(BU_CHMOD_BUNDLE_ITEMS ON) @@ -776,19 +792,16 @@ if (APPLE) " COMPONENT Runtime) set(ABSOLUTE_PLUGINS "") - set(USED_OSG_PLUGINS - osgdb_dds - osgdb_jpeg - osgdb_png - osgdb_tga - ) foreach (PLUGIN_NAME ${USED_OSG_PLUGINS}) - set(PLUGIN_ABS "${OSG_PLUGIN_LIB_SEARCH_PATH}/${PLUGIN_NAME}.so") + set(PLUGIN_ABS "${OSGPlugins_LIB_DIR}/${PLUGIN_NAME}.so") set(ABSOLUTE_PLUGINS ${PLUGIN_ABS} ${ABSOLUTE_PLUGINS}) endforeach () - get_filename_component(OSG_PLUGIN_PREFIX_DIR "${OSG_PLUGIN_LIB_SEARCH_PATH}" NAME) + get_filename_component(OSG_PLUGIN_PREFIX_DIR "${OSGPlugins_LIB_DIR}" NAME) + if (NOT OSG_PLUGIN_PREFIX_DIR) + message(FATAL_ERROR "Can't get directory name for OSG plugins from '${OSGPlugins_LIB_DIR}'") + endif() # installs used plugins in bundle at given path (bundle_path must be relative to ${CMAKE_INSTALL_PREFIX}) # and returns list of install paths for all installed plugins @@ -813,8 +826,8 @@ if (APPLE) set(${plugins_var} ${PLUGINS} PARENT_SCOPE) endfunction (install_plugins_for_bundle) - install_plugins_for_bundle("${INSTALL_SUBDIR}/${APP_BUNDLE_NAME}" PLUGINS) - install_plugins_for_bundle("${INSTALL_SUBDIR}/${OPENCS_BUNDLE_NAME}" OPENCS_PLUGINS) + install_plugins_for_bundle("${APP_BUNDLE_NAME}" PLUGINS) + install_plugins_for_bundle("${OPENCS_BUNDLE_NAME}" OPENCS_PLUGINS) set(PLUGINS ${PLUGINS} "${INSTALLED_OPENMW_APP}/Contents/MacOS/${QT_COCOA_PLUGIN_GROUP}/${QT_COCOA_PLUGIN_NAME}") set(OPENCS_PLUGINS ${OPENCS_PLUGINS} "${INSTALLED_OPENCS_APP}/Contents/MacOS/${QT_COCOA_PLUGIN_GROUP}/${QT_COCOA_PLUGIN_NAME}") @@ -856,3 +869,4 @@ if (DOXYGEN_FOUND) WORKING_DIRECTORY ${OpenMW_BINARY_DIR} COMMENT "Generating documentation for the github-pages at ${DOXYGEN_PAGES_OUTPUT_DIR}" VERBATIM) endif () + diff --git a/README.md b/README.md index 9de814fd0..4def6168e 100644 --- a/README.md +++ b/README.md @@ -1,107 +1,41 @@ -OpenMW +TES3MP ====== -[![Build Status](https://api.travis-ci.org/OpenMW/openmw.svg)](https://travis-ci.org/OpenMW/openmw) [![Build status](https://ci.appveyor.com/api/projects/status/e6bqw8oouy8ufd46?svg=true)](https://ci.appveyor.com/project/scrawl/openmw) [![Coverity Scan Build Status](https://scan.coverity.com/projects/3740/badge.svg)](https://scan.coverity.com/projects/3740) +[![Build Status](https://travis-ci.org/TES3MP/openmw-tes3mp.svg?branch=master)](https://travis-ci.org/TES3MP/openmw-tes3mp) -OpenMW is a recreation of the engine for the popular role-playing game Morrowind by Bethesda Softworks. You need to own and install the original game for OpenMW to work. +TES3MP is a project aiming to add multiplayer functionality to [OpenMW](https://github.com/OpenMW/openmw), a free and open source recreation of the popular Bethesda Softworks game "The Elder Scrolls III: Morrowind". -OpenMW also comes with OpenMW-CS, a replacement for Morrowind's TES Construction Set. - -* Version: 0.38.0 -* License: GPL (see docs/license/GPL3.txt for more information) -* Website: http://www.openmw.org -* IRC: #openmw on irc.freenode.net +* Version: 0.5.1 +* License: GPLv3 (see docs/license/GPL3.txt for more information) +* Website: https://steamcommunity.com/groups/mwmulti Font Licenses: * DejaVuLGCSansMono.ttf: custom (see docs/license/DejaVu Font License.txt for more information) -Current Status +Project Status -------------- -The main quests in Morrowind, Tribunal and Bloodmoon are all completable. Some issues with side quests are to be expected (but rare). Check the [bug tracker](https://bugs.openmw.org/versions/21) for a list of issues we need to resolve before the "1.0" release. Even before the "1.0" release however, OpenMW boasts some new [features](https://wiki.openmw.org/index.php?title=Features), such as improved graphics and user interfaces. - -Pre-existing modifications created for the original Morrowind engine can be hit-and-miss. The OpenMW script compiler performs more thorough error-checking than Morrowind does, meaning that a mod created for Morrowind may not necessarily run in OpenMW. Some mods also rely on quirky behaviour or engine bugs in order to work. We are considering such compatibility issues on a case-by-case basis - in some cases adding a workaround to OpenMW may be feasible, in other cases fixing the mod will be the only option. If you know of any mods that work or don't work, feel free to add them to the [Mod status](https://wiki.openmw.org/index.php?title=Mod_status) wiki page. +[Version changelog](https://github.com/TES3MP/openmw-tes3mp/blob/master/tes3mp-changelog.md) -Getting Started ---------------- +Our project is not yet in a playable state, though we are getting close. At the moment we have synchronization of character appearance, character skills, stats, attributes and death, movement in interiors and exteriors, melee and ranged combat, spell casting, picking up and dropping items in the world, using doors and levers, and adding and removing items from containers, as well as [serverside Lua scripts](https://github.com/TES3MP/PluginExamples) used to save and load the state of most of the aforementioned. -* [Official forums](https://forum.openmw.org/) -* [Installation instructions](https://wiki.openmw.org/index.php?title=Installation_Instructions) -* [Build from source](https://wiki.openmw.org/index.php?title=Development_Environment_Setup) -* [Testing the game](https://wiki.openmw.org/index.php?title=Testing) -* [How to contribute](https://wiki.openmw.org/index.php?title=Contribution_Wanted) -* [Report a bug](http://bugs.openmw.org/projects/openmw) - read the [guidelines](https://wiki.openmw.org/index.php?title=Bug_Reporting_Guidelines) before submitting your first bug! -* [Known issues](http://bugs.openmw.org/projects/openmw/issues?utf8=%E2%9C%93&set_filter=1&f%5B%5D=status_id&op%5Bstatus_id%5D=%3D&v%5Bstatus_id%5D%5B%5D=7&f%5B%5D=tracker_id&op%5Btracker_id%5D=%3D&v%5Btracker_id%5D%5B%5D=1&f%5B%5D=&c%5B%5D=project&c%5B%5D=tracker&c%5B%5D=status&c%5B%5D=priority&c%5B%5D=subject&c%5B%5D=assigned_to&c%5B%5D=updated_on&group_by=tracker) +Contributing +-------------- -The data path -------------- +Development has been relatively fast, but any contribution regarding [code](https://github.com/TES3MP/openmw-tes3mp/blob/master/CONTRIBUTING.md), documentation, bug hunting or video showcases is greatly appreciated. -The data path tells OpenMW where to find your Morrowind files. If you run the launcher, OpenMW should be able to pick up the location of these files on its own, if both Morrowind and OpenMW are installed properly (installing Morrowind under WINE is considered a proper install). +Test sessions are often advertised in [our Steam group](https://steamcommunity.com/groups/mwmulti) or [our Discord server](https://discord.gg/H8zhhuk). -Command line options --------------------- +Feel free to contact the [team members](https://github.com/TES3MP/openmw-tes3mp/blob/master/tes3mp-credits.md) for any questions you might have. - Syntax: openmw - Allowed options: - --help print help message - --version print version information and quit - --data arg (=data) set data directories (later directories - have higher priority) - --data-local arg set local data directory (highest - priority) - --fallback-archive arg (=fallback-archive) - set fallback BSA archives (later - archives have higher priority) - --resources arg (=resources) set resources directory - --start arg set initial cell - --content arg content file(s): esm/esp, or - omwgame/omwaddon - --no-sound [=arg(=1)] (=0) disable all sounds - --script-verbose [=arg(=1)] (=0) verbose script output - --script-all [=arg(=1)] (=0) compile all scripts (excluding dialogue - scripts) at startup - --script-all-dialogue [=arg(=1)] (=0) compile all dialogue scripts at startup - --script-console [=arg(=1)] (=0) enable console-only script - functionality - --script-run arg select a file containing a list of - console commands that is executed on - startup - --script-warn [=arg(=1)] (=1) handling of warnings when compiling - scripts - 0 - ignore warning - 1 - show warning but consider script as - correctly compiled anyway - 2 - treat warnings as errors - --script-blacklist arg ignore the specified script (if the use - of the blacklist is enabled) - --script-blacklist-use [=arg(=1)] (=1) - enable script blacklisting - --load-savegame arg load a save game file on game startup - (specify an absolute filename or a - filename relative to the current - working directory) - --skip-menu [=arg(=1)] (=0) skip main menu on game startup - --new-game [=arg(=1)] (=0) run new game sequence (ignored if - skip-menu=0) - --fs-strict [=arg(=1)] (=0) strict file system handling (no case - folding) - --encoding arg (=win1252) Character encoding used in OpenMW game - messages: +Getting Started +--------------- - win1250 - Central and Eastern European - such as Polish, Czech, Slovak, - Hungarian, Slovene, Bosnian, Croatian, - Serbian (Latin script), Romanian and - Albanian languages +* [Community forums](https://steamcommunity.com/groups/mwmulti) +* [Installation and build instructions](https://github.com/TES3MP/openmw-tes3mp/wiki/Installation-and-build-instructions) +* [Known issues and bug reports](https://github.com/TES3MP/openmw-tes3mp/issues) - win1251 - Cyrillic alphabet such as - Russian, Bulgarian, Serbian Cyrillic - and other languages +Donations +--------------- - win1252 - Western European (Latin) - alphabet, used by default - --fallback arg fallback values - --no-grab Don't grab mouse cursor - --export-fonts [=arg(=1)] (=0) Export Morrowind .fnt fonts to PNG - image and XML file in current directory - --activate-dist arg (=-1) activation distance override +You can benefit the project by supporting OpenMW and/or by [becoming Koncord's patron](https://www.patreon.com/Koncord). diff --git a/apps/browser/CMakeLists.txt b/apps/browser/CMakeLists.txt new file mode 100644 index 000000000..95f09bfec --- /dev/null +++ b/apps/browser/CMakeLists.txt @@ -0,0 +1,93 @@ + +set (CMAKE_CXX_STANDARD 11) + +set(BROWSER_UI + ${CMAKE_SOURCE_DIR}/files/tes3mp/ui/Main.ui + ${CMAKE_SOURCE_DIR}/files/tes3mp/ui/ServerInfo.ui + ) +set(BROWSER + main.cpp + MainWindow.cpp + ServerModel.cpp + NetController.cpp + ServerInfoDialog.cpp + MySortFilterProxyModel.cpp + netutils/HTTPNetwork.cpp + netutils/Utils.cpp + ${CMAKE_SOURCE_DIR}/files/tes3mp/browser.rc + ) + +set(BROWSER_HEADER_MOC + MainWindow.hpp + ServerModel.hpp + ServerInfoDialog.hpp + MySortFilterProxyModel.hpp + ) + +set(BROWSER_HEADER + ${BROWSER_HEADER_MOC} + NetController.hpp + netutils/HTTPNetwork.hpp + netutils/Utils.hpp + ) + +source_group(browser FILES ${BROWSER} ${BROWSER_HEADER}) + +set(QT_USE_QTGUI 1) + +# Set some platform specific settings +if(WIN32) + set(GUI_TYPE WIN32) + set(QT_USE_QTMAIN TRUE) +endif(WIN32) + +if (DESIRED_QT_VERSION MATCHES 4) + message(SEND_ERROR "QT4 is not supported.") + #include(${QT_USE_FILE}) + #QT4_ADD_RESOURCES(RCC_SRCS ${CMAKE_SOURCE_DIR}/files/launcher/launcher.qrc) + #QT4_WRAP_CPP(MOC_SRCS ${BROWSER_HEADER_MOC}) + #QT4_WRAP_UI(UI_HDRS ${BROWSER_UI}) +else() + QT5_ADD_RESOURCES(RCC_SRCS ${CMAKE_SOURCE_DIR}/files/launcher/launcher.qrc) + QT5_WRAP_CPP(MOC_SRCS ${BROWSER_HEADER_MOC}) + QT5_WRAP_UI(UI_HDRS ${BROWSER_UI}) +endif() + +include_directories(${CMAKE_CURRENT_BINARY_DIR}) +if(NOT WIN32) + include_directories(${LIBUNSHIELD_INCLUDE_DIR}) +endif(NOT WIN32) + +# Main executable +add_executable(tes3mp-browser + ${GUI_TYPE} + ${BROWSER} + ${BROWSER_HEADER} + ${RCC_SRCS} + ${MOC_SRCS} + ${UI_HDRS} + ) + +if (WIN32) + INSTALL(TARGETS tes3mp-browser RUNTIME DESTINATION ".") +endif (WIN32) + +target_link_libraries(tes3mp-browser + ${SDL2_LIBRARY_ONLY} + ${RakNet_LIBRARY} + components + ) + +if (DESIRED_QT_VERSION MATCHES 4) +# target_link_libraries(tes3mp-browser ${QT_QTGUI_LIBRARY} ${QT_QTCORE_LIBRARY}) +# if(WIN32) +# target_link_libraries(tes3mp-browser ${QT_QTMAIN_LIBRARY}) +# endif(WIN32) +else() + qt5_use_modules(tes3mp-browser Widgets Core) +endif() + +if (BUILD_WITH_CODE_COVERAGE) + add_definitions (--coverage) + target_link_libraries(tes3mp-browser gcov) +endif() \ No newline at end of file diff --git a/apps/browser/MainWindow.cpp b/apps/browser/MainWindow.cpp new file mode 100644 index 000000000..0d896986b --- /dev/null +++ b/apps/browser/MainWindow.cpp @@ -0,0 +1,228 @@ +// +// Created by koncord on 06.01.17. +// + +#include "MainWindow.hpp" +#include "NetController.hpp" +#include "ServerInfoDialog.hpp" +#include "components/files/configurationmanager.hpp" +#include +#include +#include +#include +#include +#include + +using namespace Process; + +MainWindow::MainWindow(QWidget *parent) +{ + setupUi(this); + + mGameInvoker = new ProcessInvoker(); + + browser = new ServerModel; + favorites = new ServerModel; + proxyModel = new MySortFilterProxyModel(this); + proxyModel->setSourceModel(browser); + tblServerBrowser->setModel(proxyModel); + tblFavorites->setModel(proxyModel); + + tblServerBrowser->hideColumn(ServerData::ADDR); + tblFavorites->hideColumn(ServerData::ADDR); + + connect(tabWidget, SIGNAL(currentChanged(int)), this, SLOT(tabSwitched(int))); + connect(actionAdd, SIGNAL(triggered(bool)), this, SLOT(addServer())); + connect(actionAdd_by_IP, SIGNAL(triggered(bool)), this, SLOT(addServerByIP())); + connect(actionDelete, SIGNAL(triggered(bool)), this, SLOT(deleteServer())); + connect(actionRefresh, SIGNAL(triggered(bool)), this, SLOT(refresh())); + connect(actionPlay, SIGNAL(triggered(bool)), this, SLOT(play())); + connect(tblServerBrowser, SIGNAL(clicked(QModelIndex)), this, SLOT(serverSelected())); + connect(tblFavorites, SIGNAL(clicked(QModelIndex)), this, SLOT(serverSelected())); + connect(tblFavorites, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(play())); + connect(tblServerBrowser, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(play())); + connect(cBoxNotFull, SIGNAL(toggled(bool)), this, SLOT(notFullSwitch(bool))); + connect(cBoxWithPlayers, SIGNAL(toggled(bool)), this, SLOT(havePlayersSwitch(bool))); + connect(comboLatency, SIGNAL(currentIndexChanged(int)), this, SLOT(maxLatencyChanged(int))); + connect(leGamemode, SIGNAL(textChanged(const QString &)), this, SLOT(gamemodeChanged(const QString &))); + loadFavorites(); +} + +MainWindow::~MainWindow() +{ + delete mGameInvoker; +} + +void MainWindow::addServerAndUpdate(QString addr) +{ + favorites->insertRow(0); + QModelIndex mi = favorites->index(0, ServerData::ADDR); + favorites->setData(mi, addr, Qt::EditRole); + NetController::get()->updateInfo(favorites, mi); +} + +void MainWindow::addServer() +{ + int id = tblServerBrowser->selectionModel()->currentIndex().row(); + + if(id >= 0) + { + int sourceId = proxyModel->mapToSource(proxyModel->index(id, ServerData::ADDR)).row(); + favorites->myData.push_back(browser->myData[sourceId]); + } +} + +void MainWindow::addServerByIP() +{ + bool ok; + QString text = QInputDialog::getText(this, tr("Add Server by address"), tr("Address:"), QLineEdit::Normal, "", &ok); + if(ok && !text.isEmpty()) + addServerAndUpdate(text); +} + +void MainWindow::deleteServer() +{ + if(tabWidget->currentIndex() != 1) + return; + int id = tblFavorites->selectionModel()->currentIndex().row(); + if(id >= 0) + { + int sourceId = proxyModel->mapToSource(proxyModel->index(id, ServerData::ADDR)).row(); + favorites->removeRow(sourceId); + if(favorites->myData.isEmpty()) + { + actionPlay->setEnabled(false); + actionDelete->setEnabled(false); + } + } +} + +bool MainWindow::refresh() +{ + return NetController::get()->updateInfo(proxyModel->sourceModel()); + /*tblServerBrowser->resizeColumnToContents(ServerData::HOSTNAME); + tblServerBrowser->resizeColumnToContents(ServerData::MODNAME); + tblFavorites->resizeColumnToContents(ServerData::HOSTNAME); + tblFavorites->resizeColumnToContents(ServerData::MODNAME);*/ +} + +void MainWindow::play() +{ + QTableView *curTable = tabWidget->currentIndex() ? tblFavorites : tblServerBrowser; + int id = curTable->selectionModel()->currentIndex().row(); + if(id < 0) + return; + + ServerInfoDialog infoDialog(this); + ServerModel *sm = ((ServerModel*)proxyModel->sourceModel()); + + int sourceId = proxyModel->mapToSource(proxyModel->index(id, ServerData::ADDR)).row(); + NetController::get()->selectServer(&sm->myData[sourceId]); + infoDialog.refresh(); + if(!infoDialog.exec()) + return; + + QStringList arguments; + arguments.append(QLatin1String("--connect=") + sm->myData[sourceId].addr.toLatin1()); + + if(sm->myData[sourceId].needPassw) + { + bool ok; + QString passw = QInputDialog::getText(this, "Connecting to: " + sm->myData[sourceId].addr, "Password: ", QLineEdit::Password, "", &ok); + if(!ok) + return; + arguments.append(QLatin1String("--password=") + passw.toLatin1()); + } + + if (mGameInvoker->startProcess(QLatin1String("tes3mp"), arguments, true)) + return qApp->quit(); +} + +void MainWindow::tabSwitched(int index) +{ + if(index == 0) + { + proxyModel->setSourceModel(browser); + actionDelete->setEnabled(false); + } + else + { + proxyModel->setSourceModel(favorites); + } + actionPlay->setEnabled(false); + actionAdd->setEnabled(false); +} + +void MainWindow::serverSelected() +{ + actionPlay->setEnabled(true); + if(tabWidget->currentIndex() == 0) + actionAdd->setEnabled(true); + if(tabWidget->currentIndex() == 1) + actionDelete->setEnabled(true); +} + +void MainWindow::closeEvent(QCloseEvent *event) +{ + Files::ConfigurationManager cfgMgr; + QString cfgPath = QString::fromStdString((cfgMgr.getUserConfigPath() / "favorites.dat").string()); + + QJsonArray saveData; + for(auto server : favorites->myData) + saveData.push_back(server.addr); + + QFile file(cfgPath); + + if(!file.open(QIODevice::WriteOnly)) + { + qDebug() << "Cannot save " << cfgPath; + return; + } + + file.write(QJsonDocument(saveData).toJson()); + file.close(); +} + + +void MainWindow::loadFavorites() +{ + Files::ConfigurationManager cfgMgr; + QString cfgPath = QString::fromStdString((cfgMgr.getUserConfigPath() / "favorites.dat").string()); + + QFile file(cfgPath); + if(!file.open(QIODevice::ReadOnly)) + { + qDebug() << "Cannot open " << cfgPath; + return; + } + + QJsonDocument jsonDoc(QJsonDocument::fromJson(file.readAll())); + + for(auto server : jsonDoc.array()) + addServerAndUpdate(server.toString()); + + file.close(); +} + +void MainWindow::notFullSwitch(bool state) +{ + proxyModel->filterFullServer(state); +} + +void MainWindow::havePlayersSwitch(bool state) +{ + proxyModel->filterEmptyServers(state); +} + +void MainWindow::maxLatencyChanged(int index) +{ + int maxLatency = index * 50; + proxyModel->pingLessThan(maxLatency); + +} + +void MainWindow::gamemodeChanged(const QString &text) +{ + proxyModel->setFilterFixedString(text); + proxyModel->setFilterKeyColumn(ServerData::MODNAME); +} diff --git a/apps/browser/MainWindow.hpp b/apps/browser/MainWindow.hpp new file mode 100644 index 000000000..d5d274cf9 --- /dev/null +++ b/apps/browser/MainWindow.hpp @@ -0,0 +1,44 @@ +// +// Created by koncord on 06.01.17. +// + +#ifndef NEWLAUNCHER_MAIN_HPP +#define NEWLAUNCHER_MAIN_HPP + + +#include "ui_Main.h" +#include "ServerModel.hpp" +#include "MySortFilterProxyModel.hpp" +#include + +class MainWindow : public QMainWindow, private Ui::MainWindow +{ + Q_OBJECT +public: + explicit MainWindow(QWidget *parent = 0); + virtual ~MainWindow(); +protected: + void closeEvent(QCloseEvent * event) Q_DECL_OVERRIDE; + void addServerAndUpdate(QString addr); +public slots: + bool refresh(); +protected slots: + void tabSwitched(int index); + void addServer(); + void addServerByIP(); + void deleteServer(); + void play(); + void serverSelected(); + void notFullSwitch(bool state); + void havePlayersSwitch(bool state); + void maxLatencyChanged(int index); + void gamemodeChanged(const QString &text); +private: + Process::ProcessInvoker *mGameInvoker; + ServerModel *browser, *favorites; + MySortFilterProxyModel *proxyModel; + void loadFavorites(); +}; + + +#endif //NEWLAUNCHER_MAIN_HPP diff --git a/apps/browser/MySortFilterProxyModel.cpp b/apps/browser/MySortFilterProxyModel.cpp new file mode 100644 index 000000000..c5d43685e --- /dev/null +++ b/apps/browser/MySortFilterProxyModel.cpp @@ -0,0 +1,54 @@ +// +// Created by koncord on 30.01.17. +// + +#include "MySortFilterProxyModel.hpp" +#include "ServerModel.hpp" + +#include + +bool MySortFilterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const +{ + + QModelIndex pingIndex = sourceModel()->index(sourceRow, ServerData::PING, sourceParent); + QModelIndex plIndex = sourceModel()->index(sourceRow, ServerData::PLAYERS, sourceParent); + QModelIndex maxPlIndex = sourceModel()->index(sourceRow, ServerData::MAX_PLAYERS, sourceParent); + + int ping = sourceModel()->data(pingIndex).toInt(); + int players = sourceModel()->data(plIndex).toInt(); + int maxPlayers = sourceModel()->data(maxPlIndex).toInt(); + + if(maxPing > 0 && (ping == -1 || ping > maxPing)) + return false; + if(filterEmpty && players == 0) + return false; + if(filterFull && players >= maxPlayers) + return false; + + return QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent); +} + +MySortFilterProxyModel::MySortFilterProxyModel(QObject *parent) : QSortFilterProxyModel(parent) +{ + filterEmpty = false; + filterFull = false; + maxPing = 0; +} + +void MySortFilterProxyModel::filterEmptyServers(bool state) +{ + filterEmpty = state; + invalidateFilter(); +} + +void MySortFilterProxyModel::filterFullServer(bool state) +{ + filterFull = state; + invalidateFilter(); +} + +void MySortFilterProxyModel::pingLessThan(int maxPing) +{ + this->maxPing = maxPing; + invalidateFilter(); +} diff --git a/apps/browser/MySortFilterProxyModel.hpp b/apps/browser/MySortFilterProxyModel.hpp new file mode 100644 index 000000000..c53de3a90 --- /dev/null +++ b/apps/browser/MySortFilterProxyModel.hpp @@ -0,0 +1,27 @@ +// +// Created by koncord on 30.01.17. +// + +#ifndef OPENMW_MYSORTFILTERPROXYMODEL_HPP +#define OPENMW_MYSORTFILTERPROXYMODEL_HPP + + +#include + +class MySortFilterProxyModel : public QSortFilterProxyModel +{ + Q_OBJECT +protected: + bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const Q_DECL_FINAL; +public: + MySortFilterProxyModel(QObject *parent); + void filterFullServer(bool state); + void filterEmptyServers(bool state); + void pingLessThan(int maxPing); +private: + bool filterEmpty, filterFull; + int maxPing; +}; + + +#endif //OPENMW_MYSORTFILTERPROXYMODEL_HPP diff --git a/apps/browser/NetController.cpp b/apps/browser/NetController.cpp new file mode 100644 index 000000000..e539f0983 --- /dev/null +++ b/apps/browser/NetController.cpp @@ -0,0 +1,266 @@ +// +// Created by koncord on 07.01.17. +// + +#include +#include +#include "NetController.hpp" +#include "qdebug.h" + +#include +#include + +#include + +#include +#include +#include +#include +#include + +using namespace std; + +NetController *NetController::mThis = nullptr; + +NetController *NetController::get() +{ + assert(mThis); + return mThis; +} + +void NetController::Create(std::string addr, unsigned short port) +{ + assert(!mThis); + mThis = new NetController(addr, port); +} + +void NetController::Destroy() +{ + assert(mThis); + delete mThis; + mThis = nullptr; +} + +NetController::NetController(std::string addr, unsigned short port) : httpNetwork(addr, port) +{ + +} + +NetController::~NetController() +{ + +} + +struct pattern +{ + pattern(QString value): value(value) {} + bool operator()(const ServerData &data) + { + return value == data.addr; + } + QString value; +}; + +void NetController::setData(QString address, QJsonObject server, ServerModel *model) +{ + QModelIndex mi = model->index(0, ServerData::ADDR); + model->setData(mi, address); + + mi = model->index(0, ServerData::PLAYERS); + model->setData(mi, server["players"].toInt()); + + mi = model->index(0, ServerData::MAX_PLAYERS); + model->setData(mi, server["max_players"].toInt()); + + mi = model->index(0, ServerData::HOSTNAME); + model->setData(mi, server["hostname"].toString()); + + mi = model->index(0, ServerData::MODNAME); + model->setData(mi, server["modname"].toString()); + + mi = model->index(0, ServerData::VERSION); + model->setData(mi, server["version"].toString()); + + mi = model->index(0, ServerData::PASSW); + model->setData(mi, server["passw"].toBool()); + + mi = model->index(0, ServerData::PING); + + // This *should* fix a crash when a port isn't returned by data. + if(!address.contains(":")) + address.append(":25565"); + QStringList addr = address.split(":"); + model->setData(mi, PingRakNetServer(addr[0].toLatin1().data(), addr[1].toUShort())); +} + +bool NetController::downloadInfo(QAbstractItemModel *pModel, QModelIndex index) +{ + ServerModel *model = ((ServerModel *) pModel); + + /* + * download stuff + */ + + QString data; + QJsonParseError err; + + if(index.isValid() && index.row() >= 0) + { + const ServerData &sd = model->myData[index.row()]; + while(true) + { + data = QString::fromStdString(httpNetwork.getData((QString("/api/servers/") + sd.addr).toLatin1())); + if (!data.isEmpty() && data != "NO_CONTENT" && data != "LOST_CONNECTION") + break; + RakSleep(30); + } + qDebug() << "Content for \"" << sd.addr << "\": " << data; + + if(data == "bad request" || data == "not found") // TODO: if server is not registered we should download info directly from the server + { + qDebug() << "Server is not registered"; + return false; + } + + QJsonDocument jsonDocument = QJsonDocument::fromJson(data.toLatin1(), &err); + QJsonObject server = jsonDocument.object()["server"].toObject(); + + setData(sd.addr, server, model); + + return true; + } + + while (true) + { + data = QString::fromStdString(httpNetwork.getData("/api/servers")); + if (!data.isEmpty() && data != "NO_CONTENT" && data != "LOST_CONNECTION") + break; + RakSleep(30); + } + + if(data == "UNKNOWN_ADDRESS") + { + QMessageBox::critical(0, "Error", "Cannot connect to the master server!"); + return false; + } + + qDebug() << "Content: " << data; + + QJsonDocument jsonDocument = QJsonDocument::fromJson(data.toLatin1(), &err); + + QJsonObject listServers = jsonDocument.object()["list servers"].toObject(); + + for(auto iter = listServers.begin(); iter != listServers.end(); iter++) + { + QJsonObject server = iter->toObject(); + qDebug() << iter.key(); + qDebug() << server["hostname"].toString(); + qDebug() << server["modname"].toString(); + qDebug() << server["players"].toInt(); + qDebug() << server["max_players"].toInt(); + qDebug() << server["version"].toString(); + qDebug() << server["passw"].toBool(); + + QVector::Iterator value = std::find_if(model->myData.begin(), model->myData.end(), pattern(iter.key())); + if(value == model->myData.end()) + model->insertRow(0); + + setData(iter.key(), server, model); + } + + return true; +} + +bool NetController::updateInfo(QAbstractItemModel *pModel, QModelIndex index) +{ + ServerModel *model = ((ServerModel*)pModel); + + bool result; + if (index.isValid() && index.row() >= 0) + result = downloadInfo(pModel, index); + else + { + for (auto iter = model->myData.begin(); iter != model->myData.end(); iter++) + { + qDebug() << iter->addr; + } + model->removeRows(0, model->rowCount(index)); + result = downloadInfo(pModel, index); + } + return result; +} + +void NetController::updateInfo() +{ + QString data; + QString uri = "/api/servers/" + sd->addr; + while (true) + { + data = QString::fromStdString(httpNetwork.getData(uri.toLatin1())); + if (!data.isEmpty() && data != "NO_CONTENT" && data != "LOST_CONNECTION") + break; + RakSleep(30); + } + + if(data == "UNKNOWN_ADDRESS") + { + QMessageBox::critical(0, "Error", "Cannot connect to the master server!"); + return; + } + + qDebug() << "Content: " << data; + + QJsonParseError err; + QJsonDocument jsonDocument = QJsonDocument::fromJson(data.toLatin1(), &err); + + QMap map = jsonDocument.toVariant().toMap()["server"].toMap(); + + qDebug() << sd->addr; + qDebug() << map["hostname"].toString(); + qDebug() << map["modname"].toString(); + qDebug() << map["players"].toInt(); + qDebug() << map["max_players"].toInt(); + qDebug() << map["version"].toString(); + qDebug() << map["passw"].toBool(); + + sd->hostName = map["hostname"].toString(); + sd->modName = map["modname"].toString(); + sd->players = map["players"].toInt(); + sd->maxPlayers = map["max_players"].toInt(); + + if(!sd->addr.contains(":")) + sd->addr.append(":25565"); + QStringList addr = sd->addr.split(":"); + sd->ping = PingRakNetServer(addr[0].toLatin1(), addr[1].toUShort()); + if(sd->ping != PING_UNREACHABLE) + sed = getExtendedData(addr[0].toLatin1(), addr[1].toUShort()); + else + qDebug() << "Server is unreachable"; +} + +QStringList NetController::players() +{ + QStringList listPlayers; + for(auto player = sed.players.begin(); player != sed.players.end(); player++) + listPlayers.push_back(player->c_str()); + return listPlayers; +} + +QStringList NetController::plugins() +{ + QStringList listPlugins; + for(auto plugin = sed.plugins.begin(); plugin != sed.plugins.end(); plugin++) + listPlugins.push_back(plugin->c_str()); + return listPlugins; +} + +void NetController::selectServer(ServerData *pServerData) +{ + sd = pServerData; +} + +ServerData *NetController::selectedServer() +{ + return sd; +} + diff --git a/apps/browser/NetController.hpp b/apps/browser/NetController.hpp new file mode 100644 index 000000000..2dd508068 --- /dev/null +++ b/apps/browser/NetController.hpp @@ -0,0 +1,42 @@ +// +// Created by koncord on 07.01.17. +// + +#ifndef NEWLAUNCHER_NETCONTROLLER_HPP +#define NEWLAUNCHER_NETCONTROLLER_HPP + + +#include "ServerModel.hpp" +#include "netutils/HTTPNetwork.hpp" +#include "netutils/Utils.hpp" + +struct ServerModel; + +class NetController +{ +public: + static NetController *get(); + static void Create(std::string addr, unsigned short port); + static void Destroy(); + bool updateInfo(QAbstractItemModel *pModel, QModelIndex index= QModelIndex()); + void updateInfo(); + QStringList players(); + QStringList plugins(); + void selectServer(ServerData *pServerData); + ServerData *selectedServer(); +protected: + NetController(std::string addr, unsigned short port); + ~NetController(); +private: + NetController(const NetController &controller); + bool downloadInfo(QAbstractItemModel *pModel, QModelIndex index); + void setData(QString addr, QJsonObject server, ServerModel *model); + + static NetController *mThis; + ServerData *sd; + HTTPNetwork httpNetwork; + ServerExtendedData sed; +}; + + +#endif //NEWLAUNCHER_NETCONTROLLER_HPP diff --git a/apps/browser/ServerInfoDialog.cpp b/apps/browser/ServerInfoDialog.cpp new file mode 100644 index 000000000..5ed4b2a31 --- /dev/null +++ b/apps/browser/ServerInfoDialog.cpp @@ -0,0 +1,39 @@ +// +// Created by koncord on 07.01.17. +// + +#include "qdebug.h" +#include "NetController.hpp" + +#include "ServerInfoDialog.hpp" + +ServerInfoDialog::ServerInfoDialog(QWidget *parent): QDialog(parent) +{ + setupUi(this); + connect(btnRefresh, SIGNAL(clicked()), this, SLOT(refresh())); +} + +ServerInfoDialog::~ServerInfoDialog() +{ + +} + +void ServerInfoDialog::refresh() +{ + NetController::get()->updateInfo(); + ServerData *sd = NetController::get()->selectedServer(); + if (sd) + { + leAddr->setText(sd->addr); + lblName->setText(sd->hostName); + lblPing->setNum(sd->ping); + + listPlayers->clear(); + QStringList players = NetController::get()->players(); + listPlayers->addItems(players); + listPlugins->clear(); + listPlugins->addItems(NetController::get()->plugins()); + + lblPlayers->setText(QString::number(players.size()) + " / " + QString::number(sd->maxPlayers)); + } +} diff --git a/apps/browser/ServerInfoDialog.hpp b/apps/browser/ServerInfoDialog.hpp new file mode 100644 index 000000000..b26b34df5 --- /dev/null +++ b/apps/browser/ServerInfoDialog.hpp @@ -0,0 +1,21 @@ +// +// Created by koncord on 07.01.17. +// + +#ifndef NEWLAUNCHER_SERVERINFODIALOG_HPP +#define NEWLAUNCHER_SERVERINFODIALOG_HPP + +#include "ui_ServerInfo.h" + +class ServerInfoDialog : public QDialog, public Ui::Dialog +{ + Q_OBJECT +public: + explicit ServerInfoDialog(QWidget *parent = 0); + virtual ~ServerInfoDialog(); +public slots: + void refresh(); +}; + + +#endif //NEWLAUNCHER_SERVERINFODIALOG_HPP diff --git a/apps/browser/ServerModel.cpp b/apps/browser/ServerModel.cpp new file mode 100644 index 000000000..1aaa9e88e --- /dev/null +++ b/apps/browser/ServerModel.cpp @@ -0,0 +1,200 @@ +#include +#include "ServerModel.hpp" +#include + +ServerModel::ServerModel(QObject *parent) : QAbstractTableModel(parent) +{ +} + +ServerModel::~ServerModel() +{ + +} + +/*QHash ServerModel::roleNames() const +{ + return roles; +}*/ + +QVariant ServerModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + if (index.row() < 0 || index.row() > myData.size()) + return QVariant(); + + const ServerData &sd = myData.at(index.row()); + + if(role == Qt::DisplayRole) + { + QVariant var; + switch (index.column()) + { + case ServerData::ADDR: + var = sd.addr; + break; + case ServerData::PASSW: + var = sd.needPassw ? "Yes" : "No"; + break; + case ServerData::VERSION: + var = sd.version; + break; + case ServerData::PLAYERS: + var = sd.players; + break; + case ServerData::MAX_PLAYERS: + var = sd.maxPlayers; + break; + case ServerData::HOSTNAME: + var = sd.hostName; + break; + case ServerData::PING: + var = sd.ping; + break; + case ServerData::MODNAME: + if(sd.modName.isEmpty()) + var = "default"; + else + var = sd.modName; + break; + } + return var; + } + return QVariant(); +} + +QVariant ServerModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + QVariant var; + if (orientation == Qt::Horizontal) + { + if (role == Qt::SizeHintRole) + { + /*if(section == ServerData::HOSTNAME) + var = QSize(200, 25);*/ + } + else if (role == Qt::DisplayRole) + { + + switch (section) + { + case ServerData::ADDR: + var = "Address"; + break; + case ServerData::PASSW: + var = "Password"; + break; + case ServerData::VERSION: + var = "Version"; + break; + case ServerData::HOSTNAME: + var = "Host name"; + break; + case ServerData::PLAYERS: + var = "Players"; + break; + case ServerData::MAX_PLAYERS: + var = "Max players"; + break; + case ServerData::PING: + var = "Ping"; + break; + case ServerData::MODNAME: + var = "Game mode"; + } + } + } + return var; +} + +int ServerModel::rowCount(const QModelIndex &parent) const +{ + return myData.size(); +} + +int ServerModel::columnCount(const QModelIndex &parent) const +{ + return ServerData::LAST; +} + +bool ServerModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (index.isValid() && role == Qt::EditRole) + { + int row = index.row(); + int col = index.column(); + + ServerData &sd = myData[row]; + bool ok = true; + switch(col) + { + case ServerData::ADDR: + sd.addr = value.toString(); + ok = !sd.addr.isEmpty(); + break; + case ServerData::PASSW: + sd.needPassw = value.toBool(); + break; + case ServerData::VERSION: + sd.version = value.toString(); + ok = !sd.addr.isEmpty(); + break; + case ServerData::PLAYERS: + sd.players = value.toInt(&ok); + break; + case ServerData::MAX_PLAYERS: + sd.maxPlayers = value.toInt(&ok); + break; + case ServerData::HOSTNAME: + sd.hostName = value.toString(); + ok = !sd.addr.isEmpty(); + break; + case ServerData::PING: + sd.ping = value.toInt(&ok); + break; + case ServerData::MODNAME: + sd.modName = value.toString(); + break; + default: + return false; + } + if(ok) + emit(dataChanged(index, index)); + return true; + } + return false; +} + +bool ServerModel::insertRows(int position, int count, const QModelIndex &index) +{ + Q_UNUSED(index); + beginInsertRows(QModelIndex(), position, position + count - 1); + + for (int row = 0; row < count; ++row) { + ServerData sd {"", -1, -1, -1, "", "", false, 0}; + myData.insert(position, sd); + } + + endInsertRows(); + return true; +} + +bool ServerModel::removeRows(int position, int count, const QModelIndex &parent) +{ + if (count == 0) + return false; + + beginRemoveRows(parent, position, position + count - 1); + myData.erase(myData.begin()+position, myData.begin() + position + count); + endRemoveRows(); + + return true; +} + +QModelIndex ServerModel::index(int row, int column, const QModelIndex &parent) const +{ + + QModelIndex index = QAbstractTableModel::index(row, column, parent); + //qDebug() << "Valid index? " << index.isValid() << " " << row << " " << column; + return index; +} diff --git a/apps/browser/ServerModel.hpp b/apps/browser/ServerModel.hpp new file mode 100644 index 000000000..78f16501b --- /dev/null +++ b/apps/browser/ServerModel.hpp @@ -0,0 +1,56 @@ +#ifndef SERVERMODEL_FONTMODEL_HPP +#define SERVERMODEL_FONTMODEL_HPP + +#include +#include +#include +#include + +struct ServerData +{ + QString addr; + int players, maxPlayers; + int ping; + QString hostName; + QString modName; + bool needPassw; + QString version; + enum IDS + { + ADDR, + HOSTNAME, + PLAYERS, + MAX_PLAYERS, + PASSW, + MODNAME, + PING, + VERSION, + LAST + }; +}; + +class ServerModel: public QAbstractTableModel +{ + Q_OBJECT +public: + explicit ServerModel(QObject *parent = 0); + ~ServerModel(); + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_FINAL; + int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_FINAL; + int columnCount(const QModelIndex &parent) const Q_DECL_FINAL; + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) Q_DECL_FINAL; + + bool insertRows(int row, int count, const QModelIndex &index = QModelIndex()) Q_DECL_FINAL; + bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) Q_DECL_FINAL; + + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const Q_DECL_FINAL; + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const Q_DECL_FINAL; + + +public: + //QHash roles; + QVector myData; +}; + + +#endif //SERVERMODEL_FONTMODEL_HPP diff --git a/apps/browser/main.cpp b/apps/browser/main.cpp new file mode 100644 index 000000000..ad54962c1 --- /dev/null +++ b/apps/browser/main.cpp @@ -0,0 +1,57 @@ +#include +#include +#include +#include "MainWindow.hpp" +#include "NetController.hpp" + +std::string loadSettings (Settings::Manager & settings) +{ + Files::ConfigurationManager mCfgMgr; + // Create the settings manager and load default settings file + const std::string localdefault = (mCfgMgr.getLocalPath() / "tes3mp-client-default.cfg").string(); + const std::string globaldefault = (mCfgMgr.getGlobalPath() / "tes3mp-client-default.cfg").string(); + + // prefer local + if (boost::filesystem::exists(localdefault)) + settings.loadDefault(localdefault); + else if (boost::filesystem::exists(globaldefault)) + settings.loadDefault(globaldefault); + else + throw std::runtime_error ("No default settings file found! Make sure the file \"tes3mp-client-default.cfg\" was properly installed."); + + // load user settings if they exist + const std::string settingspath = (mCfgMgr.getUserConfigPath() / "tes3mp-client.cfg").string(); + if (boost::filesystem::exists(settingspath)) + settings.loadUser(settingspath); + + return settingspath; +} + +int main(int argc, char *argv[]) +{ + Settings::Manager mgr; + + loadSettings(mgr); + + std::string addr = mgr.getString("address", "Master"); + int port = mgr.getInt("port", "Master"); + + // initialize resources, if needed + // Q_INIT_RESOURCE(resfile); + + NetController::Create(addr, port); + atexit(NetController::Destroy); + QApplication app(argc, argv); + MainWindow d; + + if (d.refresh()) + { + d.show(); + return app.exec(); + } + else + { + app.exit(); + return 0; + } +} \ No newline at end of file diff --git a/apps/browser/netutils/HTTPNetwork.cpp b/apps/browser/netutils/HTTPNetwork.cpp new file mode 100644 index 000000000..b266e6acb --- /dev/null +++ b/apps/browser/netutils/HTTPNetwork.cpp @@ -0,0 +1,99 @@ +// +// Created by koncord on 07.01.17. +// + +#include +#include +#include +#include + +#include + +#include "HTTPNetwork.hpp" + +using namespace RakNet; + +HTTPNetwork::HTTPNetwork(std::string addr, unsigned short port) : address(addr), port(port) +{ + httpConnection = HTTPConnection2::GetInstance(); + tcpInterface = new TCPInterface; + tcpInterface->Start(0, 64); + tcpInterface->AttachPlugin(httpConnection); +} + +HTTPNetwork::~HTTPNetwork() +{ + delete tcpInterface; +} + +std::string HTTPNetwork::answer() +{ + RakNet::SystemAddress sa; + RakNet::Packet *packet; + RakNet::SystemAddress hostReceived; + RakNet::RakString response; + RakNet::RakString transmitted, hostTransmitted; + int contentOffset = 0; + + while (true) + { + // This is kind of crappy, but for TCP plugins, always do HasCompletedConnectionAttempt, + // then Receive(), then HasFailedConnectionAttempt(),HasLostConnection() + sa = tcpInterface->HasCompletedConnectionAttempt(); + if (sa != RakNet::UNASSIGNED_SYSTEM_ADDRESS) + printf("Connected to master server: %s\n", sa.ToString()); + + sa = tcpInterface->HasFailedConnectionAttempt(); + if (sa != RakNet::UNASSIGNED_SYSTEM_ADDRESS) + { + printf("Failed to connect to master server: %s\n", sa.ToString()); + return "FAIL_CONNECT"; + } + sa = tcpInterface->HasLostConnection(); + if (sa != RakNet::UNASSIGNED_SYSTEM_ADDRESS) + { + printf("Lost connection to master server: %s\n", sa.ToString()); + return "LOST_CONNECTION"; + } + + for (packet = tcpInterface->Receive(); packet; tcpInterface->DeallocatePacket( + packet), packet = tcpInterface->Receive()); + + if (httpConnection->GetResponse(transmitted, hostTransmitted, response, hostReceived, contentOffset)) + { + if (contentOffset < 0) + return "NO_CONTENT"; // no content + tcpInterface->CloseConnection(sa); + + return (response.C_String() + contentOffset); + } + RakSleep(30); + } +} + +std::string HTTPNetwork::getData(const char *uri) +{ + RakNet::RakString createRequest = RakNet::RakString::FormatForGET(uri); + + if (!httpConnection->TransmitRequest(createRequest, address.c_str(), port)) + return "UNKNOWN_ADDRESS"; + return answer(); +} + +std::string HTTPNetwork::getDataPOST(const char *uri, const char* body, const char* contentType) +{ + RakNet::RakString createRequest = RakNet::RakString::FormatForPOST(uri, contentType, body); + + if (!httpConnection->TransmitRequest(createRequest, address.c_str(), port)) + return "UNKNOWN_ADDRESS"; + return answer(); +} + +std::string HTTPNetwork::getDataPUT(const char *uri, const char* body, const char* contentType) +{ + RakNet::RakString createRequest = RakNet::RakString::FormatForPUT(uri, contentType, body); + + if (!httpConnection->TransmitRequest(createRequest, address.c_str(), port)) + return "UNKNOWN_ADDRESS"; + return answer(); +} diff --git a/apps/browser/netutils/HTTPNetwork.hpp b/apps/browser/netutils/HTTPNetwork.hpp new file mode 100644 index 000000000..8597ddcb0 --- /dev/null +++ b/apps/browser/netutils/HTTPNetwork.hpp @@ -0,0 +1,35 @@ +// +// Created by koncord on 07.01.17. +// + +#ifndef NEWLAUNCHER_HTTPNETWORK_HPP +#define NEWLAUNCHER_HTTPNETWORK_HPP + + +#include + +namespace RakNet +{ + class TCPInterface; + class HTTPConnection2; +} + +class HTTPNetwork +{ +public: + HTTPNetwork(std::string addr, unsigned short port); + ~HTTPNetwork(); + std::string getData(const char *uri); + std::string getDataPOST(const char *uri, const char* body, const char* contentType = "application/json"); + std::string getDataPUT(const char *uri, const char* body, const char* contentType = "application/json"); + +protected: + RakNet::TCPInterface *tcpInterface; + RakNet::HTTPConnection2 *httpConnection; + std::string address; + unsigned short port; + std::string answer(); +}; + + +#endif //NEWLAUNCHER_HTTPNETWORK_HPP diff --git a/apps/browser/netutils/Utils.cpp b/apps/browser/netutils/Utils.cpp new file mode 100644 index 000000000..0530776ef --- /dev/null +++ b/apps/browser/netutils/Utils.cpp @@ -0,0 +1,156 @@ +// +// Created by koncord on 07.01.17. +// + +#include +#include +#include +#include + +#include +#include +#include + +#include "Utils.hpp" + +using namespace std; + +unsigned int PingRakNetServer(const char *addr, unsigned short port) +{ + RakNet::Packet *packet; + bool done = false; + int attempts = 0; + RakNet::TimeMS time = PING_UNREACHABLE; + + RakNet::SocketDescriptor socketDescriptor {0, ""}; + RakNet::RakPeerInterface *peer = RakNet::RakPeerInterface::GetInstance(); + peer->Startup(1,&socketDescriptor, 1); + + peer->Ping(addr, port, false); + while (!done) + { + for (packet=peer->Receive(); packet; peer->DeallocatePacket(packet), packet=peer->Receive()) + { + if(packet->data[0] == ID_UNCONNECTED_PONG) + { + RakNet::BitStream bsIn(&packet->data[1], packet->length, false); + bsIn.Read(time); + time = RakNet::GetTimeMS() - time - 5; + done = true; + break; + } + } + + if (attempts >= 60) // wait 300 msec + done = true; + attempts++; + RakSleep(5); + } + + peer->Shutdown(0); + RakNet::RakPeerInterface::DestroyInstance(peer); + return time; +} + +ServerExtendedData getExtendedData(const char *addr, unsigned short port) +{ + ServerExtendedData data; + RakNet::SocketDescriptor socketDescriptor = {0, ""}; + RakNet::RakPeerInterface *peer = RakNet::RakPeerInterface::GetInstance(); + peer->Startup(1, &socketDescriptor, 1); + + stringstream sstr(TES3MP_VERSION); + sstr << TES3MP_PROTO_VERSION; + + std::string msg = ""; + + if (peer->Connect(addr, port, sstr.str().c_str(), (int)(sstr.str().size()), 0, 0, 3, 500, 0) != RakNet::CONNECTION_ATTEMPT_STARTED) + msg = "Connection attempt failed.\n"; + + + int queue = 0; + while (queue == 0) + { + for (RakNet::Packet *packet = peer->Receive(); packet; peer->DeallocatePacket( + packet), packet = peer->Receive()) + { + switch (packet->data[0]) + { + case ID_CONNECTION_ATTEMPT_FAILED: + { + msg = "Connection failed.\n" + "Either the IP address is wrong or a firewall on either system is blocking\n" + "UDP packets on the port you have chosen."; + queue = -1; + break; + } + case ID_INVALID_PASSWORD: + { + msg = "Connection failed.\n" + "The client or server is outdated.\n"; + queue = -1; + break; + } + case ID_CONNECTION_REQUEST_ACCEPTED: + { + msg = "Connection accepted.\n"; + queue = 1; + break; + } + case ID_DISCONNECTION_NOTIFICATION: + throw runtime_error("ID_DISCONNECTION_NOTIFICATION.\n"); + case ID_CONNECTION_BANNED: + throw runtime_error("ID_CONNECTION_BANNED.\n"); + case ID_CONNECTION_LOST: + throw runtime_error("ID_CONNECTION_LOST.\n"); + default: + printf("Connection message with identifier %i has arrived in initialization.\n", packet->data[0]); + } + } + } + puts(msg.c_str()); + + if(queue == -1) // connection is failed + return data; + + { + RakNet::BitStream bs; + bs.Write((unsigned char) (ID_USER_PACKET_ENUM + 1)); + peer->Send(&bs, HIGH_PRIORITY, RELIABLE_ORDERED, 0, RakNet::UNASSIGNED_SYSTEM_ADDRESS, true); + } + + RakNet::Packet *packet; + bool done = false; + while (!done) + { + for (packet = peer->Receive(); packet; peer->DeallocatePacket(packet), packet = peer->Receive()) + { + if(packet->data[0] == (ID_USER_PACKET_ENUM+1)) + { + RakNet::BitStream bs(packet->data, packet->length, false); + bs.IgnoreBytes(1); + size_t length = 0; + bs.Read(length); + for(int i = 0; i < length; i++) + { + RakNet::RakString str; + bs.Read(str); + data.players.push_back(str.C_String()); + } + bs.Read(length); + for(int i = 0; i < length; i++) + { + RakNet::RakString str; + bs.Read(str); + data.plugins.push_back(str.C_String()); + } + done = true; + } + } + } + + peer->Shutdown(0); + RakSleep(10); + RakNet::RakPeerInterface::DestroyInstance(peer); + return data; +} diff --git a/apps/browser/netutils/Utils.hpp b/apps/browser/netutils/Utils.hpp new file mode 100644 index 000000000..1be5b788b --- /dev/null +++ b/apps/browser/netutils/Utils.hpp @@ -0,0 +1,24 @@ +// +// Created by koncord on 07.01.17. +// + +#ifndef NEWLAUNCHER_PING_HPP +#define NEWLAUNCHER_PING_HPP + +#include +#include + + +#define PING_UNREACHABLE 999 + +unsigned int PingRakNetServer(const char *addr, unsigned short port); + +struct ServerExtendedData +{ + std::vector players; + std::vector plugins; +}; + +ServerExtendedData getExtendedData(const char *addr, unsigned short port); + +#endif //NEWLAUNCHER_PING_HPP diff --git a/apps/esmtool/esmtool.cpp b/apps/esmtool/esmtool.cpp index be90afec3..6d2b59ad9 100644 --- a/apps/esmtool/esmtool.cpp +++ b/apps/esmtool/esmtool.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include @@ -354,12 +355,12 @@ int load(Arguments& info) esm.getRecHeader(flags); EsmTool::RecordBase *record = EsmTool::RecordBase::create(n); - if (record == 0) + if (record == 0) { - if (std::find(skipped.begin(), skipped.end(), n.val) == skipped.end()) + if (std::find(skipped.begin(), skipped.end(), n.intval) == skipped.end()) { std::cout << "Skipping " << n.toString() << " records." << std::endl; - skipped.push_back(n.val); + skipped.push_back(n.intval); } esm.skipRecord(); @@ -391,20 +392,20 @@ int load(Arguments& info) record->print(); } - if (record->getType().val == ESM::REC_CELL && loadCells && interested) + if (record->getType().intval == ESM::REC_CELL && loadCells && interested) { loadCell(record->cast()->get(), esm, info); } - if (save) + if (save) { info.data.mRecords.push_back(record); - } - else + } + else { delete record; } - ++info.data.mRecordStats[n.val]; + ++info.data.mRecordStats[n.intval]; } } catch(std::exception &e) { @@ -442,23 +443,18 @@ int clone(Arguments& info) size_t recordCount = info.data.mRecords.size(); int digitCount = 1; // For a nicer output - if (recordCount > 9) ++digitCount; - if (recordCount > 99) ++digitCount; - if (recordCount > 999) ++digitCount; - if (recordCount > 9999) ++digitCount; - if (recordCount > 99999) ++digitCount; - if (recordCount > 999999) ++digitCount; + if (recordCount > 0) + digitCount = (int)std::log10(recordCount) + 1; std::cout << "Loaded " << recordCount << " records:" << std::endl << std::endl; - ESM::NAME name; - int i = 0; typedef std::map Stats; Stats &stats = info.data.mRecordStats; for (Stats::iterator it = stats.begin(); it != stats.end(); ++it) { - name.val = it->first; + ESM::NAME name; + name.intval = it->first; int amount = it->second; std::cout << std::setw(digitCount) << amount << " " << name.toString() << " "; @@ -491,12 +487,12 @@ int clone(Arguments& info) for (Records::iterator it = records.begin(); it != records.end() && i > 0; ++it) { EsmTool::RecordBase *record = *it; - name.val = record->getType().val; + const ESM::NAME& typeName = record->getType(); - esm.startRecord(name.toString(), record->getFlags()); + esm.startRecord(typeName.toString(), record->getFlags()); record->save(esm); - if (name.val == ESM::REC_CELL) { + if (typeName.intval == ESM::REC_CELL) { ESM::Cell *ptr = &record->cast()->get(); if (!info.data.mCellRefs[ptr].empty()) { typedef std::deque > RefList; @@ -508,7 +504,7 @@ int clone(Arguments& info) } } - esm.endRecord(name.toString()); + esm.endRecord(typeName.toString()); saved++; int perc = (int)((saved / (float)recordCount)*100); diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp index d7cbea73b..3123308ef 100644 --- a/apps/esmtool/record.cpp +++ b/apps/esmtool/record.cpp @@ -179,7 +179,7 @@ RecordBase::create(ESM::NAME type) { RecordBase *record = 0; - switch (type.val) { + switch (type.intval) { case ESM::REC_ACTI: { record = new EsmTool::Record; @@ -494,7 +494,7 @@ void Record::print() std::cout << " Weight: " << mData.mData.mWeight << std::endl; std::cout << " Value: " << mData.mData.mValue << std::endl; std::cout << " IsScroll: " << mData.mData.mIsScroll << std::endl; - std::cout << " SkillID: " << mData.mData.mSkillID << std::endl; + std::cout << " SkillId: " << mData.mData.mSkillId << std::endl; std::cout << " Enchantment Points: " << mData.mData.mEnchant << std::endl; if (mPrintPlain) { diff --git a/apps/essimporter/CMakeLists.txt b/apps/essimporter/CMakeLists.txt index 84e31dad9..93f53d0e8 100644 --- a/apps/essimporter/CMakeLists.txt +++ b/apps/essimporter/CMakeLists.txt @@ -42,3 +42,7 @@ if (BUILD_WITH_CODE_COVERAGE) add_definitions (--coverage) target_link_libraries(openmw-essimporter gcov) endif() + +if (WIN32) + INSTALL(TARGETS openmw-essimporter RUNTIME DESTINATION ".") +endif(WIN32) diff --git a/apps/essimporter/convertacdt.cpp b/apps/essimporter/convertacdt.cpp index 8f090b3fc..5c2bcc402 100644 --- a/apps/essimporter/convertacdt.cpp +++ b/apps/essimporter/convertacdt.cpp @@ -1,3 +1,9 @@ +#include +#include +#include + +#include + #include "convertacdt.hpp" namespace ESSImport @@ -49,4 +55,49 @@ namespace ESSImport npcStats.mTimeToStartDrowning = actorData.mACDT.mBreathMeter; } + void convertANIS (const ANIS& anis, ESM::AnimationState& state) + { + static const char* animGroups[] = + { + "Idle", "Idle2", "Idle3", "Idle4", "Idle5", "Idle6", "Idle7", "Idle8", "Idle9", "Idlehh", "Idle1h", "Idle2c", + "Idle2w", "IdleSwim", "IdleSpell", "IdleCrossbow", "IdleSneak", "IdleStorm", "Torch", "Hit1", "Hit2", "Hit3", + "Hit4", "Hit5", "SwimHit1", "SwimHit2", "SwimHit3", "Death1", "Death2", "Death3", "Death4", "Death5", + "DeathKnockDown", "DeathKnockOut", "KnockDown", "KnockOut", "SwimDeath", "SwimDeath2", "SwimDeath3", + "SwimDeathKnockDown", "SwimDeathKnockOut", "SwimKnockOut", "SwimKnockDown", "SwimWalkForward", + "SwimWalkBack", "SwimWalkLeft", "SwimWalkRight", "SwimRunForward", "SwimRunBack", "SwimRunLeft", + "SwimRunRight", "SwimTurnLeft", "SwimTurnRight", "WalkForward", "WalkBack", "WalkLeft", "WalkRight", + "TurnLeft", "TurnRight", "RunForward", "RunBack", "RunLeft", "RunRight", "SneakForward", "SneakBack", + "SneakLeft", "SneakRight", "Jump", "WalkForwardhh", "WalkBackhh", "WalkLefthh", "WalkRighthh", + "TurnLefthh", "TurnRighthh", "RunForwardhh", "RunBackhh", "RunLefthh", "RunRighthh", "SneakForwardhh", + "SneakBackhh", "SneakLefthh", "SneakRighthh", "Jumphh", "WalkForward1h", "WalkBack1h", "WalkLeft1h", + "WalkRight1h", "TurnLeft1h", "TurnRight1h", "RunForward1h", "RunBack1h", "RunLeft1h", "RunRight1h", + "SneakForward1h", "SneakBack1h", "SneakLeft1h", "SneakRight1h", "Jump1h", "WalkForward2c", "WalkBack2c", + "WalkLeft2c", "WalkRight2c", "TurnLeft2c", "TurnRight2c", "RunForward2c", "RunBack2c", "RunLeft2c", + "RunRight2c", "SneakForward2c", "SneakBack2c", "SneakLeft2c", "SneakRight2c", "Jump2c", "WalkForward2w", + "WalkBack2w", "WalkLeft2w", "WalkRight2w", "TurnLeft2w", "TurnRight2w", "RunForward2w", "RunBack2w", + "RunLeft2w", "RunRight2w", "SneakForward2w", "SneakBack2w", "SneakLeft2w", "SneakRight2w", "Jump2w", + "SpellCast", "SpellTurnLeft", "SpellTurnRight", "Attack1", "Attack2", "Attack3", "SwimAttack1", + "SwimAttack2", "SwimAttack3", "HandToHand", "Crossbow", "BowAndArrow", "ThrowWeapon", "WeaponOneHand", + "WeaponTwoHand", "WeaponTwoWide", "Shield", "PickProbe", "InventoryHandToHand", "InventoryWeaponOneHand", + "InventoryWeaponTwoHand", "InventoryWeaponTwoWide" + }; + + if (anis.mGroupIndex < (sizeof(animGroups) / sizeof(*animGroups))) + { + std::string group(animGroups[anis.mGroupIndex]); + Misc::StringUtils::lowerCaseInPlace(group); + + ESM::AnimationState::ScriptedAnimation scriptedAnim; + scriptedAnim.mGroup = group; + scriptedAnim.mTime = anis.mTime; + scriptedAnim.mAbsolute = true; + // Neither loop count nor queueing seems to be supported by the ess format. + scriptedAnim.mLoopCount = std::numeric_limits::max(); + state.mScriptedAnims.push_back(scriptedAnim); + } + else + // TODO: Handle 0xFF index, which seems to be used for finished animations. + std::cerr << "unknown animation group index: " << static_cast(anis.mGroupIndex) << std::endl; + } + } diff --git a/apps/essimporter/convertacdt.hpp b/apps/essimporter/convertacdt.hpp index bc9a7bd00..4059dd1af 100644 --- a/apps/essimporter/convertacdt.hpp +++ b/apps/essimporter/convertacdt.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "importacdt.hpp" @@ -18,6 +19,8 @@ namespace ESSImport void convertACSC (const ACSC& acsc, ESM::CreatureStats& cStats); void convertNpcData (const ActorData& actorData, ESM::NpcStats& npcStats); + + void convertANIS (const ANIS& anis, ESM::AnimationState& state); } #endif diff --git a/apps/essimporter/converter.cpp b/apps/essimporter/converter.cpp index afd0ef131..a428a8c71 100644 --- a/apps/essimporter/converter.cpp +++ b/apps/essimporter/converter.cpp @@ -34,6 +34,9 @@ namespace objstate.mCount = 0; convertSCRI(cellref.mSCRI, objstate.mLocals); objstate.mHasLocals = !objstate.mLocals.mVariables.empty(); + + if (cellref.mHasANIS) + convertANIS(cellref.mANIS, objstate.mAnimationState); } bool isIndexedRefId(const std::string& indexedRefId) diff --git a/apps/essimporter/converter.hpp b/apps/essimporter/converter.hpp index 81b2bec14..e4985f993 100644 --- a/apps/essimporter/converter.hpp +++ b/apps/essimporter/converter.hpp @@ -121,7 +121,7 @@ public: { mContext->mPlayer.mObject.mCreatureStats.mLevel = npc.mNpdt52.mLevel; mContext->mPlayerBase = npc; - std::map empty; + ESM::SpellState::SpellParams empty; // FIXME: player start spells and birthsign spells aren't listed here, // need to fix openmw to account for this for (std::vector::const_iterator it = npc.mSpells.mList.begin(); it != npc.mSpells.mList.end(); ++it) @@ -202,7 +202,7 @@ public: bool isDeleted = false; book.load(esm, isDeleted); - if (book.mData.mSkillID == -1) + if (book.mData.mSkillId == -1) mContext->mPlayer.mObject.mNpcStats.mUsedIds.push_back(Misc::StringUtils::lowerCase(book.mId)); mRecords[book.mId] = book; @@ -271,23 +271,34 @@ private: class ConvertPCDT : public Converter { public: - ConvertPCDT() : mFirstPersonCam(true) {} + ConvertPCDT() + : mFirstPersonCam(true), + mTeleportingEnabled(true), + mLevitationEnabled(true) + {} virtual void read(ESM::ESMReader &esm) { PCDT pcdt; pcdt.load(esm); - convertPCDT(pcdt, mContext->mPlayer, mContext->mDialogueState.mKnownTopics, mFirstPersonCam); + convertPCDT(pcdt, mContext->mPlayer, mContext->mDialogueState.mKnownTopics, mFirstPersonCam, mTeleportingEnabled, mLevitationEnabled, mContext->mControlsState); } virtual void write(ESM::ESMWriter &esm) { + esm.startRecord(ESM::REC_ENAB); + esm.writeHNT("TELE", mTeleportingEnabled); + esm.writeHNT("LEVT", mLevitationEnabled); + esm.endRecord(ESM::REC_ENAB); + esm.startRecord(ESM::REC_CAM_); esm.writeHNT("FIRS", mFirstPersonCam); esm.endRecord(ESM::REC_CAM_); } private: bool mFirstPersonCam; + bool mTeleportingEnabled; + bool mLevitationEnabled; }; class ConvertCNTC : public Converter diff --git a/apps/essimporter/convertplayer.cpp b/apps/essimporter/convertplayer.cpp index 5718201f7..4a4a9a573 100644 --- a/apps/essimporter/convertplayer.cpp +++ b/apps/essimporter/convertplayer.cpp @@ -5,7 +5,7 @@ namespace ESSImport { - void convertPCDT(const PCDT& pcdt, ESM::Player& out, std::vector& outDialogueTopics, bool& firstPersonCam) + void convertPCDT(const PCDT& pcdt, ESM::Player& out, std::vector& outDialogueTopics, bool& firstPersonCam, bool& teleportingEnabled, bool& levitationEnabled, ESM::ControlsState& controls) { out.mBirthsign = pcdt.mBirthsign; out.mObject.mNpcStats.mBounty = pcdt.mBounty; @@ -17,24 +17,72 @@ namespace ESSImport faction.mReputation = it->mReputation; out.mObject.mNpcStats.mFactions[Misc::StringUtils::lowerCase(it->mFactionName.toString())] = faction; } + for (int i=0; i<3; ++i) + out.mObject.mNpcStats.mSpecIncreases[i] = pcdt.mPNAM.mSpecIncreases[i]; for (int i=0; i<8; ++i) out.mObject.mNpcStats.mSkillIncrease[i] = pcdt.mPNAM.mSkillIncreases[i]; for (int i=0; i<27; ++i) out.mObject.mNpcStats.mSkills[i].mProgress = pcdt.mPNAM.mSkillProgress[i]; out.mObject.mNpcStats.mLevelProgress = pcdt.mPNAM.mLevelProgress; - if (pcdt.mPNAM.mDrawState & PCDT::DrawState_Weapon) + if (pcdt.mPNAM.mPlayerFlags & PCDT::PlayerFlags_WeaponDrawn) out.mObject.mCreatureStats.mDrawState = 1; - if (pcdt.mPNAM.mDrawState & PCDT::DrawState_Spell) + if (pcdt.mPNAM.mPlayerFlags & PCDT::PlayerFlags_SpellDrawn) out.mObject.mCreatureStats.mDrawState = 2; - firstPersonCam = (pcdt.mPNAM.mCameraState == PCDT::CameraState_FirstPerson); + firstPersonCam = !(pcdt.mPNAM.mPlayerFlags & PCDT::PlayerFlags_ThirdPerson); + teleportingEnabled = !(pcdt.mPNAM.mPlayerFlags & PCDT::PlayerFlags_TeleportingDisabled); + levitationEnabled = !(pcdt.mPNAM.mPlayerFlags & PCDT::PlayerFlags_LevitationDisabled); for (std::vector::const_iterator it = pcdt.mKnownDialogueTopics.begin(); it != pcdt.mKnownDialogueTopics.end(); ++it) { outDialogueTopics.push_back(Misc::StringUtils::lowerCase(*it)); } + + controls.mViewSwitchDisabled = pcdt.mPNAM.mPlayerFlags & PCDT::PlayerFlags_ViewSwitchDisabled; + controls.mControlsDisabled = pcdt.mPNAM.mPlayerFlags & PCDT::PlayerFlags_ControlsDisabled; + controls.mJumpingDisabled = pcdt.mPNAM.mPlayerFlags & PCDT::PlayerFlags_JumpingDisabled; + controls.mLookingDisabled = pcdt.mPNAM.mPlayerFlags & PCDT::PlayerFlags_LookingDisabled; + controls.mVanityModeDisabled = pcdt.mPNAM.mPlayerFlags & PCDT::PlayerFlags_VanityModeDisabled; + controls.mWeaponDrawingDisabled = pcdt.mPNAM.mPlayerFlags & PCDT::PlayerFlags_WeaponDrawingDisabled; + controls.mSpellDrawingDisabled = pcdt.mPNAM.mPlayerFlags & PCDT::PlayerFlags_SpellDrawingDisabled; + + if (pcdt.mHasMark) + { + out.mHasMark = 1; + + const PCDT::PNAM::MarkLocation& mark = pcdt.mPNAM.mMarkLocation; + + ESM::CellId cell; + cell.mWorldspace = ESM::CellId::sDefaultWorldspace; + cell.mPaged = true; + + cell.mIndex.mX = mark.mCellX; + cell.mIndex.mY = mark.mCellY; + + // TODO: Figure out a better way to detect interiors. (0, 0) is a valid exterior cell. + if (mark.mCellX == 0 && mark.mCellY == 0) + { + cell.mWorldspace = pcdt.mMNAM; + cell.mPaged = false; + } + + out.mMarkedCell = cell; + out.mMarkedPosition.pos[0] = mark.mX; + out.mMarkedPosition.pos[1] = mark.mY; + out.mMarkedPosition.pos[2] = mark.mZ; + out.mMarkedPosition.rot[0] = out.mMarkedPosition.rot[1] = 0.0f; + out.mMarkedPosition.rot[2] = mark.mRotZ; + } + + if (pcdt.mHasENAM) + { + const int cellSize = 8192; + out.mLastKnownExteriorPosition[0] = (pcdt.mENAM.mCellX + 0.5f) * cellSize; + out.mLastKnownExteriorPosition[1] = (pcdt.mENAM.mCellY + 0.5f) * cellSize; + out.mLastKnownExteriorPosition[2] = 0.0f; + } } } diff --git a/apps/essimporter/convertplayer.hpp b/apps/essimporter/convertplayer.hpp index f6731eed7..1d2fdc87a 100644 --- a/apps/essimporter/convertplayer.hpp +++ b/apps/essimporter/convertplayer.hpp @@ -4,11 +4,12 @@ #include "importplayer.hpp" #include +#include namespace ESSImport { - void convertPCDT(const PCDT& pcdt, ESM::Player& out, std::vector& outDialogueTopics, bool& firstPersonCam); + void convertPCDT(const PCDT& pcdt, ESM::Player& out, std::vector& outDialogueTopics, bool& firstPersonCam, bool& teleportingEnabled, bool& levitationEnabled, ESM::ControlsState& controls); } diff --git a/apps/essimporter/importacdt.cpp b/apps/essimporter/importacdt.cpp index 239c46698..0ddd2eb64 100644 --- a/apps/essimporter/importacdt.cpp +++ b/apps/essimporter/importacdt.cpp @@ -75,7 +75,7 @@ namespace ESSImport // unsure at which point between TGTN and CRED if (esm.isNextSub("AADT")) { - // occured when a creature was in the middle of its attack, 44 bytes + // occurred when a creature was in the middle of its attack, 44 bytes esm.skipHSub(); } @@ -123,8 +123,13 @@ namespace ESSImport if (esm.isNextSub("ND3D")) esm.skipHSub(); + + mHasANIS = false; if (esm.isNextSub("ANIS")) - esm.skipHSub(); + { + mHasANIS = true; + esm.getHT(mANIS); + } } } diff --git a/apps/essimporter/importacdt.hpp b/apps/essimporter/importacdt.hpp index eacb2edf1..bf48d1f78 100644 --- a/apps/essimporter/importacdt.hpp +++ b/apps/essimporter/importacdt.hpp @@ -55,6 +55,12 @@ namespace ESSImport unsigned char mCorpseClearCountdown; // hours? unsigned char mUnknown3[71]; }; + struct ANIS + { + unsigned char mGroupIndex; + unsigned char mUnknown[3]; + float mTime; + }; #pragma pack(pop) struct ActorData : public ESM::CellRef @@ -77,6 +83,9 @@ namespace ESSImport SCRI mSCRI; + bool mHasANIS; + ANIS mANIS; // scripted animation state + void load(ESM::ESMReader& esm); }; diff --git a/apps/essimporter/importer.cpp b/apps/essimporter/importer.cpp index 3d94f2b90..a420d08da 100644 --- a/apps/essimporter/importer.cpp +++ b/apps/essimporter/importer.cpp @@ -51,6 +51,7 @@ namespace { for (int x=0; x<128; ++x) { + assert(image->data(x,y)); *(image->data(x,y)+2) = *it++; *(image->data(x,y)+1) = *it++; *image->data(x,y) = *it++; @@ -322,14 +323,14 @@ namespace ESSImport ESM::NAME n = esm.getRecName(); esm.getRecHeader(); - std::map >::iterator it = converters.find(n.val); + std::map >::iterator it = converters.find(n.intval); if (it != converters.end()) { it->second->read(esm); } else { - if (unknownRecords.insert(n.val).second) + if (unknownRecords.insert(n.intval).second) { std::ios::fmtflags f(std::cerr.flags()); std::cerr << "unknown record " << n.toString() << " (0x" << std::hex << esm.getFileOffset() << ")" << std::endl; @@ -422,6 +423,10 @@ namespace ESSImport writer.startRecord (ESM::REC_DIAS); context.mDialogueState.save(writer); writer.endRecord(ESM::REC_DIAS); + + writer.startRecord(ESM::REC_INPU); + context.mControlsState.save(writer); + writer.endRecord(ESM::REC_INPU); } diff --git a/apps/essimporter/importercontext.hpp b/apps/essimporter/importercontext.hpp index c93dff269..0ad73c267 100644 --- a/apps/essimporter/importercontext.hpp +++ b/apps/essimporter/importercontext.hpp @@ -9,6 +9,7 @@ #include #include #include +#include #include "importnpcc.hpp" #include "importcrec.hpp" @@ -32,6 +33,8 @@ namespace ESSImport ESM::DialogueState mDialogueState; + ESM::ControlsState mControlsState; + // cells which should show an explored overlay on the global map std::set > mExploredCells; @@ -59,10 +62,14 @@ namespace ESSImport playerCellId.mPaged = true; playerCellId.mIndex.mX = playerCellId.mIndex.mY = 0; mPlayer.mCellId = playerCellId; - //mPlayer.mLastKnownExteriorPosition - mPlayer.mHasMark = 0; // TODO + mPlayer.mLastKnownExteriorPosition[0] + = mPlayer.mLastKnownExteriorPosition[1] + = mPlayer.mLastKnownExteriorPosition[2] + = 0.0f; + mPlayer.mHasMark = 0; mPlayer.mCurrentCrimeId = 0; // TODO mPlayer.mObject.blank(); + mPlayer.mObject.mEnabled = true; mPlayer.mObject.mRef.mRefID = "player"; // REFR.mRefID would be PlayerSaveGame mGlobalMapState.mBounds.mMinX = 0; diff --git a/apps/essimporter/importinventory.cpp b/apps/essimporter/importinventory.cpp index 3ec640d3d..2c87a9a19 100644 --- a/apps/essimporter/importinventory.cpp +++ b/apps/essimporter/importinventory.cpp @@ -21,13 +21,18 @@ namespace ESSImport item.mCount = contItem.mCount; item.mRelativeEquipmentSlot = -1; - // seems that a stack of items can have a set of subrecords for each item? rings0000.ess - // doesn't make any sense to me, if the values were different then the items shouldn't stack in the first place? - // I guess we should double check the stacking logic in OpenMW - for (int i=0;i mKnownDialogueTopics; - enum DrawState_ + enum PlayerFlags { - DrawState_Weapon = 0x80, - DrawState_Spell = 0x100 - }; - enum CameraState - { - CameraState_FirstPerson = 0x8, - CameraState_ThirdPerson = 0xa + PlayerFlags_ViewSwitchDisabled = 0x1, + PlayerFlags_ControlsDisabled = 0x4, + PlayerFlags_Sleeping = 0x10, + PlayerFlags_Waiting = 0x40, + PlayerFlags_WeaponDrawn = 0x80, + PlayerFlags_SpellDrawn = 0x100, + PlayerFlags_InJail = 0x200, + PlayerFlags_JumpingDisabled = 0x1000, + PlayerFlags_LookingDisabled = 0x2000, + PlayerFlags_VanityModeDisabled = 0x4000, + PlayerFlags_WeaponDrawingDisabled = 0x8000, + PlayerFlags_SpellDrawingDisabled = 0x10000, + PlayerFlags_ThirdPerson = 0x20000, + PlayerFlags_TeleportingDisabled = 0x40000, + PlayerFlags_LevitationDisabled = 0x80000 }; #pragma pack(push) @@ -63,18 +71,53 @@ struct PCDT struct PNAM { - short mDrawState; // DrawState - short mCameraState; // CameraState + struct MarkLocation + { + float mX, mY, mZ; // worldspace position + float mRotZ; // Z angle in radians + int mCellX, mCellY; // grid coordinates; for interior cells this is always (0, 0) + }; + + int mPlayerFlags; // controls, camera and draw state unsigned int mLevelProgress; float mSkillProgress[27]; // skill progress, non-uniform scaled unsigned char mSkillIncreases[8]; // number of skill increases for each attribute - unsigned char mUnknown3[88]; + int mTelekinesisRangeBonus; // in units; seems redundant + float mVisionBonus; // range: <0.0, 1.0>; affected by light spells and Get/Mod/SetPCVisionBonus + int mDetectKeyMagnitude; // seems redundant + int mDetectEnchantmentMagnitude; // seems redundant + int mDetectAnimalMagnitude; // seems redundant + MarkLocation mMarkLocation; + unsigned char mUnknown3[40]; + unsigned char mSpecIncreases[3]; // number of skill increases for each specialization + unsigned char mUnknown4; + }; + + struct ENAM + { + int mCellX; + int mCellY; + }; + + struct AADT // 44 bytes + { + int animGroupIndex; // See convertANIS() for the mapping. + unsigned char mUnknown5[40]; }; #pragma pack(pop) std::vector mFactions; PNAM mPNAM; + bool mHasMark; + std::string mMNAM; // mark cell name; can also be sDefaultCellname or region name + + bool mHasENAM; + ENAM mENAM; // last exterior cell + + bool mHasAADT; + AADT mAADT; + void load(ESM::ESMReader& esm); }; diff --git a/apps/essimporter/importscri.hpp b/apps/essimporter/importscri.hpp index 5123f84aa..fe68e5051 100644 --- a/apps/essimporter/importscri.hpp +++ b/apps/essimporter/importscri.hpp @@ -13,7 +13,7 @@ namespace ESM namespace ESSImport { - /// Local variable assigments for a running script + /// Local variable assignments for a running script struct SCRI { std::string mScript; diff --git a/apps/launcher/CMakeLists.txt b/apps/launcher/CMakeLists.txt index 207f6a84b..8cbe18d51 100644 --- a/apps/launcher/CMakeLists.txt +++ b/apps/launcher/CMakeLists.txt @@ -87,6 +87,10 @@ add_executable(openmw-launcher ${UI_HDRS} ) +if (WIN32) + INSTALL(TARGETS openmw-launcher RUNTIME DESTINATION ".") +endif (WIN32) + target_link_libraries(openmw-launcher ${SDL2_LIBRARY_ONLY} components diff --git a/apps/launcher/main.cpp b/apps/launcher/main.cpp index eadec64d0..96cadc8a7 100644 --- a/apps/launcher/main.cpp +++ b/apps/launcher/main.cpp @@ -35,14 +35,6 @@ int main(int argc, char *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(); - } - #endif - QDir::setCurrent(dir.absolutePath()); Launcher::MainDialog mainWin; diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index 60ae5b3a0..1c1d57016 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -293,6 +293,7 @@ bool Launcher::MainDialog::setupGameSettings() { mGameSettings.clear(); + QString localPath = QString::fromUtf8(mCfgMgr.getLocalPath().string().c_str()); QString userPath = QString::fromUtf8(mCfgMgr.getUserConfigPath().string().c_str()); QString globalPath = QString::fromUtf8(mCfgMgr.getGlobalPath().string().c_str()); @@ -320,13 +321,13 @@ bool Launcher::MainDialog::setupGameSettings() // Now the rest - priority: user > local > global QStringList paths; paths.append(globalPath + QString("openmw.cfg")); - paths.append(QString("openmw.cfg")); + paths.append(localPath + QString("openmw.cfg")); paths.append(userPath + QString("openmw.cfg")); - foreach (const QString &path, paths) { - qDebug() << "Loading config file:" << path.toUtf8().constData(); + foreach (const QString &path2, paths) { + qDebug() << "Loading config file:" << path2.toUtf8().constData(); - QFile file(path); + file.setFileName(path2); if (file.exists()) { if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { cfgError(tr("Error opening OpenMW configuration file"), @@ -346,13 +347,13 @@ bool Launcher::MainDialog::setupGameSettings() QStringList dataDirs; // Check if the paths actually contain data files - foreach (const QString path, mGameSettings.getDataDirs()) { - QDir dir(path); + foreach (const QString path3, mGameSettings.getDataDirs()) { + QDir dir(path3); QStringList filters; filters << "*.esp" << "*.esm" << "*.omwgame" << "*.omwaddon"; if (!dir.entryList(filters).isEmpty()) - dataDirs.append(path); + dataDirs.append(path3); } if (dataDirs.isEmpty()) @@ -579,6 +580,6 @@ void Launcher::MainDialog::play() // Launch the game detached - if (mGameInvoker->startProcess(QLatin1String("openmw"), true)) + if (mGameInvoker->startProcess(QLatin1String("tes3mp-browser"), true)) return qApp->quit(); } diff --git a/apps/mwiniimporter/CMakeLists.txt b/apps/mwiniimporter/CMakeLists.txt index 4024c0b42..4bd661685 100644 --- a/apps/mwiniimporter/CMakeLists.txt +++ b/apps/mwiniimporter/CMakeLists.txt @@ -22,7 +22,8 @@ target_link_libraries(openmw-iniimporter if (WIN32) target_link_libraries(openmw-iniimporter ${Boost_LOCALE_LIBRARY}) -endif() + INSTALL(TARGETS openmw-iniimporter RUNTIME DESTINATION ".") +endif(WIN32) if (MINGW) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -municode") diff --git a/apps/mwiniimporter/importer.cpp b/apps/mwiniimporter/importer.cpp index f2fcdb711..fb3144814 100644 --- a/apps/mwiniimporter/importer.cpp +++ b/apps/mwiniimporter/importer.cpp @@ -824,8 +824,8 @@ void MwIniImporter::importArchives(multistrmap &cfg, const multistrmap &ini) con // does not appears in the ini file cfg["fallback-archive"].push_back("Morrowind.bsa"); - for(std::vector::const_iterator it=archives.begin(); it!=archives.end(); ++it) { - cfg["fallback-archive"].push_back(*it); + for(std::vector::const_iterator iter=archives.begin(); iter!=archives.end(); ++iter) { + cfg["fallback-archive"].push_back(*iter); } } @@ -865,8 +865,8 @@ void MwIniImporter::importGameFiles(multistrmap &cfg, const multistrmap &ini, co // this will sort files by time order first, then alphabetical (maybe), I suspect non ASCII filenames will be stuffed. sort(contentFiles.begin(), contentFiles.end()); - for(std::vector >::const_iterator it=contentFiles.begin(); it!=contentFiles.end(); ++it) { - cfg["content"].push_back(it->second); + for(std::vector >::const_iterator iter=contentFiles.begin(); iter!=contentFiles.end(); ++iter) { + cfg["content"].push_back(iter->second); } } diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 0bde541bf..e7e19be94 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -26,7 +26,7 @@ opencs_units_noqt (model/world universalid record commands columnbase columnimp scriptcontext cell refidcollection refidadapter refiddata refidadapterimp ref collectionbase refcollection columns infocollection tablemimedata cellcoordinates cellselection resources resourcesmanager scope pathgrid landtexture land nestedtablewrapper nestedcollection nestedcoladapterimp nestedinfocollection - idcompletionmanager metadata defaultgmsts + idcompletionmanager metadata defaultgmsts infoselectwrapper commandmacro ) opencs_hdrs_noqt (model/world @@ -35,14 +35,14 @@ opencs_hdrs_noqt (model/world opencs_units (model/tools - tools reportmodel mergeoperation + tools reportmodel mergeoperation ) opencs_units_noqt (model/tools mandatoryid skillcheck classcheck factioncheck racecheck soundcheck regioncheck birthsigncheck spellcheck referencecheck referenceablecheck scriptcheck bodypartcheck startscriptcheck search searchoperation searchstage pathgridcheck soundgencheck magiceffectcheck - mergestages gmstcheck + mergestages gmstcheck topicinfocheck journalcheck ) opencs_hdrs_noqt (model/tools @@ -66,8 +66,8 @@ opencs_hdrs_noqt (view/doc opencs_units (view/world - table tablesubview scriptsubview util regionmapsubview tablebottombox creator genericcreator - cellcreator referenceablecreator startscriptcreator referencecreator scenesubview + table tablesubview scriptsubview util regionmapsubview tablebottombox creator genericcreator globalcreator + cellcreator pathgridcreator referenceablecreator startscriptcreator referencecreator scenesubview infocreator scriptedit dialoguesubview previewsubview regionmap dragrecordtable nestedtable dialoguespinbox recordbuttonbar tableeditidaction scripterrortable extendedcommandconfigurator ) @@ -85,12 +85,14 @@ opencs_units (view/widget opencs_units (view/render scenewidget worldspacewidget pagedworldspacewidget unpagedworldspacewidget - previewwidget editmode instancemode instanceselectionmode + previewwidget editmode instancemode instanceselectionmode instancemovemode + orbitcameramode pathgridmode selectionmode pathgridselectionmode cameracontroller + cellwater ) opencs_units_noqt (view/render - lighting lightingday lightingnight - lightingbright object cell terrainstorage tagbase cellarrow + lighting lightingday lightingnight lightingbright object cell terrainstorage tagbase + cellarrow cellmarker cellborder pathgrid ) opencs_hdrs_noqt (view/render @@ -107,11 +109,12 @@ opencs_units_noqt (view/tools ) opencs_units (view/prefs - dialogue pagebase page + dialogue pagebase page keybindingpage ) opencs_units (model/prefs - state setting intsetting doublesetting boolsetting enumsetting coloursetting + state setting intsetting doublesetting boolsetting enumsetting coloursetting shortcut + shortcuteventhandler shortcutmanager shortcutsetting modifiersetting ) opencs_units_noqt (model/prefs @@ -159,9 +162,15 @@ endif() include_directories(${CMAKE_CURRENT_BINARY_DIR}) if(APPLE) - set (OPENCS_MAC_ICON ${CMAKE_SOURCE_DIR}/files/mac/openmw-cs.icns) + set (OPENCS_MAC_ICON "${CMAKE_SOURCE_DIR}/files/mac/openmw-cs.icns") + set (OPENCS_CFG "${OpenMW_BINARY_DIR}/openmw-cs.cfg") + set (OPENCS_DEFAULT_FILTERS_FILE "${OpenMW_BINARY_DIR}/resources/defaultfilters") + set (OPENCS_OPENMW_CFG "${OpenMW_BINARY_DIR}/openmw.cfg") else() set (OPENCS_MAC_ICON "") + set (OPENCS_CFG "") + set (OPENCS_DEFAULT_FILTERS_FILE "") + set (OPENCS_OPENMW_CFG "") endif(APPLE) add_executable(openmw-cs @@ -171,12 +180,23 @@ add_executable(openmw-cs ${OPENCS_MOC_SRC} ${OPENCS_RES_SRC} ${OPENCS_MAC_ICON} + ${OPENCS_CFG} + ${OPENCS_DEFAULT_FILTERS_FILE} + ${OPENCS_OPENMW_CFG} ) if(APPLE) + set(OPENCS_BUNDLE_NAME "OpenMW-CS") + set(OPENCS_BUNDLE_RESOURCES_DIR "${OpenMW_BINARY_DIR}/${OPENCS_BUNDLE_NAME}.app/Contents/Resources") + + set(OPENMW_MYGUI_FILES_ROOT ${OPENCS_BUNDLE_RESOURCES_DIR}) + set(OPENMW_SHADERS_ROOT ${OPENCS_BUNDLE_RESOURCES_DIR}) + + add_subdirectory(../../files/ ${CMAKE_CURRENT_BINARY_DIR}/files) + set_target_properties(openmw-cs PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${OpenMW_BINARY_DIR}" - OUTPUT_NAME "OpenMW-CS" + OUTPUT_NAME ${OPENCS_BUNDLE_NAME} MACOSX_BUNDLE_ICON_FILE "openmw-cs.icns" MACOSX_BUNDLE_BUNDLE_NAME "OpenCS" MACOSX_BUNDLE_GUI_IDENTIFIER "org.openmw.opencs" @@ -187,16 +207,27 @@ if(APPLE) set_source_files_properties(${OPENCS_MAC_ICON} PROPERTIES MACOSX_PACKAGE_LOCATION Resources) + set_source_files_properties(${OPENCS_CFG} PROPERTIES + MACOSX_PACKAGE_LOCATION Resources) + set_source_files_properties(${OPENCS_DEFAULT_FILTERS_FILE} PROPERTIES + MACOSX_PACKAGE_LOCATION Resources/resources) + set_source_files_properties(${OPENCS_OPENMW_CFG} PROPERTIES + MACOSX_PACKAGE_LOCATION Resources) + + add_custom_command(TARGET openmw-cs + POST_BUILD + COMMAND cp "${OpenMW_BINARY_DIR}/resources/version" "${OPENCS_BUNDLE_RESOURCES_DIR}/resources") endif(APPLE) target_link_libraries(openmw-cs ${OSG_LIBRARIES} ${OPENTHREADS_LIBRARIES} + ${OSGTEXT_LIBRARIES} ${OSGUTIL_LIBRARIES} ${OSGVIEWER_LIBRARIES} ${OSGGA_LIBRARIES} ${OSGFX_LIBRARIES} - ${OSGQT_LIBRARIES} + ${EXTERN_OSGQT_LIBRARY} ${Boost_SYSTEM_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} @@ -219,9 +250,18 @@ endif() if (WIN32) target_link_libraries(openmw-cs ${Boost_LOCALE_LIBRARY}) + INSTALL(TARGETS openmw-cs RUNTIME DESTINATION ".") + INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw-cs.cfg" DESTINATION ".") endif() +if (MSVC) + # Debug version needs increased number of sections beyond 2^16 + if (CMAKE_CL_64) + set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /bigobj") + endif (CMAKE_CL_64) +endif (MSVC) + if(APPLE) - INSTALL(TARGETS openmw-cs BUNDLE DESTINATION OpenMW COMPONENT BUNDLE) + INSTALL(TARGETS openmw-cs BUNDLE DESTINATION "." COMPONENT BUNDLE) endif() diff --git a/apps/opencs/Networking.cpp b/apps/opencs/Networking.cpp new file mode 100644 index 000000000..c6fe34835 --- /dev/null +++ b/apps/opencs/Networking.cpp @@ -0,0 +1,90 @@ +#include "editor.hpp" + +#include +#include +#include + +#include +#include +#include + +#include "model/doc/messages.hpp" + +#include "model/world/universalid.hpp" + +#ifdef Q_OS_MAC +#include +#endif + +Q_DECLARE_METATYPE (std::string) + +class Application : public QApplication +{ + private: + + bool notify (QObject *receiver, QEvent *event) + { + try + { + return QApplication::notify (receiver, event); + } + catch (const std::exception& exception) + { + std::cerr << "An exception has been caught: " << exception.what() << std::endl; + } + + return false; + } + + public: + + Application (int& argc, char *argv[]) : QApplication (argc, argv) {} +}; + +int main(int argc, char *argv[]) +{ + #ifdef Q_OS_MAC + setenv("OSG_GL_TEXTURE_STORAGE", "OFF", 0); + #endif + + try + { + // To allow background thread drawing in OSG + QApplication::setAttribute(Qt::AA_X11InitThreads, true); + + Q_INIT_RESOURCE (resources); + + qRegisterMetaType ("std::string"); + qRegisterMetaType ("CSMWorld::UniversalId"); + qRegisterMetaType ("CSMDoc::Message"); + + Application application (argc, argv); + + #ifdef Q_OS_MAC + QDir dir(QCoreApplication::applicationDirPath()); + if (dir.dirName() == "MacOS") { + dir.cdUp(); + dir.cdUp(); + dir.cdUp(); + } + QDir::setCurrent(dir.absolutePath()); + #endif + + application.setWindowIcon (QIcon (":./openmw-cs.png")); + + CS::Editor editor; + + if(!editor.makeIPCServer()) + { + editor.connectToIPCServer(); + return 0; + } + return editor.run(); + } + catch (std::exception& e) + { + std::cerr << "ERROR: " << e.what() << std::endl; + return 0; + } + +} diff --git a/apps/opencs/main.cpp b/apps/opencs/main.cpp index c6fe34835..fc5e8fc7a 100644 --- a/apps/opencs/main.cpp +++ b/apps/opencs/main.cpp @@ -62,11 +62,6 @@ int main(int argc, char *argv[]) #ifdef Q_OS_MAC QDir dir(QCoreApplication::applicationDirPath()); - if (dir.dirName() == "MacOS") { - dir.cdUp(); - dir.cdUp(); - dir.cdUp(); - } QDir::setCurrent(dir.absolutePath()); #endif diff --git a/apps/opencs/model/doc/document.cpp b/apps/opencs/model/doc/document.cpp index 19891363f..87fbccb3f 100644 --- a/apps/opencs/model/doc/document.cpp +++ b/apps/opencs/model/doc/document.cpp @@ -274,7 +274,7 @@ CSMDoc::Document::Document (const VFS::Manager* vfs, const Files::ConfigurationM const Fallback::Map* fallback, ToUTF8::FromType encoding, const CSMWorld::ResourcesManager& resourcesManager, const std::vector& blacklistedScripts) -: mVFS(vfs), mSavePath (savePath), mContentFiles (files), mNew (new_), mData (encoding, resourcesManager, fallback), +: mVFS(vfs), mSavePath (savePath), mContentFiles (files), mNew (new_), mData (encoding, resourcesManager, fallback, resDir), mTools (*this, encoding), mProjectPath ((configuration.getUserDataPath() / "projects") / (savePath.filename().string() + ".project")), diff --git a/apps/opencs/model/doc/documentmanager.cpp b/apps/opencs/model/doc/documentmanager.cpp index cbb255ebb..87fb960c1 100644 --- a/apps/opencs/model/doc/documentmanager.cpp +++ b/apps/opencs/model/doc/documentmanager.cpp @@ -41,6 +41,7 @@ CSMDoc::DocumentManager::DocumentManager (const Files::ConfigurationManager& con CSMDoc::DocumentManager::~DocumentManager() { mLoaderThread.quit(); + mLoader.stop(); mLoader.hasThingsToDo().wakeAll(); mLoaderThread.wait(); diff --git a/apps/opencs/model/doc/loader.cpp b/apps/opencs/model/doc/loader.cpp index 166e6f3db..44d6883ef 100644 --- a/apps/opencs/model/doc/loader.cpp +++ b/apps/opencs/model/doc/loader.cpp @@ -1,6 +1,6 @@ #include "loader.hpp" -#include +#include #include "../tools/reportmodel.hpp" @@ -11,11 +11,12 @@ CSMDoc::Loader::Stage::Stage() : mFile (0), mRecordsLoaded (0), mRecordsLeft (fa CSMDoc::Loader::Loader() + : mShouldStop(false) { - QTimer *timer = new QTimer (this); + mTimer = new QTimer (this); - connect (timer, SIGNAL (timeout()), this, SLOT (load())); - timer->start(); + connect (mTimer, SIGNAL (timeout()), this, SLOT (load())); + mTimer->start(); } QWaitCondition& CSMDoc::Loader::hasThingsToDo() @@ -23,6 +24,11 @@ QWaitCondition& CSMDoc::Loader::hasThingsToDo() return mThingsToDo; } +void CSMDoc::Loader::stop() +{ + mShouldStop = true; +} + void CSMDoc::Loader::load() { if (mDocuments.empty()) @@ -30,6 +36,10 @@ void CSMDoc::Loader::load() mMutex.lock(); mThingsToDo.wait (&mMutex); mMutex.unlock(); + + if (mShouldStop) + mTimer->stop(); + return; } @@ -63,11 +73,11 @@ void CSMDoc::Loader::load() CSMWorld::UniversalId log (CSMWorld::UniversalId::Type_LoadErrorLog, 0); { // silence a g++ warning - for (CSMDoc::Messages::Iterator iter (messages.begin()); - iter!=messages.end(); ++iter) + for (CSMDoc::Messages::Iterator messageIter (messages.begin()); + messageIter!=messages.end(); ++messageIter) { - document->getReport (log)->add (*iter); - emit loadMessage (document, iter->mMessage); + document->getReport (log)->add (*messageIter); + emit loadMessage (document, messageIter->mMessage); } } diff --git a/apps/opencs/model/doc/loader.hpp b/apps/opencs/model/doc/loader.hpp index d1ee38f9f..ce5bc5848 100644 --- a/apps/opencs/model/doc/loader.hpp +++ b/apps/opencs/model/doc/loader.hpp @@ -5,6 +5,7 @@ #include #include +#include #include namespace CSMDoc @@ -28,12 +29,17 @@ namespace CSMDoc QWaitCondition mThingsToDo; std::vector > mDocuments; + QTimer* mTimer; + bool mShouldStop; + public: Loader(); QWaitCondition& hasThingsToDo(); + void stop(); + private slots: void load(); diff --git a/apps/opencs/model/doc/runner.cpp b/apps/opencs/model/doc/runner.cpp index 5a0bc39be..84bc61a9a 100644 --- a/apps/opencs/model/doc/runner.cpp +++ b/apps/opencs/model/doc/runner.cpp @@ -81,7 +81,7 @@ void CSMDoc::Runner::start (bool delayed) arguments << ("--script-run="+mStartup->fileName());; arguments << - QString::fromUtf8 (("--data="+mProjectPath.parent_path().string()).c_str()); + QString::fromUtf8 (("--data=\""+mProjectPath.parent_path().string()+"\"").c_str()); for (std::vector::const_iterator iter (mContentFiles.begin()); iter!=mContentFiles.end(); ++iter) diff --git a/apps/opencs/model/doc/savingstages.cpp b/apps/opencs/model/doc/savingstages.cpp index af9c380a3..82a965ae4 100644 --- a/apps/opencs/model/doc/savingstages.cpp +++ b/apps/opencs/model/doc/savingstages.cpp @@ -11,6 +11,7 @@ #include #include "../world/infocollection.hpp" +#include "../world/cellcoordinates.hpp" #include "document.hpp" #include "savingstate.hpp" @@ -238,7 +239,7 @@ void CSMDoc::CollectionReferencesStage::perform (int stage, Messages& messages) // An empty mOriginalCell is meant to indicate that it is the same as // the current cell. It is possible that a moved ref is moved again. if ((record.get().mOriginalCell.empty() ? - record.get().mCell : record.get().mOriginalCell) != stream.str() && !interior) + record.get().mCell : record.get().mOriginalCell) != stream.str() && !interior && record.mState!=CSMWorld::RecordBase::State_ModifiedOnly && !record.get().mNew) indices.push_back (i); else indices.push_front (i); @@ -265,13 +266,32 @@ void CSMDoc::WriteCellCollectionStage::perform (int stage, Messages& messages) std::map >::const_iterator references = mState.getSubRecords().find (Misc::StringUtils::lowerCase (cell.get().mId)); - if (cell.isModified() || + if (cell.isModified() || cell.mState == CSMWorld::RecordBase::State_Deleted || references!=mState.getSubRecords().end()) { CSMWorld::Cell cellRecord = cell.get(); bool interior = cellRecord.mId.substr (0, 1)!="#"; + // count new references and adjust RefNumCount accordingsly + int newRefNum = cellRecord.mRefNumCounter; + + if (references!=mState.getSubRecords().end()) + { + for (std::deque::const_iterator iter (references->second.begin()); + iter!=references->second.end(); ++iter) + { + const CSMWorld::Record& ref = + mDocument.getData().getReferences().getRecord (*iter); + + if (ref.get().mNew || + (!interior && ref.mState==CSMWorld::RecordBase::State_ModifiedOnly && + /// \todo consider worldspace + CSMWorld::CellCoordinates (ref.get().getCellIndex()).getId("")!=ref.get().mCell)) + ++cellRecord.mRefNumCounter; + } + } + // write cell data writer.startRecord (cellRecord.sRecordId); @@ -301,6 +321,10 @@ void CSMDoc::WriteCellCollectionStage::perform (int stage, Messages& messages) { CSMWorld::CellRef refRecord = ref.get(); + // Check for uninitialized content file + if (!refRecord.mRefNum.hasContentFile()) + refRecord.mRefNum.mContentFile = 0; + // recalculate the ref's cell location std::ostringstream stream; if (!interior) @@ -309,11 +333,18 @@ void CSMDoc::WriteCellCollectionStage::perform (int stage, Messages& messages) stream << "#" << index.first << " " << index.second; } - // An empty mOriginalCell is meant to indicate that it is the same as - // the current cell. It is possible that a moved ref is moved again. - if ((refRecord.mOriginalCell.empty() ? refRecord.mCell : refRecord.mOriginalCell) + if (refRecord.mNew || + (!interior && ref.mState==CSMWorld::RecordBase::State_ModifiedOnly && + refRecord.mCell!=stream.str())) + { + refRecord.mRefNum.mIndex = newRefNum++; + } + else if ((refRecord.mOriginalCell.empty() ? refRecord.mCell : refRecord.mOriginalCell) != stream.str() && !interior) { + // An empty mOriginalCell is meant to indicate that it is the same as + // the current cell. It is possible that a moved ref is moved again. + ESM::MovedCellRef moved; moved.mRefNum = refRecord.mRefNum; @@ -350,7 +381,7 @@ int CSMDoc::WritePathgridCollectionStage::setup() void CSMDoc::WritePathgridCollectionStage::perform (int stage, Messages& messages) { ESM::ESMWriter& writer = mState.getWriter(); - const CSMWorld::Record& pathgrid = + const CSMWorld::Record& pathgrid = mDocument.getData().getPathgrids().getRecord (stage); if (pathgrid.isModified() || pathgrid.mState == CSMWorld::RecordBase::State_Deleted) @@ -386,7 +417,7 @@ int CSMDoc::WriteLandCollectionStage::setup() void CSMDoc::WriteLandCollectionStage::perform (int stage, Messages& messages) { ESM::ESMWriter& writer = mState.getWriter(); - const CSMWorld::Record& land = + const CSMWorld::Record& land = mDocument.getData().getLand().getRecord (stage); if (land.isModified() || land.mState == CSMWorld::RecordBase::State_Deleted) @@ -412,7 +443,7 @@ int CSMDoc::WriteLandTextureCollectionStage::setup() void CSMDoc::WriteLandTextureCollectionStage::perform (int stage, Messages& messages) { ESM::ESMWriter& writer = mState.getWriter(); - const CSMWorld::Record& landTexture = + const CSMWorld::Record& landTexture = mDocument.getData().getLandTextures().getRecord (stage); if (landTexture.isModified() || landTexture.mState == CSMWorld::RecordBase::State_Deleted) diff --git a/apps/opencs/model/filter/parser.cpp b/apps/opencs/model/filter/parser.cpp index 7936a1ae2..c0c089169 100644 --- a/apps/opencs/model/filter/parser.cpp +++ b/apps/opencs/model/filter/parser.cpp @@ -313,7 +313,7 @@ boost::shared_ptr CSMFilter::Parser::parseNAry (const Token& ke nodes.push_back (node); - Token token = getNextToken(); + token = getNextToken(); if (!token || (token.mType!=Token::Type_Close && token.mType!=Token::Type_Comma)) { diff --git a/apps/opencs/model/prefs/doublesetting.cpp b/apps/opencs/model/prefs/doublesetting.cpp index 7c247777d..531196174 100644 --- a/apps/opencs/model/prefs/doublesetting.cpp +++ b/apps/opencs/model/prefs/doublesetting.cpp @@ -15,10 +15,16 @@ CSMPrefs::DoubleSetting::DoubleSetting (Category *parent, Settings::Manager *values, QMutex *mutex, const std::string& key, const std::string& label, double default_) : Setting (parent, values, mutex, key, label), - mMin (0), mMax (std::numeric_limits::max()), + mPrecision(2), mMin (0), mMax (std::numeric_limits::max()), mDefault (default_) {} +CSMPrefs::DoubleSetting& CSMPrefs::DoubleSetting::setPrecision(int precision) +{ + mPrecision = precision; + return *this; +} + CSMPrefs::DoubleSetting& CSMPrefs::DoubleSetting::setRange (double min, double max) { mMin = min; @@ -49,6 +55,7 @@ std::pair CSMPrefs::DoubleSetting::makeWidgets (QWidget *p QLabel *label = new QLabel (QString::fromUtf8 (getLabel().c_str()), parent); QDoubleSpinBox *widget = new QDoubleSpinBox (parent); + widget->setDecimals(mPrecision); widget->setRange (mMin, mMax); widget->setValue (mDefault); diff --git a/apps/opencs/model/prefs/doublesetting.hpp b/apps/opencs/model/prefs/doublesetting.hpp index 3868f014e..8fd345f4d 100644 --- a/apps/opencs/model/prefs/doublesetting.hpp +++ b/apps/opencs/model/prefs/doublesetting.hpp @@ -9,6 +9,7 @@ namespace CSMPrefs { Q_OBJECT + int mPrecision; double mMin; double mMax; std::string mTooltip; @@ -20,6 +21,8 @@ namespace CSMPrefs QMutex *mutex, const std::string& key, const std::string& label, double default_); + DoubleSetting& setPrecision (int precision); + // defaults to [0, std::numeric_limits::max()] DoubleSetting& setRange (double min, double max); diff --git a/apps/opencs/model/prefs/modifiersetting.cpp b/apps/opencs/model/prefs/modifiersetting.cpp new file mode 100644 index 000000000..4f4fac248 --- /dev/null +++ b/apps/opencs/model/prefs/modifiersetting.cpp @@ -0,0 +1,147 @@ +#include "modifiersetting.hpp" + +#include +#include +#include +#include +#include +#include + +#include "state.hpp" +#include "shortcutmanager.hpp" + +namespace CSMPrefs +{ + ModifierSetting::ModifierSetting(Category* parent, Settings::Manager* values, QMutex* mutex, const std::string& key, + const std::string& label) + : Setting(parent, values, mutex, key, label) + , mButton(0) + , mEditorActive(false) + { + } + + std::pair ModifierSetting::makeWidgets(QWidget* parent) + { + int modifier = 0; + State::get().getShortcutManager().getModifier(getKey(), modifier); + + QString text = QString::fromUtf8(State::get().getShortcutManager().convertToString(modifier).c_str()); + + QLabel* label = new QLabel(QString::fromUtf8(getLabel().c_str()), parent); + QPushButton* widget = new QPushButton(text, parent); + + widget->setCheckable(true); + widget->installEventFilter(this); + mButton = widget; + + connect(widget, SIGNAL(toggled(bool)), this, SLOT(buttonToggled(bool))); + + return std::make_pair(label, widget); + } + + bool ModifierSetting::eventFilter(QObject* target, QEvent* event) + { + if (event->type() == QEvent::KeyPress) + { + QKeyEvent* keyEvent = static_cast(event); + if (keyEvent->isAutoRepeat()) + return true; + + int mod = keyEvent->modifiers(); + int key = keyEvent->key(); + + return handleEvent(target, mod, key); + } + else if (event->type() == QEvent::MouseButtonPress) + { + QMouseEvent* mouseEvent = static_cast(event); + int mod = mouseEvent->modifiers(); + int button = mouseEvent->button(); + + return handleEvent(target, mod, button); + } + else if (event->type() == QEvent::FocusOut) + { + resetState(); + } + + return false; + } + + bool ModifierSetting::handleEvent(QObject* target, int mod, int value) + { + // For potential future exceptions + const int Blacklist[] = + { + 0 + }; + + const size_t BlacklistSize = sizeof(Blacklist) / sizeof(int); + + if (!mEditorActive) + { + if (value == Qt::RightButton) + { + // Clear modifier + int modifier = 0; + storeValue(modifier); + + resetState(); + } + + return false; + } + + // Handle blacklist + for (size_t i = 0; i < BlacklistSize; ++i) + { + if (value == Blacklist[i]) + return true; + } + + + // Update modifier + int modifier = value; + storeValue(modifier); + + resetState(); + + return true; + } + + void ModifierSetting::storeValue(int modifier) + { + State::get().getShortcutManager().setModifier(getKey(), modifier); + + // Convert to string and assign + std::string value = State::get().getShortcutManager().convertToString(modifier); + + { + QMutexLocker lock(getMutex()); + getValues().setString(getKey(), getParent()->getKey(), value); + } + + getParent()->getState()->update(*this); + } + + void ModifierSetting::resetState() + { + mButton->setChecked(false); + mEditorActive = false; + + // Button text + int modifier = 0; + State::get().getShortcutManager().getModifier(getKey(), modifier); + + QString text = QString::fromUtf8(State::get().getShortcutManager().convertToString(modifier).c_str()); + mButton->setText(text); + } + + void ModifierSetting::buttonToggled(bool checked) + { + if (checked) + mButton->setText("Press keys or click here..."); + + mEditorActive = checked; + } +} diff --git a/apps/opencs/model/prefs/modifiersetting.hpp b/apps/opencs/model/prefs/modifiersetting.hpp new file mode 100644 index 000000000..95983f66d --- /dev/null +++ b/apps/opencs/model/prefs/modifiersetting.hpp @@ -0,0 +1,44 @@ +#ifndef CSM_PREFS_MODIFIERSETTING_H +#define CSM_PREFS_MODIFIERSETTING_H + +#include + +#include "setting.hpp" + +class QEvent; +class QPushButton; + +namespace CSMPrefs +{ + class ModifierSetting : public Setting + { + Q_OBJECT + + public: + + ModifierSetting(Category* parent, Settings::Manager* values, QMutex* mutex, const std::string& key, + const std::string& label); + + virtual std::pair makeWidgets(QWidget* parent); + + protected: + + bool eventFilter(QObject* target, QEvent* event); + + private: + + bool handleEvent(QObject* target, int mod, int value); + + void storeValue(int modifier); + void resetState(); + + QPushButton* mButton; + bool mEditorActive; + + private slots: + + void buttonToggled(bool checked); + }; +} + +#endif diff --git a/apps/opencs/model/prefs/shortcut.cpp b/apps/opencs/model/prefs/shortcut.cpp new file mode 100644 index 000000000..27077ac83 --- /dev/null +++ b/apps/opencs/model/prefs/shortcut.cpp @@ -0,0 +1,214 @@ +#include "shortcut.hpp" + +#include + +#include +#include + +#include "state.hpp" +#include "shortcutmanager.hpp" + +namespace CSMPrefs +{ + Shortcut::Shortcut(const std::string& name, QWidget* parent) + : QObject(parent) + , mEnabled(true) + , mName(name) + , mModName("") + , mSecondaryMode(SM_Ignore) + , mModifier(0) + , mCurrentPos(0) + , mLastPos(0) + , mActivationStatus(AS_Inactive) + , mModifierStatus(false) + , mAction(0) + { + assert (parent); + + State::get().getShortcutManager().addShortcut(this); + State::get().getShortcutManager().getSequence(name, mSequence); + } + + Shortcut::Shortcut(const std::string& name, const std::string& modName, QWidget* parent) + : QObject(parent) + , mEnabled(true) + , mName(name) + , mModName(modName) + , mSecondaryMode(SM_Ignore) + , mModifier(0) + , mCurrentPos(0) + , mLastPos(0) + , mActivationStatus(AS_Inactive) + , mModifierStatus(false) + , mAction(0) + { + assert (parent); + + State::get().getShortcutManager().addShortcut(this); + State::get().getShortcutManager().getSequence(name, mSequence); + State::get().getShortcutManager().getModifier(modName, mModifier); + } + + Shortcut::Shortcut(const std::string& name, const std::string& modName, SecondaryMode secMode, QWidget* parent) + : QObject(parent) + , mEnabled(true) + , mName(name) + , mModName(modName) + , mSecondaryMode(secMode) + , mModifier(0) + , mCurrentPos(0) + , mLastPos(0) + , mActivationStatus(AS_Inactive) + , mModifierStatus(false) + , mAction(0) + { + assert (parent); + + State::get().getShortcutManager().addShortcut(this); + State::get().getShortcutManager().getSequence(name, mSequence); + State::get().getShortcutManager().getModifier(modName, mModifier); + } + + Shortcut::~Shortcut() + { + State::get().getShortcutManager().removeShortcut(this); + } + + bool Shortcut::isEnabled() const + { + return mEnabled; + } + + const std::string& Shortcut::getName() const + { + return mName; + } + + const std::string& Shortcut::getModifierName() const + { + return mModName; + } + + Shortcut::SecondaryMode Shortcut::getSecondaryMode() const + { + return mSecondaryMode; + } + + const QKeySequence& Shortcut::getSequence() const + { + return mSequence; + } + + int Shortcut::getModifier() const + { + return mModifier; + } + + int Shortcut::getPosition() const + { + return mCurrentPos; + } + + int Shortcut::getLastPosition() const + { + return mLastPos; + } + + Shortcut::ActivationStatus Shortcut::getActivationStatus() const + { + return mActivationStatus; + } + + bool Shortcut::getModifierStatus() const + { + return mModifierStatus; + } + + void Shortcut::enable(bool state) + { + mEnabled = state; + } + + void Shortcut::setSequence(const QKeySequence& sequence) + { + mSequence = sequence; + mCurrentPos = 0; + mLastPos = sequence.count() - 1; + + if (mAction) + { + mAction->setText(mActionText + "\t" + State::get().getShortcutManager().convertToString(mSequence).data()); + } + } + + void Shortcut::setModifier(int modifier) + { + mModifier = modifier; + } + + void Shortcut::setPosition(int pos) + { + mCurrentPos = pos; + } + + void Shortcut::setActivationStatus(ActivationStatus status) + { + mActivationStatus = status; + } + + void Shortcut::setModifierStatus(bool status) + { + mModifierStatus = status; + } + + void Shortcut::associateAction(QAction* action) + { + if (mAction) + { + mAction->setText(mActionText); + + disconnect(this, SIGNAL(activated()), mAction, SLOT(trigger())); + disconnect(mAction, SIGNAL(destroyed()), this, SLOT(actionDeleted())); + } + + mAction = action; + + if (mAction) + { + mActionText = mAction->text(); + mAction->setText(mActionText + "\t" + State::get().getShortcutManager().convertToString(mSequence).data()); + + connect(this, SIGNAL(activated()), mAction, SLOT(trigger())); + connect(mAction, SIGNAL(destroyed()), this, SLOT(actionDeleted())); + } + } + + void Shortcut::signalActivated(bool state) + { + emit activated(state); + } + + void Shortcut::signalActivated() + { + emit activated(); + } + + void Shortcut::signalSecondary(bool state) + { + emit secondary(state); + } + void Shortcut::signalSecondary() + { + emit secondary(); + } + + QString Shortcut::toString() const + { + return QString(State::get().getShortcutManager().convertToString(mSequence, mModifier).data()); + } + + void Shortcut::actionDeleted() + { + mAction = 0; + } +} diff --git a/apps/opencs/model/prefs/shortcut.hpp b/apps/opencs/model/prefs/shortcut.hpp new file mode 100644 index 000000000..4fa6f8a1a --- /dev/null +++ b/apps/opencs/model/prefs/shortcut.hpp @@ -0,0 +1,122 @@ +#ifndef CSM_PREFS_SHORTCUT_H +#define CSM_PREFS_SHORTCUT_H + +#include + +#include +#include +#include + +class QAction; +class QWidget; + +namespace CSMPrefs +{ + /// A class similar in purpose to QShortcut, but with the ability to use mouse buttons + class Shortcut : public QObject + { + Q_OBJECT + + public: + + enum ActivationStatus + { + AS_Regular, + AS_Secondary, + AS_Inactive + }; + + enum SecondaryMode + { + SM_Replace, ///< The secondary signal replaces the regular signal when the modifier is active + SM_Detach, ///< The secondary signal is emitted independent of the regular signal, even if not active + SM_Ignore ///< The secondary signal will not ever be emitted + }; + + Shortcut(const std::string& name, QWidget* parent); + Shortcut(const std::string& name, const std::string& modName, QWidget* parent); + Shortcut(const std::string& name, const std::string& modName, SecondaryMode secMode, QWidget* parent); + + ~Shortcut(); + + bool isEnabled() const; + + const std::string& getName() const; + const std::string& getModifierName() const; + + SecondaryMode getSecondaryMode() const; + + const QKeySequence& getSequence() const; + int getModifier() const; + + /// The position in the sequence + int getPosition() const; + /// The position in the sequence + int getLastPosition() const; + + ActivationStatus getActivationStatus() const; + bool getModifierStatus() const; + + void enable(bool state); + + void setSequence(const QKeySequence& sequence); + void setModifier(int modifier); + + /// The position in the sequence + void setPosition(int pos); + + void setActivationStatus(ActivationStatus status); + void setModifierStatus(bool status); + + /// Appends the sequence to the QAction text, also keeps it up to date + void associateAction(QAction* action); + + // Workaround for Qt4 signals being "protected" + void signalActivated(bool state); + void signalActivated(); + + void signalSecondary(bool state); + void signalSecondary(); + + QString toString() const; + + private: + + bool mEnabled; + + std::string mName; + std::string mModName; + SecondaryMode mSecondaryMode; + QKeySequence mSequence; + int mModifier; + + int mCurrentPos; + int mLastPos; + + ActivationStatus mActivationStatus; + bool mModifierStatus; + + QAction* mAction; + QString mActionText; + + private slots: + + void actionDeleted(); + + signals: + + /// Triggered when the shortcut is activated or deactivated; can be determined from \p state + void activated(bool state); + + /// Convenience signal. + void activated(); + + /// Triggered depending on SecondaryMode + void secondary(bool state); + + /// Convenience signal. + void secondary(); + }; +} + +#endif diff --git a/apps/opencs/model/prefs/shortcuteventhandler.cpp b/apps/opencs/model/prefs/shortcuteventhandler.cpp new file mode 100644 index 000000000..93e2d85d3 --- /dev/null +++ b/apps/opencs/model/prefs/shortcuteventhandler.cpp @@ -0,0 +1,338 @@ +#include "shortcuteventhandler.hpp" + +#include +#include + +#include +#include +#include +#include + +#include "shortcut.hpp" + +namespace CSMPrefs +{ + ShortcutEventHandler::ShortcutEventHandler(QObject* parent) + : QObject(parent) + { + } + + void ShortcutEventHandler::addShortcut(Shortcut* shortcut) + { + // Enforced by shortcut class + QWidget* widget = static_cast(shortcut->parent()); + + // Check if widget setup is needed + ShortcutMap::iterator shortcutListIt = mWidgetShortcuts.find(widget); + if (shortcutListIt == mWidgetShortcuts.end()) + { + // Create list + shortcutListIt = mWidgetShortcuts.insert(std::make_pair(widget, ShortcutList())).first; + + // Check if widget has a parent with shortcuts, unfortunately it is not typically set yet + updateParent(widget); + + // Intercept widget events + widget->installEventFilter(this); + connect(widget, SIGNAL(destroyed()), this, SLOT(widgetDestroyed())); + } + + // Add to list + shortcutListIt->second.push_back(shortcut); + } + + void ShortcutEventHandler::removeShortcut(Shortcut* shortcut) + { + // Enforced by shortcut class + QWidget* widget = static_cast(shortcut->parent()); + + ShortcutMap::iterator shortcutListIt = mWidgetShortcuts.find(widget); + if (shortcutListIt != mWidgetShortcuts.end()) + { + std::remove(shortcutListIt->second.begin(), shortcutListIt->second.end(), shortcut); + } + } + + bool ShortcutEventHandler::eventFilter(QObject* watched, QEvent* event) + { + // Process event + if (event->type() == QEvent::KeyPress) + { + QWidget* widget = static_cast(watched); + QKeyEvent* keyEvent = static_cast(event); + unsigned int mod = (unsigned int) keyEvent->modifiers(); + unsigned int key = (unsigned int) keyEvent->key(); + + if (!keyEvent->isAutoRepeat()) + return activate(widget, mod, key); + } + else if (event->type() == QEvent::KeyRelease) + { + QWidget* widget = static_cast(watched); + QKeyEvent* keyEvent = static_cast(event); + unsigned int mod = (unsigned int) keyEvent->modifiers(); + unsigned int key = (unsigned int) keyEvent->key(); + + if (!keyEvent->isAutoRepeat()) + return deactivate(widget, mod, key); + } + else if (event->type() == QEvent::MouseButtonPress) + { + QWidget* widget = static_cast(watched); + QMouseEvent* mouseEvent = static_cast(event); + unsigned int mod = (unsigned int) mouseEvent->modifiers(); + unsigned int button = (unsigned int) mouseEvent->button(); + + return activate(widget, mod, button); + } + else if (event->type() == QEvent::MouseButtonRelease) + { + QWidget* widget = static_cast(watched); + QMouseEvent* mouseEvent = static_cast(event); + unsigned int mod = (unsigned int) mouseEvent->modifiers(); + unsigned int button = (unsigned int) mouseEvent->button(); + + return deactivate(widget, mod, button); + } + else if (event->type() == QEvent::FocusOut) + { + QWidget* widget = static_cast(watched); + ShortcutMap::iterator shortcutListIt = mWidgetShortcuts.find(widget); + + // Deactivate in case events are missed + for (ShortcutList::iterator it = shortcutListIt->second.begin(); it != shortcutListIt->second.end(); ++it) + { + Shortcut* shortcut = *it; + + shortcut->setPosition(0); + shortcut->setModifierStatus(false); + + if (shortcut->getActivationStatus() == Shortcut::AS_Regular) + { + shortcut->setActivationStatus(Shortcut::AS_Inactive); + shortcut->signalActivated(false); + } + else if (shortcut->getActivationStatus() == Shortcut::AS_Secondary) + { + shortcut->setActivationStatus(Shortcut::AS_Inactive); + shortcut->signalSecondary(false); + } + } + } + else if (event->type() == QEvent::FocusIn) + { + QWidget* widget = static_cast(watched); + updateParent(widget); + } + + return false; + } + + void ShortcutEventHandler::updateParent(QWidget* widget) + { + QWidget* parent = widget->parentWidget(); + while (parent) + { + ShortcutMap::iterator parentIt = mWidgetShortcuts.find(parent); + if (parentIt != mWidgetShortcuts.end()) + { + mChildParentRelations.insert(std::make_pair(widget, parent)); + updateParent(parent); + break; + } + + // Check next + parent = parent->parentWidget(); + } + } + + bool ShortcutEventHandler::activate(QWidget* widget, unsigned int mod, unsigned int button) + { + std::vector > potentials; + bool used = false; + + while (widget) + { + ShortcutMap::iterator shortcutListIt = mWidgetShortcuts.find(widget); + assert(shortcutListIt != mWidgetShortcuts.end()); + + // Find potential activations + for (ShortcutList::iterator it = shortcutListIt->second.begin(); it != shortcutListIt->second.end(); ++it) + { + Shortcut* shortcut = *it; + + if (!shortcut->isEnabled()) + continue; + + if (checkModifier(mod, button, shortcut, true)) + used = true; + + if (shortcut->getActivationStatus() != Shortcut::AS_Inactive) + continue; + + int pos = shortcut->getPosition(); + int lastPos = shortcut->getLastPosition(); + MatchResult result = match(mod, button, shortcut->getSequence()[pos]); + + if (result == Matches_WithMod || result == Matches_NoMod) + { + if (pos < lastPos && (result == Matches_WithMod || pos > 0)) + { + shortcut->setPosition(pos+1); + } + else if (pos == lastPos) + { + potentials.push_back(std::make_pair(result, shortcut)); + } + } + } + + // Move on to parent + WidgetMap::iterator widgetIt = mChildParentRelations.find(widget); + widget = (widgetIt != mChildParentRelations.end()) ? widgetIt->second : 0; + } + + // Only activate the best match; in exact conflicts, this will favor the first shortcut added. + if (!potentials.empty()) + { + std::sort(potentials.begin(), potentials.end(), ShortcutEventHandler::sort); + Shortcut* shortcut = potentials.front().second; + + if (shortcut->getModifierStatus() && shortcut->getSecondaryMode() == Shortcut::SM_Replace) + { + shortcut->setActivationStatus(Shortcut::AS_Secondary); + shortcut->signalSecondary(true); + shortcut->signalSecondary(); + } + else + { + shortcut->setActivationStatus(Shortcut::AS_Regular); + shortcut->signalActivated(true); + shortcut->signalActivated(); + } + + used = true; + } + + return used; + } + + bool ShortcutEventHandler::deactivate(QWidget* widget, unsigned int mod, unsigned int button) + { + const int KeyMask = 0x01FFFFFF; + + bool used = false; + + while (widget) + { + ShortcutMap::iterator shortcutListIt = mWidgetShortcuts.find(widget); + assert(shortcutListIt != mWidgetShortcuts.end()); + + for (ShortcutList::iterator it = shortcutListIt->second.begin(); it != shortcutListIt->second.end(); ++it) + { + Shortcut* shortcut = *it; + + if (checkModifier(mod, button, shortcut, false)) + used = true; + + int pos = shortcut->getPosition(); + MatchResult result = match(0, button, shortcut->getSequence()[pos] & KeyMask); + + if (result != Matches_Not) + { + shortcut->setPosition(0); + + if (shortcut->getActivationStatus() == Shortcut::AS_Regular) + { + shortcut->setActivationStatus(Shortcut::AS_Inactive); + shortcut->signalActivated(false); + used = true; + } + else if (shortcut->getActivationStatus() == Shortcut::AS_Secondary) + { + shortcut->setActivationStatus(Shortcut::AS_Inactive); + shortcut->signalSecondary(false); + used = true; + } + } + } + + // Move on to parent + WidgetMap::iterator widgetIt = mChildParentRelations.find(widget); + widget = (widgetIt != mChildParentRelations.end()) ? widgetIt->second : 0; + } + + return used; + } + + bool ShortcutEventHandler::checkModifier(unsigned int mod, unsigned int button, Shortcut* shortcut, bool activate) + { + if (!shortcut->isEnabled() || !shortcut->getModifier() || shortcut->getSecondaryMode() == Shortcut::SM_Ignore || + shortcut->getModifierStatus() == activate) + return false; + + MatchResult result = match(mod, button, shortcut->getModifier()); + bool used = false; + + if (result != Matches_Not) + { + shortcut->setModifierStatus(activate); + + if (shortcut->getSecondaryMode() == Shortcut::SM_Detach) + { + if (activate) + { + shortcut->signalSecondary(true); + shortcut->signalSecondary(); + } + else + { + shortcut->signalSecondary(false); + } + } + else if (!activate && shortcut->getActivationStatus() == Shortcut::AS_Secondary) + { + shortcut->setActivationStatus(Shortcut::AS_Inactive); + shortcut->setPosition(0); + shortcut->signalSecondary(false); + used = true; + } + } + + return used; + } + + ShortcutEventHandler::MatchResult ShortcutEventHandler::match(unsigned int mod, unsigned int button, + unsigned int value) + { + if ((mod | button) == value) + { + return Matches_WithMod; + } + else if (button == value) + { + return Matches_NoMod; + } + else + { + return Matches_Not; + } + } + + bool ShortcutEventHandler::sort(const std::pair& left, + const std::pair& right) + { + if (left.first == Matches_WithMod && right.first == Matches_NoMod) + return true; + else + return left.second->getPosition() >= right.second->getPosition(); + } + + void ShortcutEventHandler::widgetDestroyed() + { + QWidget* widget = static_cast(sender()); + + mWidgetShortcuts.erase(widget); + mChildParentRelations.erase(widget); + } +} diff --git a/apps/opencs/model/prefs/shortcuteventhandler.hpp b/apps/opencs/model/prefs/shortcuteventhandler.hpp new file mode 100644 index 000000000..6a7ba2522 --- /dev/null +++ b/apps/opencs/model/prefs/shortcuteventhandler.hpp @@ -0,0 +1,69 @@ +#ifndef CSM_PREFS_SHORTCUT_EVENT_HANDLER_H +#define CSM_PREFS_SHORTCUT_EVENT_HANDLER_H + +#include +#include + +#include + +class QEvent; +class QWidget; + +namespace CSMPrefs +{ + class Shortcut; + + /// Users of this class should install it as an event handler + class ShortcutEventHandler : public QObject + { + Q_OBJECT + + public: + + ShortcutEventHandler(QObject* parent); + + void addShortcut(Shortcut* shortcut); + void removeShortcut(Shortcut* shortcut); + + protected: + + bool eventFilter(QObject* watched, QEvent* event); + + private: + + typedef std::vector ShortcutList; + // Child, Parent + typedef std::map WidgetMap; + typedef std::map ShortcutMap; + + enum MatchResult + { + Matches_WithMod, + Matches_NoMod, + Matches_Not + }; + + void updateParent(QWidget* widget); + + bool activate(QWidget* widget, unsigned int mod, unsigned int button); + + bool deactivate(QWidget* widget, unsigned int mod, unsigned int button); + + bool checkModifier(unsigned int mod, unsigned int button, Shortcut* shortcut, bool activate); + + MatchResult match(unsigned int mod, unsigned int button, unsigned int value); + + // Prefers Matches_WithMod and a larger number of buttons + static bool sort(const std::pair& left, + const std::pair& right); + + WidgetMap mChildParentRelations; + ShortcutMap mWidgetShortcuts; + + private slots: + + void widgetDestroyed(); + }; +} + +#endif diff --git a/apps/opencs/model/prefs/shortcutmanager.cpp b/apps/opencs/model/prefs/shortcutmanager.cpp new file mode 100644 index 000000000..6ae778fff --- /dev/null +++ b/apps/opencs/model/prefs/shortcutmanager.cpp @@ -0,0 +1,791 @@ +#include "shortcutmanager.hpp" + +#include + +#include +#include + +#include "shortcut.hpp" +#include "shortcuteventhandler.hpp" + +namespace CSMPrefs +{ + ShortcutManager::ShortcutManager() + { + createLookupTables(); + mEventHandler = new ShortcutEventHandler(this); + } + + void ShortcutManager::addShortcut(Shortcut* shortcut) + { + mShortcuts.insert(std::make_pair(shortcut->getName(), shortcut)); + mShortcuts.insert(std::make_pair(shortcut->getModifierName(), shortcut)); + mEventHandler->addShortcut(shortcut); + } + + void ShortcutManager::removeShortcut(Shortcut* shortcut) + { + std::pair range = mShortcuts.equal_range(shortcut->getName()); + + for (ShortcutMap::iterator it = range.first; it != range.second;) + { + if (it->second == shortcut) + { + mShortcuts.erase(it++); + } + else + { + ++it; + } + } + + mEventHandler->removeShortcut(shortcut); + } + + bool ShortcutManager::getSequence(const std::string& name, QKeySequence& sequence) const + { + SequenceMap::const_iterator item = mSequences.find(name); + if (item != mSequences.end()) + { + sequence = item->second; + + return true; + } + else + return false; + } + + void ShortcutManager::setSequence(const std::string& name, const QKeySequence& sequence) + { + // Add to map/modify + SequenceMap::iterator item = mSequences.find(name); + if (item != mSequences.end()) + { + item->second = sequence; + } + else + { + mSequences.insert(std::make_pair(name, sequence)); + } + + // Change active shortcuts + std::pair rangeS = mShortcuts.equal_range(name); + + for (ShortcutMap::iterator it = rangeS.first; it != rangeS.second; ++it) + { + it->second->setSequence(sequence); + } + } + + bool ShortcutManager::getModifier(const std::string& name, int& modifier) const + { + ModifierMap::const_iterator item = mModifiers.find(name); + if (item != mModifiers.end()) + { + modifier = item->second; + + return true; + } + else + return false; + } + + void ShortcutManager::setModifier(const std::string& name, int modifier) + { + // Add to map/modify + ModifierMap::iterator item = mModifiers.find(name); + if (item != mModifiers.end()) + { + item->second = modifier; + } + else + { + mModifiers.insert(std::make_pair(name, modifier)); + } + + // Change active shortcuts + std::pair rangeS = mShortcuts.equal_range(name); + + for (ShortcutMap::iterator it = rangeS.first; it != rangeS.second; ++it) + { + it->second->setModifier(modifier); + } + } + + std::string ShortcutManager::convertToString(const QKeySequence& sequence) const + { + const int MouseKeyMask = 0x01FFFFFF; + const int ModMask = 0x7E000000; + + std::string result; + + for (int i = 0; i < (int)sequence.count(); ++i) + { + int mods = sequence[i] & ModMask; + int key = sequence[i] & MouseKeyMask; + + if (key) + { + NameMap::const_iterator searchResult = mNames.find(key); + if (searchResult != mNames.end()) + { + if (mods && i == 0) + { + if (mods & Qt::ControlModifier) + result.append("Ctl+"); + if (mods & Qt::ShiftModifier) + result.append("Shift+"); + if (mods & Qt::AltModifier) + result.append("Alt+"); + if (mods & Qt::MetaModifier) + result.append("Meta+"); + if (mods & Qt::KeypadModifier) + result.append("Keypad+"); + if (mods & Qt::GroupSwitchModifier) + result.append("GroupSwitch+"); + } + else if (i > 0) + { + result.append("+"); + } + + result.append(searchResult->second); + } + } + } + + return result; + } + + std::string ShortcutManager::convertToString(int modifier) const + { + NameMap::const_iterator searchResult = mNames.find(modifier); + if (searchResult != mNames.end()) + { + return searchResult->second; + } + else + return ""; + } + + std::string ShortcutManager::convertToString(const QKeySequence& sequence, int modifier) const + { + std::string concat = convertToString(sequence) + ";" + convertToString(modifier); + return concat; + } + + void ShortcutManager::convertFromString(const std::string& data, QKeySequence& sequence) const + { + const int MaxKeys = 4; // A limitation of QKeySequence + + size_t end = data.find(";"); + size_t size = std::min(end, data.size()); + + std::string value = data.substr(0, size); + size_t start = 0; + + int keyPos = 0; + int mods = 0; + + int keys[MaxKeys] = {}; + + while (start < value.size()) + { + end = data.find("+", start); + end = std::min(end, value.size()); + + std::string name = value.substr(start, end - start); + + if (name == "Ctl") + { + mods |= Qt::ControlModifier; + } + else if (name == "Shift") + { + mods |= Qt::ShiftModifier; + } + else if (name == "Alt") + { + mods |= Qt::AltModifier; + } + else if (name == "Meta") + { + mods |= Qt::MetaModifier; + } + else if (name == "Keypad") + { + mods |= Qt::KeypadModifier; + } + else if (name == "GroupSwitch") + { + mods |= Qt::GroupSwitchModifier; + } + else + { + KeyMap::const_iterator searchResult = mKeys.find(name); + if (searchResult != mKeys.end()) + { + keys[keyPos] = mods | searchResult->second; + + mods = 0; + keyPos += 1; + + if (keyPos >= MaxKeys) + break; + } + } + + start = end + 1; + } + + sequence = QKeySequence(keys[0], keys[1], keys[2], keys[3]); + } + + void ShortcutManager::convertFromString(const std::string& data, int& modifier) const + { + size_t start = data.find(";") + 1; + start = std::min(start, data.size()); + + std::string name = data.substr(start); + KeyMap::const_iterator searchResult = mKeys.find(name); + if (searchResult != mKeys.end()) + { + modifier = searchResult->second; + } + else + { + modifier = 0; + } + } + + void ShortcutManager::convertFromString(const std::string& data, QKeySequence& sequence, int& modifier) const + { + convertFromString(data, sequence); + convertFromString(data, modifier); + } + + void ShortcutManager::createLookupTables() + { + // Mouse buttons + mNames.insert(std::make_pair(Qt::LeftButton, "LMB")); + mNames.insert(std::make_pair(Qt::RightButton, "RMB")); + mNames.insert(std::make_pair(Qt::MiddleButton, "MMB")); + mNames.insert(std::make_pair(Qt::XButton1, "Mouse4")); + mNames.insert(std::make_pair(Qt::XButton2, "Mouse5")); + + // Keyboard buttons + for (size_t i = 0; QtKeys[i].first != 0; ++i) + { + mNames.insert(QtKeys[i]); + } + + // Generate inverse map + for (NameMap::const_iterator it = mNames.begin(); it != mNames.end(); ++it) + { + mKeys.insert(std::make_pair(it->second, it->first)); + } + } + + QString ShortcutManager::processToolTip(const QString& toolTip) const + { + const QChar SequenceStart = '{'; + const QChar SequenceEnd = '}'; + + QStringList substrings; + + int prevIndex = 0; + int startIndex = toolTip.indexOf(SequenceStart); + int endIndex = (startIndex != -1) ? toolTip.indexOf(SequenceEnd, startIndex) : -1; + + // Process every valid shortcut escape sequence + while (startIndex != -1 && endIndex != -1) + { + int count = startIndex - prevIndex; + if (count > 0) + { + substrings.push_back(toolTip.mid(prevIndex, count)); + } + + // Find sequence name + startIndex += 1; // '{' character + count = endIndex - startIndex; + if (count > 0) + { + QString settingName = toolTip.mid(startIndex, count); + + QKeySequence sequence; + int modifier; + + if (getSequence(settingName.toUtf8().data(), sequence)) + { + QString value = QString::fromUtf8(convertToString(sequence).c_str()); + substrings.push_back(value); + } + else if (getModifier(settingName.toUtf8().data(), modifier)) + { + QString value = QString::fromUtf8(convertToString(modifier).c_str()); + substrings.push_back(value); + } + + prevIndex = endIndex + 1; // '}' character + } + + startIndex = toolTip.indexOf(SequenceStart, endIndex); + endIndex = (startIndex != -1) ? toolTip.indexOf(SequenceEnd, startIndex) : -1; + } + + if (prevIndex < toolTip.size()) + { + substrings.push_back(toolTip.mid(prevIndex)); + } + + return substrings.join(""); + } + + const std::pair ShortcutManager::QtKeys[] = + { + std::make_pair((int)Qt::Key_Space , "Space"), + std::make_pair((int)Qt::Key_Exclam , "Exclam"), + std::make_pair((int)Qt::Key_QuoteDbl , "QuoteDbl"), + std::make_pair((int)Qt::Key_NumberSign , "NumberSign"), + std::make_pair((int)Qt::Key_Dollar , "Dollar"), + std::make_pair((int)Qt::Key_Percent , "Percent"), + std::make_pair((int)Qt::Key_Ampersand , "Ampersand"), + std::make_pair((int)Qt::Key_Apostrophe , "Apostrophe"), + std::make_pair((int)Qt::Key_ParenLeft , "ParenLeft"), + std::make_pair((int)Qt::Key_ParenRight , "ParenRight"), + std::make_pair((int)Qt::Key_Asterisk , "Asterisk"), + std::make_pair((int)Qt::Key_Plus , "Plus"), + std::make_pair((int)Qt::Key_Comma , "Comma"), + std::make_pair((int)Qt::Key_Minus , "Minus"), + std::make_pair((int)Qt::Key_Period , "Period"), + std::make_pair((int)Qt::Key_Slash , "Slash"), + std::make_pair((int)Qt::Key_0 , "0"), + std::make_pair((int)Qt::Key_1 , "1"), + std::make_pair((int)Qt::Key_2 , "2"), + std::make_pair((int)Qt::Key_3 , "3"), + std::make_pair((int)Qt::Key_4 , "4"), + std::make_pair((int)Qt::Key_5 , "5"), + std::make_pair((int)Qt::Key_6 , "6"), + std::make_pair((int)Qt::Key_7 , "7"), + std::make_pair((int)Qt::Key_8 , "8"), + std::make_pair((int)Qt::Key_9 , "9"), + std::make_pair((int)Qt::Key_Colon , "Colon"), + std::make_pair((int)Qt::Key_Semicolon , "Semicolon"), + std::make_pair((int)Qt::Key_Less , "Less"), + std::make_pair((int)Qt::Key_Equal , "Equal"), + std::make_pair((int)Qt::Key_Greater , "Greater"), + std::make_pair((int)Qt::Key_Question , "Question"), + std::make_pair((int)Qt::Key_At , "At"), + std::make_pair((int)Qt::Key_A , "A"), + std::make_pair((int)Qt::Key_B , "B"), + std::make_pair((int)Qt::Key_C , "C"), + std::make_pair((int)Qt::Key_D , "D"), + std::make_pair((int)Qt::Key_E , "E"), + std::make_pair((int)Qt::Key_F , "F"), + std::make_pair((int)Qt::Key_G , "G"), + std::make_pair((int)Qt::Key_H , "H"), + std::make_pair((int)Qt::Key_I , "I"), + std::make_pair((int)Qt::Key_J , "J"), + std::make_pair((int)Qt::Key_K , "K"), + std::make_pair((int)Qt::Key_L , "L"), + std::make_pair((int)Qt::Key_M , "M"), + std::make_pair((int)Qt::Key_N , "N"), + std::make_pair((int)Qt::Key_O , "O"), + std::make_pair((int)Qt::Key_P , "P"), + std::make_pair((int)Qt::Key_Q , "Q"), + std::make_pair((int)Qt::Key_R , "R"), + std::make_pair((int)Qt::Key_S , "S"), + std::make_pair((int)Qt::Key_T , "T"), + std::make_pair((int)Qt::Key_U , "U"), + std::make_pair((int)Qt::Key_V , "V"), + std::make_pair((int)Qt::Key_W , "W"), + std::make_pair((int)Qt::Key_X , "X"), + std::make_pair((int)Qt::Key_Y , "Y"), + std::make_pair((int)Qt::Key_Z , "Z"), + std::make_pair((int)Qt::Key_BracketLeft , "BracketLeft"), + std::make_pair((int)Qt::Key_Backslash , "Backslash"), + std::make_pair((int)Qt::Key_BracketRight , "BracketRight"), + std::make_pair((int)Qt::Key_AsciiCircum , "AsciiCircum"), + std::make_pair((int)Qt::Key_Underscore , "Underscore"), + std::make_pair((int)Qt::Key_QuoteLeft , "QuoteLeft"), + std::make_pair((int)Qt::Key_BraceLeft , "BraceLeft"), + std::make_pair((int)Qt::Key_Bar , "Bar"), + std::make_pair((int)Qt::Key_BraceRight , "BraceRight"), + std::make_pair((int)Qt::Key_AsciiTilde , "AsciiTilde"), + std::make_pair((int)Qt::Key_nobreakspace , "nobreakspace"), + std::make_pair((int)Qt::Key_exclamdown , "exclamdown"), + std::make_pair((int)Qt::Key_cent , "cent"), + std::make_pair((int)Qt::Key_sterling , "sterling"), + std::make_pair((int)Qt::Key_currency , "currency"), + std::make_pair((int)Qt::Key_yen , "yen"), + std::make_pair((int)Qt::Key_brokenbar , "brokenbar"), + std::make_pair((int)Qt::Key_section , "section"), + std::make_pair((int)Qt::Key_diaeresis , "diaeresis"), + std::make_pair((int)Qt::Key_copyright , "copyright"), + std::make_pair((int)Qt::Key_ordfeminine , "ordfeminine"), + std::make_pair((int)Qt::Key_guillemotleft , "guillemotleft"), + std::make_pair((int)Qt::Key_notsign , "notsign"), + std::make_pair((int)Qt::Key_hyphen , "hyphen"), + std::make_pair((int)Qt::Key_registered , "registered"), + std::make_pair((int)Qt::Key_macron , "macron"), + std::make_pair((int)Qt::Key_degree , "degree"), + std::make_pair((int)Qt::Key_plusminus , "plusminus"), + std::make_pair((int)Qt::Key_twosuperior , "twosuperior"), + std::make_pair((int)Qt::Key_threesuperior , "threesuperior"), + std::make_pair((int)Qt::Key_acute , "acute"), + std::make_pair((int)Qt::Key_mu , "mu"), + std::make_pair((int)Qt::Key_paragraph , "paragraph"), + std::make_pair((int)Qt::Key_periodcentered , "periodcentered"), + std::make_pair((int)Qt::Key_cedilla , "cedilla"), + std::make_pair((int)Qt::Key_onesuperior , "onesuperior"), + std::make_pair((int)Qt::Key_masculine , "masculine"), + std::make_pair((int)Qt::Key_guillemotright , "guillemotright"), + std::make_pair((int)Qt::Key_onequarter , "onequarter"), + std::make_pair((int)Qt::Key_onehalf , "onehalf"), + std::make_pair((int)Qt::Key_threequarters , "threequarters"), + std::make_pair((int)Qt::Key_questiondown , "questiondown"), + std::make_pair((int)Qt::Key_Agrave , "Agrave"), + std::make_pair((int)Qt::Key_Aacute , "Aacute"), + std::make_pair((int)Qt::Key_Acircumflex , "Acircumflex"), + std::make_pair((int)Qt::Key_Atilde , "Atilde"), + std::make_pair((int)Qt::Key_Adiaeresis , "Adiaeresis"), + std::make_pair((int)Qt::Key_Aring , "Aring"), + std::make_pair((int)Qt::Key_AE , "AE"), + std::make_pair((int)Qt::Key_Ccedilla , "Ccedilla"), + std::make_pair((int)Qt::Key_Egrave , "Egrave"), + std::make_pair((int)Qt::Key_Eacute , "Eacute"), + std::make_pair((int)Qt::Key_Ecircumflex , "Ecircumflex"), + std::make_pair((int)Qt::Key_Ediaeresis , "Ediaeresis"), + std::make_pair((int)Qt::Key_Igrave , "Igrave"), + std::make_pair((int)Qt::Key_Iacute , "Iacute"), + std::make_pair((int)Qt::Key_Icircumflex , "Icircumflex"), + std::make_pair((int)Qt::Key_Idiaeresis , "Idiaeresis"), + std::make_pair((int)Qt::Key_ETH , "ETH"), + std::make_pair((int)Qt::Key_Ntilde , "Ntilde"), + std::make_pair((int)Qt::Key_Ograve , "Ograve"), + std::make_pair((int)Qt::Key_Oacute , "Oacute"), + std::make_pair((int)Qt::Key_Ocircumflex , "Ocircumflex"), + std::make_pair((int)Qt::Key_Otilde , "Otilde"), + std::make_pair((int)Qt::Key_Odiaeresis , "Odiaeresis"), + std::make_pair((int)Qt::Key_multiply , "multiply"), + std::make_pair((int)Qt::Key_Ooblique , "Ooblique"), + std::make_pair((int)Qt::Key_Ugrave , "Ugrave"), + std::make_pair((int)Qt::Key_Uacute , "Uacute"), + std::make_pair((int)Qt::Key_Ucircumflex , "Ucircumflex"), + std::make_pair((int)Qt::Key_Udiaeresis , "Udiaeresis"), + std::make_pair((int)Qt::Key_Yacute , "Yacute"), + std::make_pair((int)Qt::Key_THORN , "THORN"), + std::make_pair((int)Qt::Key_ssharp , "ssharp"), + std::make_pair((int)Qt::Key_division , "division"), + std::make_pair((int)Qt::Key_ydiaeresis , "ydiaeresis"), + std::make_pair((int)Qt::Key_Escape , "Escape"), + std::make_pair((int)Qt::Key_Tab , "Tab"), + std::make_pair((int)Qt::Key_Backtab , "Backtab"), + std::make_pair((int)Qt::Key_Backspace , "Backspace"), + std::make_pair((int)Qt::Key_Return , "Return"), + std::make_pair((int)Qt::Key_Enter , "Enter"), + std::make_pair((int)Qt::Key_Insert , "Insert"), + std::make_pair((int)Qt::Key_Delete , "Delete"), + std::make_pair((int)Qt::Key_Pause , "Pause"), + std::make_pair((int)Qt::Key_Print , "Print"), + std::make_pair((int)Qt::Key_SysReq , "SysReq"), + std::make_pair((int)Qt::Key_Clear , "Clear"), + std::make_pair((int)Qt::Key_Home , "Home"), + std::make_pair((int)Qt::Key_End , "End"), + std::make_pair((int)Qt::Key_Left , "Left"), + std::make_pair((int)Qt::Key_Up , "Up"), + std::make_pair((int)Qt::Key_Right , "Right"), + std::make_pair((int)Qt::Key_Down , "Down"), + std::make_pair((int)Qt::Key_PageUp , "PageUp"), + std::make_pair((int)Qt::Key_PageDown , "PageDown"), + std::make_pair((int)Qt::Key_Shift , "Shift"), + std::make_pair((int)Qt::Key_Control , "Control"), + std::make_pair((int)Qt::Key_Meta , "Meta"), + std::make_pair((int)Qt::Key_Alt , "Alt"), + std::make_pair((int)Qt::Key_CapsLock , "CapsLock"), + std::make_pair((int)Qt::Key_NumLock , "NumLock"), + std::make_pair((int)Qt::Key_ScrollLock , "ScrollLock"), + std::make_pair((int)Qt::Key_F1 , "F1"), + std::make_pair((int)Qt::Key_F2 , "F2"), + std::make_pair((int)Qt::Key_F3 , "F3"), + std::make_pair((int)Qt::Key_F4 , "F4"), + std::make_pair((int)Qt::Key_F5 , "F5"), + std::make_pair((int)Qt::Key_F6 , "F6"), + std::make_pair((int)Qt::Key_F7 , "F7"), + std::make_pair((int)Qt::Key_F8 , "F8"), + std::make_pair((int)Qt::Key_F9 , "F9"), + std::make_pair((int)Qt::Key_F10 , "F10"), + std::make_pair((int)Qt::Key_F11 , "F11"), + std::make_pair((int)Qt::Key_F12 , "F12"), + std::make_pair((int)Qt::Key_F13 , "F13"), + std::make_pair((int)Qt::Key_F14 , "F14"), + std::make_pair((int)Qt::Key_F15 , "F15"), + std::make_pair((int)Qt::Key_F16 , "F16"), + std::make_pair((int)Qt::Key_F17 , "F17"), + std::make_pair((int)Qt::Key_F18 , "F18"), + std::make_pair((int)Qt::Key_F19 , "F19"), + std::make_pair((int)Qt::Key_F20 , "F20"), + std::make_pair((int)Qt::Key_F21 , "F21"), + std::make_pair((int)Qt::Key_F22 , "F22"), + std::make_pair((int)Qt::Key_F23 , "F23"), + std::make_pair((int)Qt::Key_F24 , "F24"), + std::make_pair((int)Qt::Key_F25 , "F25"), + std::make_pair((int)Qt::Key_F26 , "F26"), + std::make_pair((int)Qt::Key_F27 , "F27"), + std::make_pair((int)Qt::Key_F28 , "F28"), + std::make_pair((int)Qt::Key_F29 , "F29"), + std::make_pair((int)Qt::Key_F30 , "F30"), + std::make_pair((int)Qt::Key_F31 , "F31"), + std::make_pair((int)Qt::Key_F32 , "F32"), + std::make_pair((int)Qt::Key_F33 , "F33"), + std::make_pair((int)Qt::Key_F34 , "F34"), + std::make_pair((int)Qt::Key_F35 , "F35"), + std::make_pair((int)Qt::Key_Super_L , "Super_L"), + std::make_pair((int)Qt::Key_Super_R , "Super_R"), + std::make_pair((int)Qt::Key_Menu , "Menu"), + std::make_pair((int)Qt::Key_Hyper_L , "Hyper_L"), + std::make_pair((int)Qt::Key_Hyper_R , "Hyper_R"), + std::make_pair((int)Qt::Key_Help , "Help"), + std::make_pair((int)Qt::Key_Direction_L , "Direction_L"), + std::make_pair((int)Qt::Key_Direction_R , "Direction_R"), + std::make_pair((int)Qt::Key_Back , "Back"), + std::make_pair((int)Qt::Key_Forward , "Forward"), + std::make_pair((int)Qt::Key_Stop , "Stop"), + std::make_pair((int)Qt::Key_Refresh , "Refresh"), + std::make_pair((int)Qt::Key_VolumeDown , "VolumeDown"), + std::make_pair((int)Qt::Key_VolumeMute , "VolumeMute"), + std::make_pair((int)Qt::Key_VolumeUp , "VolumeUp"), + std::make_pair((int)Qt::Key_BassBoost , "BassBoost"), + std::make_pair((int)Qt::Key_BassUp , "BassUp"), + std::make_pair((int)Qt::Key_BassDown , "BassDown"), + std::make_pair((int)Qt::Key_TrebleUp , "TrebleUp"), + std::make_pair((int)Qt::Key_TrebleDown , "TrebleDown"), + std::make_pair((int)Qt::Key_MediaPlay , "MediaPlay"), + std::make_pair((int)Qt::Key_MediaStop , "MediaStop"), + std::make_pair((int)Qt::Key_MediaPrevious , "MediaPrevious"), + std::make_pair((int)Qt::Key_MediaNext , "MediaNext"), + std::make_pair((int)Qt::Key_MediaRecord , "MediaRecord"), + std::make_pair((int)Qt::Key_MediaPause , "MediaPause"), + std::make_pair((int)Qt::Key_MediaTogglePlayPause , "MediaTogglePlayPause"), + std::make_pair((int)Qt::Key_HomePage , "HomePage"), + std::make_pair((int)Qt::Key_Favorites , "Favorites"), + std::make_pair((int)Qt::Key_Search , "Search"), + std::make_pair((int)Qt::Key_Standby , "Standby"), + std::make_pair((int)Qt::Key_OpenUrl , "OpenUrl"), + std::make_pair((int)Qt::Key_LaunchMail , "LaunchMail"), + std::make_pair((int)Qt::Key_LaunchMedia , "LaunchMedia"), + std::make_pair((int)Qt::Key_Launch0 , "Launch0"), + std::make_pair((int)Qt::Key_Launch1 , "Launch1"), + std::make_pair((int)Qt::Key_Launch2 , "Launch2"), + std::make_pair((int)Qt::Key_Launch3 , "Launch3"), + std::make_pair((int)Qt::Key_Launch4 , "Launch4"), + std::make_pair((int)Qt::Key_Launch5 , "Launch5"), + std::make_pair((int)Qt::Key_Launch6 , "Launch6"), + std::make_pair((int)Qt::Key_Launch7 , "Launch7"), + std::make_pair((int)Qt::Key_Launch8 , "Launch8"), + std::make_pair((int)Qt::Key_Launch9 , "Launch9"), + std::make_pair((int)Qt::Key_LaunchA , "LaunchA"), + std::make_pair((int)Qt::Key_LaunchB , "LaunchB"), + std::make_pair((int)Qt::Key_LaunchC , "LaunchC"), + std::make_pair((int)Qt::Key_LaunchD , "LaunchD"), + std::make_pair((int)Qt::Key_LaunchE , "LaunchE"), + std::make_pair((int)Qt::Key_LaunchF , "LaunchF"), + std::make_pair((int)Qt::Key_MonBrightnessUp , "MonBrightnessUp"), + std::make_pair((int)Qt::Key_MonBrightnessDown , "MonBrightnessDown"), + std::make_pair((int)Qt::Key_KeyboardLightOnOff , "KeyboardLightOnOff"), + std::make_pair((int)Qt::Key_KeyboardBrightnessUp , "KeyboardBrightnessUp"), + std::make_pair((int)Qt::Key_KeyboardBrightnessDown , "KeyboardBrightnessDown"), + std::make_pair((int)Qt::Key_PowerOff , "PowerOff"), + std::make_pair((int)Qt::Key_WakeUp , "WakeUp"), + std::make_pair((int)Qt::Key_Eject , "Eject"), + std::make_pair((int)Qt::Key_ScreenSaver , "ScreenSaver"), + std::make_pair((int)Qt::Key_WWW , "WWW"), + std::make_pair((int)Qt::Key_Memo , "Memo"), + std::make_pair((int)Qt::Key_LightBulb , "LightBulb"), + std::make_pair((int)Qt::Key_Shop , "Shop"), + std::make_pair((int)Qt::Key_History , "History"), + std::make_pair((int)Qt::Key_AddFavorite , "AddFavorite"), + std::make_pair((int)Qt::Key_HotLinks , "HotLinks"), + std::make_pair((int)Qt::Key_BrightnessAdjust , "BrightnessAdjust"), + std::make_pair((int)Qt::Key_Finance , "Finance"), + std::make_pair((int)Qt::Key_Community , "Community"), + std::make_pair((int)Qt::Key_AudioRewind , "AudioRewind"), + std::make_pair((int)Qt::Key_BackForward , "BackForward"), + std::make_pair((int)Qt::Key_ApplicationLeft , "ApplicationLeft"), + std::make_pair((int)Qt::Key_ApplicationRight , "ApplicationRight"), + std::make_pair((int)Qt::Key_Book , "Book"), + std::make_pair((int)Qt::Key_CD , "CD"), + std::make_pair((int)Qt::Key_Calculator , "Calculator"), + std::make_pair((int)Qt::Key_ToDoList , "ToDoList"), + std::make_pair((int)Qt::Key_ClearGrab , "ClearGrab"), + std::make_pair((int)Qt::Key_Close , "Close"), + std::make_pair((int)Qt::Key_Copy , "Copy"), + std::make_pair((int)Qt::Key_Cut , "Cut"), + std::make_pair((int)Qt::Key_Display , "Display"), + std::make_pair((int)Qt::Key_DOS , "DOS"), + std::make_pair((int)Qt::Key_Documents , "Documents"), + std::make_pair((int)Qt::Key_Excel , "Excel"), + std::make_pair((int)Qt::Key_Explorer , "Explorer"), + std::make_pair((int)Qt::Key_Game , "Game"), + std::make_pair((int)Qt::Key_Go , "Go"), + std::make_pair((int)Qt::Key_iTouch , "iTouch"), + std::make_pair((int)Qt::Key_LogOff , "LogOff"), + std::make_pair((int)Qt::Key_Market , "Market"), + std::make_pair((int)Qt::Key_Meeting , "Meeting"), + std::make_pair((int)Qt::Key_MenuKB , "MenuKB"), + std::make_pair((int)Qt::Key_MenuPB , "MenuPB"), + std::make_pair((int)Qt::Key_MySites , "MySites"), + std::make_pair((int)Qt::Key_News , "News"), + std::make_pair((int)Qt::Key_OfficeHome , "OfficeHome"), + std::make_pair((int)Qt::Key_Option , "Option"), + std::make_pair((int)Qt::Key_Paste , "Paste"), + std::make_pair((int)Qt::Key_Phone , "Phone"), + std::make_pair((int)Qt::Key_Calendar , "Calendar"), + std::make_pair((int)Qt::Key_Reply , "Reply"), + std::make_pair((int)Qt::Key_Reload , "Reload"), + std::make_pair((int)Qt::Key_RotateWindows , "RotateWindows"), + std::make_pair((int)Qt::Key_RotationPB , "RotationPB"), + std::make_pair((int)Qt::Key_RotationKB , "RotationKB"), + std::make_pair((int)Qt::Key_Save , "Save"), + std::make_pair((int)Qt::Key_Send , "Send"), + std::make_pair((int)Qt::Key_Spell , "Spell"), + std::make_pair((int)Qt::Key_SplitScreen , "SplitScreen"), + std::make_pair((int)Qt::Key_Support , "Support"), + std::make_pair((int)Qt::Key_TaskPane , "TaskPane"), + std::make_pair((int)Qt::Key_Terminal , "Terminal"), + std::make_pair((int)Qt::Key_Tools , "Tools"), + std::make_pair((int)Qt::Key_Travel , "Travel"), + std::make_pair((int)Qt::Key_Video , "Video"), + std::make_pair((int)Qt::Key_Word , "Word"), + std::make_pair((int)Qt::Key_Xfer , "Xfer"), + std::make_pair((int)Qt::Key_ZoomIn , "ZoomIn"), + std::make_pair((int)Qt::Key_ZoomOut , "ZoomOut"), + std::make_pair((int)Qt::Key_Away , "Away"), + std::make_pair((int)Qt::Key_Messenger , "Messenger"), + std::make_pair((int)Qt::Key_WebCam , "WebCam"), + std::make_pair((int)Qt::Key_MailForward , "MailForward"), + std::make_pair((int)Qt::Key_Pictures , "Pictures"), + std::make_pair((int)Qt::Key_Music , "Music"), + std::make_pair((int)Qt::Key_Battery , "Battery"), + std::make_pair((int)Qt::Key_Bluetooth , "Bluetooth"), + std::make_pair((int)Qt::Key_WLAN , "WLAN"), + std::make_pair((int)Qt::Key_UWB , "UWB"), + std::make_pair((int)Qt::Key_AudioForward , "AudioForward"), + std::make_pair((int)Qt::Key_AudioRepeat , "AudioRepeat"), + std::make_pair((int)Qt::Key_AudioRandomPlay , "AudioRandomPlay"), + std::make_pair((int)Qt::Key_Subtitle , "Subtitle"), + std::make_pair((int)Qt::Key_AudioCycleTrack , "AudioCycleTrack"), + std::make_pair((int)Qt::Key_Time , "Time"), + std::make_pair((int)Qt::Key_Hibernate , "Hibernate"), + std::make_pair((int)Qt::Key_View , "View"), + std::make_pair((int)Qt::Key_TopMenu , "TopMenu"), + std::make_pair((int)Qt::Key_PowerDown , "PowerDown"), + std::make_pair((int)Qt::Key_Suspend , "Suspend"), + std::make_pair((int)Qt::Key_ContrastAdjust , "ContrastAdjust"), + std::make_pair((int)Qt::Key_LaunchG , "LaunchG"), + std::make_pair((int)Qt::Key_LaunchH , "LaunchH"), +#if QT_VERSION >= QT_VERSION_CHECK(5,7,0) + std::make_pair((int)Qt::Key_TouchpadToggle , "TouchpadToggle"), + std::make_pair((int)Qt::Key_TouchpadOn , "TouchpadOn"), + std::make_pair((int)Qt::Key_TouchpadOff , "TouchpadOff"), + std::make_pair((int)Qt::Key_MicMute , "MicMute"), + std::make_pair((int)Qt::Key_Red , "Red"), + std::make_pair((int)Qt::Key_Green , "Green"), + std::make_pair((int)Qt::Key_Yellow , "Yellow"), + std::make_pair((int)Qt::Key_Blue , "Blue"), + std::make_pair((int)Qt::Key_ChannelUp , "ChannelUp"), + std::make_pair((int)Qt::Key_ChannelDown , "ChannelDown"), + std::make_pair((int)Qt::Key_Guide , "Guide"), + std::make_pair((int)Qt::Key_Info , "Info"), + std::make_pair((int)Qt::Key_Settings , "Settings"), + std::make_pair((int)Qt::Key_MicVolumeUp , "MicVolumeUp"), + std::make_pair((int)Qt::Key_MicVolumeDown , "MicVolumeDown"), + std::make_pair((int)Qt::Key_New , "New"), + std::make_pair((int)Qt::Key_Open , "Open"), + std::make_pair((int)Qt::Key_Find , "Find"), + std::make_pair((int)Qt::Key_Undo , "Undo"), + std::make_pair((int)Qt::Key_Redo , "Redo"), +#endif + std::make_pair((int)Qt::Key_AltGr , "AltGr"), + std::make_pair((int)Qt::Key_Multi_key , "Multi_key"), + std::make_pair((int)Qt::Key_Kanji , "Kanji"), + std::make_pair((int)Qt::Key_Muhenkan , "Muhenkan"), + std::make_pair((int)Qt::Key_Henkan , "Henkan"), + std::make_pair((int)Qt::Key_Romaji , "Romaji"), + std::make_pair((int)Qt::Key_Hiragana , "Hiragana"), + std::make_pair((int)Qt::Key_Katakana , "Katakana"), + std::make_pair((int)Qt::Key_Hiragana_Katakana , "Hiragana_Katakana"), + std::make_pair((int)Qt::Key_Zenkaku , "Zenkaku"), + std::make_pair((int)Qt::Key_Hankaku , "Hankaku"), + std::make_pair((int)Qt::Key_Zenkaku_Hankaku , "Zenkaku_Hankaku"), + std::make_pair((int)Qt::Key_Touroku , "Touroku"), + std::make_pair((int)Qt::Key_Massyo , "Massyo"), + std::make_pair((int)Qt::Key_Kana_Lock , "Kana_Lock"), + std::make_pair((int)Qt::Key_Kana_Shift , "Kana_Shift"), + std::make_pair((int)Qt::Key_Eisu_Shift , "Eisu_Shift"), + std::make_pair((int)Qt::Key_Eisu_toggle , "Eisu_toggle"), + std::make_pair((int)Qt::Key_Hangul , "Hangul"), + std::make_pair((int)Qt::Key_Hangul_Start , "Hangul_Start"), + std::make_pair((int)Qt::Key_Hangul_End , "Hangul_End"), + std::make_pair((int)Qt::Key_Hangul_Hanja , "Hangul_Hanja"), + std::make_pair((int)Qt::Key_Hangul_Jamo , "Hangul_Jamo"), + std::make_pair((int)Qt::Key_Hangul_Romaja , "Hangul_Romaja"), + std::make_pair((int)Qt::Key_Codeinput , "Codeinput"), + std::make_pair((int)Qt::Key_Hangul_Jeonja , "Hangul_Jeonja"), + std::make_pair((int)Qt::Key_Hangul_Banja , "Hangul_Banja"), + std::make_pair((int)Qt::Key_Hangul_PreHanja , "Hangul_PreHanja"), + std::make_pair((int)Qt::Key_Hangul_PostHanja , "Hangul_PostHanja"), + std::make_pair((int)Qt::Key_SingleCandidate , "SingleCandidate"), + std::make_pair((int)Qt::Key_MultipleCandidate , "MultipleCandidate"), + std::make_pair((int)Qt::Key_PreviousCandidate , "PreviousCandidate"), + std::make_pair((int)Qt::Key_Hangul_Special , "Hangul_Special"), + std::make_pair((int)Qt::Key_Mode_switch , "Mode_switch"), + std::make_pair((int)Qt::Key_Dead_Grave , "Dead_Grave"), + std::make_pair((int)Qt::Key_Dead_Acute , "Dead_Acute"), + std::make_pair((int)Qt::Key_Dead_Circumflex , "Dead_Circumflex"), + std::make_pair((int)Qt::Key_Dead_Tilde , "Dead_Tilde"), + std::make_pair((int)Qt::Key_Dead_Macron , "Dead_Macron"), + std::make_pair((int)Qt::Key_Dead_Breve , "Dead_Breve"), + std::make_pair((int)Qt::Key_Dead_Abovedot , "Dead_Abovedot"), + std::make_pair((int)Qt::Key_Dead_Diaeresis , "Dead_Diaeresis"), + std::make_pair((int)Qt::Key_Dead_Abovering , "Dead_Abovering"), + std::make_pair((int)Qt::Key_Dead_Doubleacute , "Dead_Doubleacute"), + std::make_pair((int)Qt::Key_Dead_Caron , "Dead_Caron"), + std::make_pair((int)Qt::Key_Dead_Cedilla , "Dead_Cedilla"), + std::make_pair((int)Qt::Key_Dead_Ogonek , "Dead_Ogonek"), + std::make_pair((int)Qt::Key_Dead_Iota , "Dead_Iota"), + std::make_pair((int)Qt::Key_Dead_Voiced_Sound , "Dead_Voiced_Sound"), + std::make_pair((int)Qt::Key_Dead_Semivoiced_Sound , "Dead_Semivoiced_Sound"), + std::make_pair((int)Qt::Key_Dead_Belowdot , "Dead_Belowdot"), + std::make_pair((int)Qt::Key_Dead_Hook , "Dead_Hook"), + std::make_pair((int)Qt::Key_Dead_Horn , "Dead_Horn"), + std::make_pair((int)Qt::Key_MediaLast , "MediaLast"), + std::make_pair((int)Qt::Key_Select , "Select"), + std::make_pair((int)Qt::Key_Yes , "Yes"), + std::make_pair((int)Qt::Key_No , "No"), + std::make_pair((int)Qt::Key_Cancel , "Cancel"), + std::make_pair((int)Qt::Key_Printer , "Printer"), + std::make_pair((int)Qt::Key_Execute , "Execute"), + std::make_pair((int)Qt::Key_Sleep , "Sleep"), + std::make_pair((int)Qt::Key_Play , "Play"), + std::make_pair((int)Qt::Key_Zoom , "Zoom"), +#if QT_VERSION >= QT_VERSION_CHECK(5,7,0) + std::make_pair((int)Qt::Key_Exit , "Exit"), +#endif + std::make_pair((int)Qt::Key_Context1 , "Context1"), + std::make_pair((int)Qt::Key_Context2 , "Context2"), + std::make_pair((int)Qt::Key_Context3 , "Context3"), + std::make_pair((int)Qt::Key_Context4 , "Context4"), + std::make_pair((int)Qt::Key_Call , "Call"), + std::make_pair((int)Qt::Key_Hangup , "Hangup"), + std::make_pair((int)Qt::Key_Flip , "Flip"), + std::make_pair((int)Qt::Key_ToggleCallHangup , "ToggleCallHangup"), + std::make_pair((int)Qt::Key_VoiceDial , "VoiceDial"), + std::make_pair((int)Qt::Key_LastNumberRedial , "LastNumberRedial"), + std::make_pair((int)Qt::Key_Camera , "Camera"), + std::make_pair((int)Qt::Key_CameraFocus , "CameraFocus"), + std::make_pair(0 , (const char*) 0) + }; + +} diff --git a/apps/opencs/model/prefs/shortcutmanager.hpp b/apps/opencs/model/prefs/shortcutmanager.hpp new file mode 100644 index 000000000..99f01a5df --- /dev/null +++ b/apps/opencs/model/prefs/shortcutmanager.hpp @@ -0,0 +1,73 @@ +#ifndef CSM_PREFS_SHORTCUTMANAGER_H +#define CSM_PREFS_SHORTCUTMANAGER_H + +#include + +#include +#include +#include + +namespace CSMPrefs +{ + class Shortcut; + class ShortcutEventHandler; + + /// Class used to track and update shortcuts/sequences + class ShortcutManager : public QObject + { + Q_OBJECT + + public: + + ShortcutManager(); + + /// The shortcut class will do this automatically + void addShortcut(Shortcut* shortcut); + + /// The shortcut class will do this automatically + void removeShortcut(Shortcut* shortcut); + + bool getSequence(const std::string& name, QKeySequence& sequence) const; + void setSequence(const std::string& name, const QKeySequence& sequence); + + bool getModifier(const std::string& name, int& modifier) const; + void setModifier(const std::string& name, int modifier); + + std::string convertToString(const QKeySequence& sequence) const; + std::string convertToString(int modifier) const; + + std::string convertToString(const QKeySequence& sequence, int modifier) const; + + void convertFromString(const std::string& data, QKeySequence& sequence) const; + void convertFromString(const std::string& data, int& modifier) const; + + void convertFromString(const std::string& data, QKeySequence& sequence, int& modifier) const; + + /// Replaces "{sequence-name}" or "{modifier-name}" with the appropriate text + QString processToolTip(const QString& toolTip) const; + + private: + + // Need a multimap in case multiple shortcuts share the same name + typedef std::multimap ShortcutMap; + typedef std::map SequenceMap; + typedef std::map ModifierMap; + typedef std::map NameMap; + typedef std::map KeyMap; + + ShortcutMap mShortcuts; + SequenceMap mSequences; + ModifierMap mModifiers; + + NameMap mNames; + KeyMap mKeys; + + ShortcutEventHandler* mEventHandler; + + void createLookupTables(); + + static const std::pair QtKeys[]; + }; +} + +#endif diff --git a/apps/opencs/model/prefs/shortcutsetting.cpp b/apps/opencs/model/prefs/shortcutsetting.cpp new file mode 100644 index 000000000..c56119deb --- /dev/null +++ b/apps/opencs/model/prefs/shortcutsetting.cpp @@ -0,0 +1,197 @@ +#include "shortcutsetting.hpp" + +#include +#include +#include +#include +#include +#include + +#include "state.hpp" +#include "shortcutmanager.hpp" + +namespace CSMPrefs +{ + const int ShortcutSetting::MaxKeys; + + ShortcutSetting::ShortcutSetting(Category* parent, Settings::Manager* values, QMutex* mutex, const std::string& key, + const std::string& label) + : Setting(parent, values, mutex, key, label) + , mButton(0) + , mEditorActive(false) + , mEditorPos(0) + { + for (int i = 0; i < MaxKeys; ++i) + { + mEditorKeys[i] = 0; + } + } + + std::pair ShortcutSetting::makeWidgets(QWidget* parent) + { + QKeySequence sequence; + State::get().getShortcutManager().getSequence(getKey(), sequence); + + QString text = QString::fromUtf8(State::get().getShortcutManager().convertToString(sequence).c_str()); + + QLabel* label = new QLabel(QString::fromUtf8(getLabel().c_str()), parent); + QPushButton* widget = new QPushButton(text, parent); + + widget->setCheckable(true); + widget->installEventFilter(this); + mButton = widget; + + connect(widget, SIGNAL(toggled(bool)), this, SLOT(buttonToggled(bool))); + + return std::make_pair(label, widget); + } + + bool ShortcutSetting::eventFilter(QObject* target, QEvent* event) + { + if (event->type() == QEvent::KeyPress) + { + QKeyEvent* keyEvent = static_cast(event); + if (keyEvent->isAutoRepeat()) + return true; + + int mod = keyEvent->modifiers(); + int key = keyEvent->key(); + + return handleEvent(target, mod, key, true); + } + else if (event->type() == QEvent::KeyRelease) + { + QKeyEvent* keyEvent = static_cast(event); + if (keyEvent->isAutoRepeat()) + return true; + + int mod = keyEvent->modifiers(); + int key = keyEvent->key(); + + return handleEvent(target, mod, key, false); + } + else if (event->type() == QEvent::MouseButtonPress) + { + QMouseEvent* mouseEvent = static_cast(event); + int mod = mouseEvent->modifiers(); + int key = mouseEvent->button(); + + return handleEvent(target, mod, key, true); + } + else if (event->type() == QEvent::MouseButtonRelease) + { + QMouseEvent* mouseEvent = static_cast(event); + int mod = mouseEvent->modifiers(); + int key = mouseEvent->button(); + + return handleEvent(target, mod, key, false); + } + else if (event->type() == QEvent::FocusOut) + { + resetState(); + } + + return false; + } + + bool ShortcutSetting::handleEvent(QObject* target, int mod, int value, bool active) + { + // Modifiers are handled differently + const int Blacklist[] = + { + Qt::Key_Shift, + Qt::Key_Control, + Qt::Key_Meta, + Qt::Key_Alt, + Qt::Key_AltGr + }; + + const size_t BlacklistSize = sizeof(Blacklist) / sizeof(int); + + if (!mEditorActive) + { + if (value == Qt::RightButton && !active) + { + // Clear sequence + QKeySequence sequence = QKeySequence(0, 0, 0, 0); + storeValue(sequence); + + resetState(); + } + + return false; + } + + // Handle blacklist + for (size_t i = 0; i < BlacklistSize; ++i) + { + if (value == Blacklist[i]) + return true; + } + + if (!active || mEditorPos >= MaxKeys) + { + // Update key + QKeySequence sequence = QKeySequence(mEditorKeys[0], mEditorKeys[1], mEditorKeys[2], mEditorKeys[3]); + storeValue(sequence); + + resetState(); + } + else + { + if (mEditorPos == 0) + { + mEditorKeys[0] = mod | value; + } + else + { + mEditorKeys[mEditorPos] = value; + } + + mEditorPos += 1; + } + + return true; + } + + void ShortcutSetting::storeValue(const QKeySequence& sequence) + { + State::get().getShortcutManager().setSequence(getKey(), sequence); + + // Convert to string and assign + std::string value = State::get().getShortcutManager().convertToString(sequence); + + { + QMutexLocker lock(getMutex()); + getValues().setString(getKey(), getParent()->getKey(), value); + } + + getParent()->getState()->update(*this); + } + + void ShortcutSetting::resetState() + { + mButton->setChecked(false); + mEditorActive = false; + mEditorPos = 0; + for (int i = 0; i < MaxKeys; ++i) + { + mEditorKeys[i] = 0; + } + + // Button text + QKeySequence sequence; + State::get().getShortcutManager().getSequence(getKey(), sequence); + + QString text = QString::fromUtf8(State::get().getShortcutManager().convertToString(sequence).c_str()); + mButton->setText(text); + } + + void ShortcutSetting::buttonToggled(bool checked) + { + if (checked) + mButton->setText("Press keys or click here..."); + + mEditorActive = checked; + } +} diff --git a/apps/opencs/model/prefs/shortcutsetting.hpp b/apps/opencs/model/prefs/shortcutsetting.hpp new file mode 100644 index 000000000..bb38b580a --- /dev/null +++ b/apps/opencs/model/prefs/shortcutsetting.hpp @@ -0,0 +1,49 @@ +#ifndef CSM_PREFS_SHORTCUTSETTING_H +#define CSM_PREFS_SHORTCUTSETTING_H + +#include + +#include "setting.hpp" + +class QEvent; +class QPushButton; + +namespace CSMPrefs +{ + class ShortcutSetting : public Setting + { + Q_OBJECT + + public: + + ShortcutSetting(Category* parent, Settings::Manager* values, QMutex* mutex, const std::string& key, + const std::string& label); + + virtual std::pair makeWidgets(QWidget* parent); + + protected: + + bool eventFilter(QObject* target, QEvent* event); + + private: + + bool handleEvent(QObject* target, int mod, int value, bool active); + + void storeValue(const QKeySequence& sequence); + void resetState(); + + static const int MaxKeys = 4; + + QPushButton* mButton; + + bool mEditorActive; + int mEditorPos; + int mEditorKeys[MaxKeys]; + + private slots: + + void buttonToggled(bool checked); + }; +} + +#endif diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index 772047961..b8d6102ac 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -9,6 +9,8 @@ #include "doublesetting.hpp" #include "boolsetting.hpp" #include "coloursetting.hpp" +#include "shortcutsetting.hpp" +#include "modifiersetting.hpp" CSMPrefs::State *CSMPrefs::State::sThis = 0; @@ -136,6 +138,9 @@ void CSMPrefs::State::declare() declareBool ("wrap-lines", "Wrap Lines", false). setTooltip ("Wrap lines longer than width of script editor."); declareBool ("mono-font", "Use monospace font", true); + declareInt ("tab-width", "Tab Width", 4). + setTooltip ("Number of characters for tab width"). + setRange (1, 10); EnumValue warningsNormal ("Normal", "Report warnings as warning"); declareEnum ("warnings", "Warning Mode", warningsNormal). addValue ("Ignore", "Do not report warning"). @@ -162,20 +167,17 @@ void CSMPrefs::State::declare() "list go to the first/last item"); declareCategory ("3D Scene Input"); - EnumValue left ("Left Mouse-Button"); - EnumValue cLeft ("Ctrl-Left Mouse-Button"); - EnumValue right ("Right Mouse-Button"); - EnumValue cRight ("Ctrl-Right Mouse-Button"); - EnumValue middle ("Middle Mouse-Button"); - EnumValue cMiddle ("Ctrl-Middle Mouse-Button"); - EnumValues inputButtons; - inputButtons.add (left).add (cLeft).add (right).add (cRight).add (middle).add (cMiddle); - declareEnum ("p-navi", "Primary Camera Navigation Button", left).addValues (inputButtons); - declareEnum ("s-navi", "Secondary Camera Navigation Button", cLeft).addValues (inputButtons); - declareEnum ("p-edit", "Primary Editing Button", right).addValues (inputButtons); - declareEnum ("s-edit", "Secondary Editing Button", cRight).addValues (inputButtons); - declareEnum ("p-select", "Primary Selection Button", middle).addValues (inputButtons); - declareEnum ("s-select", "Secondary Selection Button", cMiddle).addValues (inputButtons); + declareDouble ("p-navi-free-sensitivity", "Free Camera Sensitivity", 1/650.).setPrecision(5).setRange(0.0, 1.0); + declareBool ("p-navi-free-invert", "Invert Free Camera Mouse Input", false); + declareDouble ("p-navi-orbit-sensitivity", "Orbit Camera Sensitivity", 1/650.).setPrecision(5).setRange(0.0, 1.0); + declareBool ("p-navi-orbit-invert", "Invert Orbit Camera Mouse Input", false); + declareDouble ("s-navi-sensitivity", "Secondary Camera Movement Sensitivity", 50.0).setRange(-1000.0, 1000.0); + declareDouble ("navi-wheel-factor", "Camera Zoom Sensitivity", 8).setRange(-100.0, 100.0); + declareDouble ("navi-free-lin-speed", "Free Camera Linear Speed", 1000.0).setRange(1.0, 10000.0); + declareDouble ("navi-free-rot-speed", "Free Camera Rotational Speed", 3.14 / 2).setRange(0.001, 6.28); + declareDouble ("navi-free-speed-mult", "Free Camera Speed Multiplier (from Modifier)", 8).setRange(0.001, 1000.0); + declareDouble ("navi-orbit-rot-speed", "Orbital Camera Rotational Speed", 3.14 / 4).setRange(0.001, 6.28); + declareDouble ("navi-orbit-speed-mult", "Orbital Camera Speed Multiplier (from Modifier)", 4).setRange(0.001, 1000.0); declareSeparator(); declareBool ("context-select", "Context Sensitive Selection", false); declareDouble ("drag-factor", "Mouse sensitivity during drag operations", 1.0). @@ -186,6 +188,7 @@ void CSMPrefs::State::declare() "Shift-acceleration factor during drag operations", 4.0). setTooltip ("Acceleration factor during drag operations while holding down shift"). setRange (0.001, 100.0); + declareDouble ("rotate-factor", "Free rotation factor", 0.007).setPrecision(4).setRange(0.0001, 0.1); declareCategory ("Tooltips"); declareBool ("scene", "Show Tooltips in 3D scenes", true); @@ -210,6 +213,119 @@ void CSMPrefs::State::declare() addValues (insertOutsideCell); declareEnum ("outside-visible-drop", "Handling drops outside of visible cells", showAndInsert). addValues (insertOutsideVisibleCell); + + declareCategory ("Key Bindings"); + + declareSubcategory ("Document"); + declareShortcut ("document-file-newgame", "New Game", QKeySequence(Qt::ControlModifier | Qt::Key_N)); + declareShortcut ("document-file-newaddon", "New Addon", QKeySequence()); + declareShortcut ("document-file-open", "Open", QKeySequence(Qt::ControlModifier | Qt::Key_O)); + declareShortcut ("document-file-save", "Save", QKeySequence(Qt::ControlModifier | Qt::Key_S)); + declareShortcut ("document-file-verify", "Verify", QKeySequence()); + declareShortcut ("document-file-merge", "Merge", QKeySequence()); + declareShortcut ("document-file-errorlog", "Open Load Error Log", QKeySequence()); + declareShortcut ("document-file-metadata", "Meta Data", QKeySequence()); + declareShortcut ("document-file-close", "Close Document", QKeySequence(Qt::ControlModifier | Qt::Key_W)); + declareShortcut ("document-file-exit", "Exit Application", QKeySequence(Qt::ControlModifier | Qt::Key_Q)); + declareShortcut ("document-edit-undo", "Undo", QKeySequence(Qt::ControlModifier | Qt::Key_Z)); + declareShortcut ("document-edit-redo", "Redo", QKeySequence(Qt::ControlModifier | Qt::ShiftModifier | Qt::Key_Z)); + declareShortcut ("document-edit-preferences", "Open Preferences", QKeySequence()); + declareShortcut ("document-edit-search", "Search", QKeySequence(Qt::ControlModifier | Qt::Key_F)); + declareShortcut ("document-view-newview", "New View", QKeySequence()); + declareShortcut ("document-view-statusbar", "Toggle Status Bar", QKeySequence()); + declareShortcut ("document-view-filters", "Open Filter List", QKeySequence()); + declareShortcut ("document-world-regions", "Open Region List", QKeySequence()); + declareShortcut ("document-world-cells", "Open Cell List", QKeySequence()); + declareShortcut ("document-world-referencables", "Open Object List", QKeySequence()); + declareShortcut ("document-world-references", "Open Instance List", QKeySequence()); + declareShortcut ("document-world-pathgrid", "Open Pathgrid List", QKeySequence()); + declareShortcut ("document-world-regionmap", "Open Region Map", QKeySequence()); + declareShortcut ("document-mechanics-globals", "Open Global List", QKeySequence()); + declareShortcut ("document-mechanics-gamesettings", "Open Game Settings", QKeySequence()); + declareShortcut ("document-mechanics-scripts", "Open Script List", QKeySequence()); + declareShortcut ("document-mechanics-spells", "Open Spell List", QKeySequence()); + declareShortcut ("document-mechanics-enchantments", "Open Enchantment List", QKeySequence()); + declareShortcut ("document-mechanics-magiceffects", "Open Magic Effect List", QKeySequence()); + declareShortcut ("document-mechanics-startscripts", "Open Start Script List", QKeySequence()); + declareShortcut ("document-character-skills", "Open Skill List", QKeySequence()); + declareShortcut ("document-character-classes", "Open Class List", QKeySequence()); + declareShortcut ("document-character-factions", "Open Faction List", QKeySequence()); + declareShortcut ("document-character-races", "Open Race List", QKeySequence()); + declareShortcut ("document-character-birthsigns", "Open Birthsign List", QKeySequence()); + declareShortcut ("document-character-topics", "Open Topic List", QKeySequence()); + declareShortcut ("document-character-journals", "Open Journal List", QKeySequence()); + declareShortcut ("document-character-topicinfos", "Open Topic Info List", QKeySequence()); + declareShortcut ("document-character-journalinfos", "Open Journal Info List", QKeySequence()); + declareShortcut ("document-character-bodyparts", "Open Body Part List", QKeySequence()); + declareShortcut ("document-assets-sounds", "Open Sound Asset List", QKeySequence()); + declareShortcut ("document-assets-soundgens", "Open Sound Generator List", QKeySequence()); + declareShortcut ("document-assets-meshes", "Open Mesh Asset List", QKeySequence()); + declareShortcut ("document-assets-icons", "Open Icon Asset List", QKeySequence()); + declareShortcut ("document-assets-music", "Open Music Asset List", QKeySequence()); + declareShortcut ("document-assets-soundres", "Open Sound File List", QKeySequence()); + declareShortcut ("document-assets-textures", "Open Texture Asset List", QKeySequence()); + declareShortcut ("document-assets-videos", "Open Video Asset List", QKeySequence()); + declareShortcut ("document-debug-run", "Run Debug", QKeySequence()); + declareShortcut ("document-debug-shutdown", "Stop Debug", QKeySequence()); + declareShortcut ("document-debug-runlog", "Open Run Log", QKeySequence()); + + declareSubcategory ("Table"); + declareShortcut ("table-edit", "Edit Record", QKeySequence()); + declareShortcut ("table-add", "Add Row/Record", QKeySequence(Qt::ShiftModifier | Qt::Key_A)); + declareShortcut ("table-clone", "Clone Record", QKeySequence(Qt::ShiftModifier | Qt::Key_D)); + declareShortcut ("table-revert", "Revert Record", QKeySequence()); + declareShortcut ("table-remove", "Remove Row/Record", QKeySequence(Qt::Key_Delete)); + declareShortcut ("table-moveup", "Move Record Up", QKeySequence()); + declareShortcut ("table-movedown", "Move Record Down", QKeySequence()); + declareShortcut ("table-view", "View Record", QKeySequence()); + declareShortcut ("table-preview", "Preview Record", QKeySequence()); + declareShortcut ("table-extendeddelete", "Extended Record Deletion", QKeySequence()); + declareShortcut ("table-extendedrevert", "Extended Record Revertion", QKeySequence()); + + declareSubcategory ("Report Table"); + declareShortcut ("reporttable-show", "Show Report", QKeySequence()); + declareShortcut ("reporttable-remove", "Remove Report", QKeySequence(Qt::Key_Delete)); + declareShortcut ("reporttable-replace", "Replace Report", QKeySequence()); + declareShortcut ("reporttable-refresh", "Refresh Report", QKeySequence()); + + declareSubcategory ("Scene"); + declareShortcut ("scene-navi-primary", "Camera Rotation From Mouse Movement", QKeySequence(Qt::LeftButton)); + declareShortcut ("scene-navi-secondary", "Camera Translation From Mouse Movement", + QKeySequence(Qt::ControlModifier | (int)Qt::LeftButton)); + declareShortcut ("scene-edit-primary", "Primary Edit", QKeySequence(Qt::RightButton)); + declareShortcut ("scene-edit-secondary", "Secondary Edit", + QKeySequence(Qt::ControlModifier | (int)Qt::RightButton)); + declareShortcut ("scene-select-primary", "Primary Select", QKeySequence(Qt::MiddleButton)); + declareShortcut ("scene-select-secondary", "Secondary Select", + QKeySequence(Qt::ControlModifier | (int)Qt::MiddleButton)); + declareModifier ("scene-speed-modifier", "Speed Modifier", Qt::Key_Shift); + declareShortcut ("scene-load-cam-cell", "Load Camera Cell", QKeySequence(Qt::KeypadModifier | Qt::Key_5)); + declareShortcut ("scene-load-cam-eastcell", "Load East Cell", QKeySequence(Qt::KeypadModifier | Qt::Key_6)); + declareShortcut ("scene-load-cam-northcell", "Load North Cell", QKeySequence(Qt::KeypadModifier | Qt::Key_8)); + declareShortcut ("scene-load-cam-westcell", "Load West Cell", QKeySequence(Qt::KeypadModifier | Qt::Key_4)); + declareShortcut ("scene-load-cam-southcell", "Load South Cell", QKeySequence(Qt::KeypadModifier | Qt::Key_2)); + declareShortcut ("scene-edit-abort", "Abort", QKeySequence(Qt::Key_Escape)); + declareShortcut ("scene-focus-toolbar", "Toggle Toolbar Focus", QKeySequence(Qt::Key_T)); + declareShortcut ("scene-render-stats", "Debug Rendering Stats", QKeySequence(Qt::Key_F3)); + + declareSubcategory ("1st/Free Camera"); + declareShortcut ("free-forward", "Forward", QKeySequence(Qt::Key_W)); + declareShortcut ("free-backward", "Backward", QKeySequence(Qt::Key_S)); + declareShortcut ("free-left", "Left", QKeySequence(Qt::Key_A)); + declareShortcut ("free-right", "Right", QKeySequence(Qt::Key_D)); + declareShortcut ("free-roll-left", "Roll Left", QKeySequence(Qt::Key_Q)); + declareShortcut ("free-roll-right", "Roll Right", QKeySequence(Qt::Key_E)); + declareShortcut ("free-speed-mode", "Toggle Speed Mode", QKeySequence(Qt::Key_F)); + + declareSubcategory ("Orbit Camera"); + declareShortcut ("orbit-up", "Up", QKeySequence(Qt::Key_W)); + declareShortcut ("orbit-down", "Down", QKeySequence(Qt::Key_S)); + declareShortcut ("orbit-left", "Left", QKeySequence(Qt::Key_A)); + declareShortcut ("orbit-right", "Right", QKeySequence(Qt::Key_D)); + declareShortcut ("orbit-roll-left", "Roll Left", QKeySequence(Qt::Key_Q)); + declareShortcut ("orbit-roll-right", "Roll Right", QKeySequence(Qt::Key_E)); + declareShortcut ("orbit-speed-mode", "Toggle Speed Mode", QKeySequence(Qt::Key_F)); + declareShortcut ("orbit-center-selection", "Center On Selected", QKeySequence(Qt::Key_C)); } void CSMPrefs::State::declareCategory (const std::string& key) @@ -326,6 +442,50 @@ CSMPrefs::ColourSetting& CSMPrefs::State::declareColour (const std::string& key, return *setting; } +CSMPrefs::ShortcutSetting& CSMPrefs::State::declareShortcut (const std::string& key, const std::string& label, + const QKeySequence& default_) +{ + if (mCurrentCategory==mCategories.end()) + throw std::logic_error ("no category for setting"); + + std::string seqStr = getShortcutManager().convertToString(default_); + setDefault (key, seqStr); + + // Setup with actual data + QKeySequence sequence; + + getShortcutManager().convertFromString(mSettings.getString(key, mCurrentCategory->second.getKey()), sequence); + getShortcutManager().setSequence(key, sequence); + + CSMPrefs::ShortcutSetting *setting = new CSMPrefs::ShortcutSetting (&mCurrentCategory->second, &mSettings, &mMutex, + key, label); + mCurrentCategory->second.addSetting (setting); + + return *setting; +} + +CSMPrefs::ModifierSetting& CSMPrefs::State::declareModifier(const std::string& key, const std::string& label, + int default_) +{ + if (mCurrentCategory==mCategories.end()) + throw std::logic_error ("no category for setting"); + + std::string modStr = getShortcutManager().convertToString(default_); + setDefault (key, modStr); + + // Setup with actual data + int modifier; + + getShortcutManager().convertFromString(mSettings.getString(key, mCurrentCategory->second.getKey()), modifier); + getShortcutManager().setModifier(key, modifier); + + CSMPrefs::ModifierSetting *setting = new CSMPrefs::ModifierSetting (&mCurrentCategory->second, &mSettings, &mMutex, + key, label); + mCurrentCategory->second.addSetting (setting); + + return *setting; +} + void CSMPrefs::State::declareSeparator() { if (mCurrentCategory==mCategories.end()) @@ -337,6 +497,17 @@ void CSMPrefs::State::declareSeparator() mCurrentCategory->second.addSetting (setting); } +void CSMPrefs::State::declareSubcategory(const std::string& label) +{ + if (mCurrentCategory==mCategories.end()) + throw std::logic_error ("no category for setting"); + + CSMPrefs::Setting *setting = + new CSMPrefs::Setting (&mCurrentCategory->second, &mSettings, &mMutex, "", label); + + mCurrentCategory->second.addSetting (setting); +} + void CSMPrefs::State::setDefault (const std::string& key, const std::string& default_) { Settings::CategorySetting fullKey (mCurrentCategory->second.getKey(), key); @@ -355,10 +526,10 @@ CSMPrefs::State::State (const Files::ConfigurationManager& configurationManager) if (sThis) throw std::logic_error ("An instance of CSMPRefs::State already exists"); + sThis = this; + load(); declare(); - - sThis = this; } CSMPrefs::State::~State() @@ -382,6 +553,11 @@ CSMPrefs::State::Iterator CSMPrefs::State::end() return mCategories.end(); } +CSMPrefs::ShortcutManager& CSMPrefs::State::getShortcutManager() +{ + return mShortcutManager; +} + CSMPrefs::Category& CSMPrefs::State::operator[] (const std::string& key) { Iterator iter = mCategories.find (key); diff --git a/apps/opencs/model/prefs/state.hpp b/apps/opencs/model/prefs/state.hpp index bcd76c671..1e46c68ee 100644 --- a/apps/opencs/model/prefs/state.hpp +++ b/apps/opencs/model/prefs/state.hpp @@ -1,4 +1,4 @@ -#ifndef CSV_PREFS_STATE_H +#ifndef CSM_PREFS_STATE_H #define CSM_PREFS_STATE_H #include @@ -16,6 +16,7 @@ #include "category.hpp" #include "setting.hpp" #include "enumsetting.hpp" +#include "shortcutmanager.hpp" class QColor; @@ -25,6 +26,8 @@ namespace CSMPrefs class DoubleSetting; class BoolSetting; class ColourSetting; + class ShortcutSetting; + class ModifierSetting; /// \brief User settings state /// @@ -45,6 +48,7 @@ namespace CSMPrefs const std::string mConfigFile; const Files::ConfigurationManager& mConfigurationManager; + ShortcutManager mShortcutManager; Settings::Manager mSettings; Collection mCategories; Iterator mCurrentCategory; @@ -71,8 +75,15 @@ namespace CSMPrefs ColourSetting& declareColour (const std::string& key, const std::string& label, QColor default_); + ShortcutSetting& declareShortcut (const std::string& key, const std::string& label, + const QKeySequence& default_); + + ModifierSetting& declareModifier(const std::string& key, const std::string& label, int modifier_); + void declareSeparator(); + void declareSubcategory(const std::string& label); + void setDefault (const std::string& key, const std::string& default_); public: @@ -87,6 +98,8 @@ namespace CSMPrefs Iterator end(); + ShortcutManager& getShortcutManager(); + Category& operator[](const std::string& key); void update (const Setting& setting); diff --git a/apps/opencs/model/tools/journalcheck.cpp b/apps/opencs/model/tools/journalcheck.cpp new file mode 100644 index 000000000..bdd14ddf0 --- /dev/null +++ b/apps/opencs/model/tools/journalcheck.cpp @@ -0,0 +1,79 @@ +#include "journalcheck.hpp" + +#include +#include + +CSMTools::JournalCheckStage::JournalCheckStage(const CSMWorld::IdCollection &journals, + const CSMWorld::InfoCollection& journalInfos) + : mJournals(journals), mJournalInfos(journalInfos) +{} + +int CSMTools::JournalCheckStage::setup() +{ + return mJournals.getSize(); +} + +void CSMTools::JournalCheckStage::perform(int stage, CSMDoc::Messages& messages) +{ + const CSMWorld::Record &journalRecord = mJournals.getRecord(stage); + + if (journalRecord.isDeleted()) + return; + + const ESM::Dialogue &journal = journalRecord.get(); + int statusNamedCount = 0; + int totalInfoCount = 0; + std::set questIndices; + + CSMWorld::InfoCollection::Range range = mJournalInfos.getTopicRange(journal.mId); + + for (CSMWorld::InfoCollection::RecordConstIterator it = range.first; it != range.second; ++it) + { + const CSMWorld::Record infoRecord = (*it); + + if (infoRecord.isDeleted()) + continue; + + const CSMWorld::Info& journalInfo = infoRecord.get(); + + totalInfoCount += 1; + + if (journalInfo.mQuestStatus == ESM::DialInfo::QS_Name) + { + statusNamedCount += 1; + } + + if (journalInfo.mResponse.empty()) + { + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_JournalInfo, journalInfo.mId); + + messages.add(id, "Journal Info: missing description", "", CSMDoc::Message::Severity_Warning); + } + + std::pair::iterator, bool> result = questIndices.insert(journalInfo.mData.mJournalIndex); + + // Duplicate index + if (result.second == false) + { + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_JournalInfo, journalInfo.mId); + + std::ostringstream stream; + stream << "Journal: duplicated quest index " << journalInfo.mData.mJournalIndex; + + messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error); + } + } + + if (totalInfoCount == 0) + { + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Journal, journal.mId); + + messages.add(id, "Journal: no defined Journal Infos", "", CSMDoc::Message::Severity_Warning); + } + else if (statusNamedCount > 1) + { + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Journal, journal.mId); + + messages.add(id, "Journal: multiple infos with quest status \"Named\"", "", CSMDoc::Message::Severity_Error); + } +} diff --git a/apps/opencs/model/tools/journalcheck.hpp b/apps/opencs/model/tools/journalcheck.hpp new file mode 100644 index 000000000..c9f619698 --- /dev/null +++ b/apps/opencs/model/tools/journalcheck.hpp @@ -0,0 +1,35 @@ +#ifndef CSM_TOOLS_JOURNALCHECK_H +#define CSM_TOOLS_JOURNALCHECK_H + +#include + +#include "../world/idcollection.hpp" +#include "../world/infocollection.hpp" + +#include "../doc/stage.hpp" + +namespace CSMTools +{ + /// \brief VerifyStage: make sure that journal infos are good + class JournalCheckStage : public CSMDoc::Stage + { + public: + + JournalCheckStage(const CSMWorld::IdCollection& journals, + const CSMWorld::InfoCollection& journalInfos); + + virtual int setup(); + ///< \return number of steps + + virtual void perform(int stage, CSMDoc::Messages& messages); + ///< Messages resulting from this stage will be appended to \a messages + + private: + + const CSMWorld::IdCollection& mJournals; + const CSMWorld::InfoCollection& mJournalInfos; + + }; +} + +#endif diff --git a/apps/opencs/model/tools/mergestages.cpp b/apps/opencs/model/tools/mergestages.cpp index f52e8886f..176d35914 100644 --- a/apps/opencs/model/tools/mergestages.cpp +++ b/apps/opencs/model/tools/mergestages.cpp @@ -99,6 +99,7 @@ void CSMTools::MergeReferencesStage::perform (int stage, CSMDoc::Messages& messa ref.mRefNum.mIndex = mIndex[Misc::StringUtils::lowerCase (ref.mCell)]++; ref.mRefNum.mContentFile = 0; + ref.mNew = false; CSMWorld::Record newRecord ( CSMWorld::RecordBase::State_ModifiedOnly, 0, &ref); @@ -128,7 +129,7 @@ void CSMTools::ListLandTexturesMergeStage::perform (int stage, CSMDoc::Messages& // make sure record is loaded land.loadData (ESM::Land::DATA_VHGT | ESM::Land::DATA_VNML | - ESM::Land::DATA_VCLR | ESM::Land::DATA_VTEX | ESM::Land::DATA_WNAM); + ESM::Land::DATA_VCLR | ESM::Land::DATA_VTEX); if (const ESM::Land::LandData *data = land.getLandData (ESM::Land::DATA_VTEX)) { @@ -183,7 +184,7 @@ void CSMTools::MergeLandTexturesStage::perform (int stage, CSMDoc::Messages& mes CSMWorld::LandTexture texture = mState.mSource.getData().getLandTextures().getRecord (index).get(); - std::ostringstream stream; + stream.clear(); stream << mNext->second-1 << "_0"; texture.mIndex = mNext->second-1; @@ -220,7 +221,7 @@ void CSMTools::MergeLandStage::perform (int stage, CSMDoc::Messages& messages) const CSMWorld::Land& land = record.get(); land.loadData (ESM::Land::DATA_VCLR | ESM::Land::DATA_VHGT | ESM::Land::DATA_VNML | - ESM::Land::DATA_VTEX | ESM::Land::DATA_WNAM); + ESM::Land::DATA_VTEX); CSMWorld::Land newLand (land); diff --git a/apps/opencs/model/tools/pathgridcheck.cpp b/apps/opencs/model/tools/pathgridcheck.cpp index 69ee5a809..3cd4a1b09 100644 --- a/apps/opencs/model/tools/pathgridcheck.cpp +++ b/apps/opencs/model/tools/pathgridcheck.cpp @@ -70,20 +70,6 @@ void CSMTools::PathgridCheckStage::perform (int stage, CSMDoc::Messages& message for (unsigned int i = 0; i < pathgrid.mPoints.size(); ++i) { - // check the connection number for each point matches the edge connections - if (pathgrid.mPoints[i].mConnectionNum > pointList[i].mConnectionNum) - { - std::ostringstream ss; - ss << " has has less edges than expected for point " << i; - messages.add (id, pathgrid.mId + ss.str(), "", CSMDoc::Message::Severity_Error); - } - else if (pathgrid.mPoints[i].mConnectionNum < pointList[i].mConnectionNum) - { - std::ostringstream ss; - ss << " has has more edges than expected for point " << i; - messages.add (id, pathgrid.mId + ss.str(), "", CSMDoc::Message::Severity_Error); - } - // check that edges are bidirectional bool foundReverse = false; for (unsigned int j = 0; j < pointList[i].mOtherIndex.size(); ++j) diff --git a/apps/opencs/model/tools/referenceablecheck.cpp b/apps/opencs/model/tools/referenceablecheck.cpp index ce78e52b2..4dd3e1edf 100644 --- a/apps/opencs/model/tools/referenceablecheck.cpp +++ b/apps/opencs/model/tools/referenceablecheck.cpp @@ -425,7 +425,7 @@ void CSMTools::ReferenceableCheckStage::creatureCheck ( //stats checks if (creature.mData.mLevel < 1) - messages.push_back (std::make_pair (id, creature.mId + " has non-postive level")); + messages.push_back (std::make_pair (id, creature.mId + " has non-positive level")); if (creature.mData.mStrength < 0) messages.push_back (std::make_pair (id, creature.mId + " has negative strength")); @@ -659,7 +659,7 @@ void CSMTools::ReferenceableCheckStage::npcCheck ( { if ((npc.mFlags & ESM::NPC::Autocalc) == 0) //0x0010 = autocalculated flag { - messages.push_back (std::make_pair (id, npc.mId + " mNpdtType or flags mismatch!")); //should not happend? + messages.push_back (std::make_pair (id, npc.mId + " mNpdtType or flags mismatch!")); //should not happen? return; } @@ -915,7 +915,7 @@ void CSMTools::ReferenceableCheckStage::inventoryListCheck( id + " contains non-existing item (" + itemName + ")")); else { - // Needs to accomodate Containers, Creatures, and NPCs + // Needs to accommodate containers, creatures, and NPCs switch (localIndex.second) { case CSMWorld::UniversalId::Type_Potion: diff --git a/apps/opencs/model/tools/regioncheck.cpp b/apps/opencs/model/tools/regioncheck.cpp index 2fdff5f38..734861080 100644 --- a/apps/opencs/model/tools/regioncheck.cpp +++ b/apps/opencs/model/tools/regioncheck.cpp @@ -29,9 +29,16 @@ void CSMTools::RegionCheckStage::perform (int stage, CSMDoc::Messages& messages) // test for empty name if (region.mName.empty()) - messages.push_back (std::make_pair (id, region.mId + " has an empty name")); + messages.add(id, region.mId + " has an empty name", "", CSMDoc::Message::Severity_Error); /// \todo test that the ID in mSleeplist exists + // test that chances add up to 100 + int chances = region.mData.mClear + region.mData.mCloudy + region.mData.mFoggy + region.mData.mOvercast + + region.mData.mRain + region.mData.mThunder + region.mData.mAsh + region.mData.mBlight + + region.mData.mA + region.mData.mB; + if (chances != 100) + messages.add(id, "Weather chances do not add up to 100", "", CSMDoc::Message::Severity_Error); + /// \todo check data members that can't be edited in the table view } diff --git a/apps/opencs/model/tools/reportmodel.cpp b/apps/opencs/model/tools/reportmodel.cpp index 49de1d651..f9a1fdb0c 100644 --- a/apps/opencs/model/tools/reportmodel.cpp +++ b/apps/opencs/model/tools/reportmodel.cpp @@ -37,14 +37,18 @@ int CSMTools::ReportModel::columnCount (const QModelIndex & parent) const QVariant CSMTools::ReportModel::data (const QModelIndex & index, int role) const { - if (role!=Qt::DisplayRole) + if (role!=Qt::DisplayRole && role!=Qt::UserRole) return QVariant(); switch (index.column()) { case Column_Type: - return static_cast (mRows.at (index.row()).mId.getType()); + if(role == Qt::UserRole) + return QString::fromUtf8 ( + mRows.at (index.row()).mId.getTypeName().c_str()); + else + return static_cast (mRows.at (index.row()).mId.getType()); case Column_Id: { diff --git a/apps/opencs/model/tools/tools.cpp b/apps/opencs/model/tools/tools.cpp index e750092b9..f538a716e 100644 --- a/apps/opencs/model/tools/tools.cpp +++ b/apps/opencs/model/tools/tools.cpp @@ -30,6 +30,8 @@ #include "magiceffectcheck.hpp" #include "mergeoperation.hpp" #include "gmstcheck.hpp" +#include "topicinfocheck.hpp" +#include "journalcheck.hpp" CSMDoc::OperationHolder *CSMTools::Tools::get (int type) { @@ -111,9 +113,24 @@ CSMDoc::OperationHolder *CSMTools::Tools::getVerifier() mData.getReferenceables(), mData.getResources (CSMWorld::UniversalId::Type_Icons), mData.getResources (CSMWorld::UniversalId::Type_Textures))); - + mVerifierOperation->appendStage (new GmstCheckStage (mData.getGmsts())); + mVerifierOperation->appendStage (new TopicInfoCheckStage (mData.getTopicInfos(), + mData.getCells(), + mData.getClasses(), + mData.getFactions(), + mData.getGmsts(), + mData.getGlobals(), + mData.getJournals(), + mData.getRaces(), + mData.getRegions(), + mData.getTopics(), + mData.getReferenceables().getDataSet(), + mData.getResources (CSMWorld::UniversalId::Type_SoundsRes))); + + mVerifierOperation->appendStage (new JournalCheckStage(mData.getJournals(), mData.getJournalInfos())); + mVerifier.setOperation (mVerifierOperation); } diff --git a/apps/opencs/model/tools/topicinfocheck.cpp b/apps/opencs/model/tools/topicinfocheck.cpp new file mode 100644 index 000000000..05f02c763 --- /dev/null +++ b/apps/opencs/model/tools/topicinfocheck.cpp @@ -0,0 +1,441 @@ +#include "topicinfocheck.hpp" + +#include + +#include "../world/infoselectwrapper.hpp" + +CSMTools::TopicInfoCheckStage::TopicInfoCheckStage( + const CSMWorld::InfoCollection& topicInfos, + const CSMWorld::IdCollection& cells, + const CSMWorld::IdCollection& classes, + const CSMWorld::IdCollection& factions, + const CSMWorld::IdCollection& gmsts, + const CSMWorld::IdCollection& globals, + const CSMWorld::IdCollection& journals, + const CSMWorld::IdCollection& races, + const CSMWorld::IdCollection& regions, + const CSMWorld::IdCollection &topics, + const CSMWorld::RefIdData& referencables, + const CSMWorld::Resources& soundFiles) + : mTopicInfos(topicInfos), + mCells(cells), + mClasses(classes), + mFactions(factions), + mGameSettings(gmsts), + mGlobals(globals), + mJournals(journals), + mRaces(races), + mRegions(regions), + mTopics(topics), + mReferencables(referencables), + mSoundFiles(soundFiles) +{} + +int CSMTools::TopicInfoCheckStage::setup() +{ + // Generate list of cell names for reference checking + + mCellNames.clear(); + for (int i = 0; i < mCells.getSize(); ++i) + { + const CSMWorld::Record& cellRecord = mCells.getRecord(i); + + if (cellRecord.isDeleted()) + continue; + + mCellNames.insert(cellRecord.get().mName); + } + // Cell names can also include region names + for (int i = 0; i < mRegions.getSize(); ++i) + { + const CSMWorld::Record& regionRecord = mRegions.getRecord(i); + + if (regionRecord.isDeleted()) + continue; + + mCellNames.insert(regionRecord.get().mName); + } + // Default cell name + int index = mGameSettings.searchId("sDefaultCellname"); + if (index != -1) + { + const CSMWorld::Record& gmstRecord = mGameSettings.getRecord(index); + + if (!gmstRecord.isDeleted() && gmstRecord.get().mValue.getType() == ESM::VT_String) + { + mCellNames.insert(gmstRecord.get().mValue.getString()); + } + } + + return mTopicInfos.getSize(); +} + +void CSMTools::TopicInfoCheckStage::perform(int stage, CSMDoc::Messages& messages) +{ + const CSMWorld::Record& infoRecord = mTopicInfos.getRecord(stage); + + if (infoRecord.isDeleted()) + return; + + const CSMWorld::Info& topicInfo = infoRecord.get(); + + // There should always be a topic that matches + int topicIndex = mTopics.searchId(topicInfo.mTopicId); + + const CSMWorld::Record& topicRecord = mTopics.getRecord(topicIndex); + + if (topicRecord.isDeleted()) + return; + + const ESM::Dialogue& topic = topicRecord.get(); + + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_TopicInfo, topicInfo.mId); + + // Check fields + + if (!topicInfo.mActor.empty()) + { + verifyActor(topicInfo.mActor, id, messages); + } + + if (!topicInfo.mClass.empty()) + { + verifyId(topicInfo.mClass, mClasses, id, messages); + } + + if (!topicInfo.mCell.empty()) + { + verifyCell(topicInfo.mCell, id, messages); + } + + if (!topicInfo.mFaction.empty() && !topicInfo.mFactionLess) + { + if (verifyId(topicInfo.mFaction, mFactions, id, messages)) + { + verifyFactionRank(topicInfo.mFaction, topicInfo.mData.mRank, id, messages); + } + } + + if (!topicInfo.mPcFaction.empty()) + { + if (verifyId(topicInfo.mPcFaction, mFactions, id, messages)) + { + verifyFactionRank(topicInfo.mPcFaction, topicInfo.mData.mPCrank, id, messages); + } + } + + if (topicInfo.mData.mGender < -1 || topicInfo.mData.mGender > 1) + { + std::ostringstream stream; + messages.add(id, "Gender: Value is invalid", "", CSMDoc::Message::Severity_Error); + } + + if (!topicInfo.mRace.empty()) + { + verifyId(topicInfo.mRace, mRaces, id, messages); + } + + if (!topicInfo.mSound.empty()) + { + verifySound(topicInfo.mSound, id, messages); + } + + if (topicInfo.mResponse.empty() && topic.mType != ESM::Dialogue::Voice) + { + messages.add(id, "Response is empty", "", CSMDoc::Message::Severity_Warning); + } + + // Check info conditions + + for (std::vector::const_iterator it = topicInfo.mSelects.begin(); + it != topicInfo.mSelects.end(); ++it) + { + verifySelectStruct((*it), id, messages); + } +} + +// Verification functions + +bool CSMTools::TopicInfoCheckStage::verifyActor(const std::string& actor, const CSMWorld::UniversalId& id, + CSMDoc::Messages& messages) +{ + const std::string specifier = "Actor"; + + CSMWorld::RefIdData::LocalIndex index = mReferencables.searchId(actor); + + if (index.first == -1) + { + writeMissingIdError(specifier, actor, id, messages); + return false; + } + else if (mReferencables.getRecord(index).isDeleted()) + { + writeDeletedRecordError(specifier, actor, id, messages); + return false; + } + else if (index.second != CSMWorld::UniversalId::Type_Npc && index.second != CSMWorld::UniversalId::Type_Creature) + { + writeInvalidTypeError(specifier, actor, index.second, "NPC or Creature", id, messages); + return false; + } + + return true; +} + +bool CSMTools::TopicInfoCheckStage::verifyCell(const std::string& cell, const CSMWorld::UniversalId& id, + CSMDoc::Messages& messages) +{ + const std::string specifier = "Cell"; + + if (mCellNames.find(cell) == mCellNames.end()) + { + writeMissingIdError(specifier, cell, id, messages); + return false; + } + + return true; +} + +bool CSMTools::TopicInfoCheckStage::verifyFactionRank(const std::string& factionName, int rank, const CSMWorld::UniversalId& id, + CSMDoc::Messages& messages) +{ + if (rank < -1) + { + std::ostringstream stream; + stream << "Rank or PC Rank is set to " << rank << ", but should be set to -1 if no rank is required"; + + messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error); + return false; + } + + int index = mFactions.searchId(factionName); + + const ESM::Faction &faction = mFactions.getRecord(index).get(); + + int limit = 0; + for (; limit < 10; ++limit) + { + if (faction.mRanks[limit].empty()) + break; + } + + if (rank >= limit) + { + std::ostringstream stream; + stream << "Rank or PC Rank is set to " << rank << " which is more than the maximum of " << limit - 1 + << " for the " << factionName << " faction"; + + messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error); + return false; + } + + return true; +} + +bool CSMTools::TopicInfoCheckStage::verifyItem(const std::string& item, const CSMWorld::UniversalId& id, + CSMDoc::Messages& messages) +{ + const std::string specifier = "Item"; + + CSMWorld::RefIdData::LocalIndex index = mReferencables.searchId(item); + + if (index.first == -1) + { + writeMissingIdError(specifier, item, id, messages); + return false; + } + else if (mReferencables.getRecord(index).isDeleted()) + { + writeDeletedRecordError(specifier, item, id, messages); + return false; + } + else + { + switch (index.second) + { + case CSMWorld::UniversalId::Type_Potion: + case CSMWorld::UniversalId::Type_Apparatus: + case CSMWorld::UniversalId::Type_Armor: + case CSMWorld::UniversalId::Type_Book: + case CSMWorld::UniversalId::Type_Clothing: + case CSMWorld::UniversalId::Type_Ingredient: + case CSMWorld::UniversalId::Type_Light: + case CSMWorld::UniversalId::Type_Lockpick: + case CSMWorld::UniversalId::Type_Miscellaneous: + case CSMWorld::UniversalId::Type_Probe: + case CSMWorld::UniversalId::Type_Repair: + case CSMWorld::UniversalId::Type_Weapon: + case CSMWorld::UniversalId::Type_ItemLevelledList: + break; + + default: + writeInvalidTypeError(specifier, item, index.second, "Potion, Armor, Book, etc.", id, messages); + return false; + } + } + + return true; +} + +bool CSMTools::TopicInfoCheckStage::verifySelectStruct(const ESM::DialInfo::SelectStruct& select, + const CSMWorld::UniversalId& id, CSMDoc::Messages& messages) +{ + CSMWorld::ConstInfoSelectWrapper infoCondition(select); + + if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_None) + { + messages.add(id, "Invalid Info Condition: " + infoCondition.toString(), "", CSMDoc::Message::Severity_Error); + return false; + } + else if (!infoCondition.variantTypeIsValid()) + { + std::ostringstream stream; + stream << "Info Condition: Value for \"" << infoCondition.toString() << "\" has a type of "; + + switch (select.mValue.getType()) + { + case ESM::VT_None: stream << "None"; break; + case ESM::VT_Short: stream << "Short"; break; + case ESM::VT_Int: stream << "Int"; break; + case ESM::VT_Long: stream << "Long"; break; + case ESM::VT_Float: stream << "Float"; break; + case ESM::VT_String: stream << "String"; break; + default: stream << "Unknown"; break; + } + + messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error); + return false; + } + else if (infoCondition.conditionIsAlwaysTrue()) + { + std::ostringstream stream; + stream << "Info Condition: " << infoCondition.toString() << " is always true"; + + messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Warning); + return false; + } + else if (infoCondition.conditionIsNeverTrue()) + { + std::ostringstream stream; + stream << "Info Condition: " << infoCondition.toString() << " is never true"; + + messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Warning); + return false; + } + + // Id checks + if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_Global && + !verifyId(infoCondition.getVariableName(), mGlobals, id, messages)) + { + return false; + } + else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_Journal && + !verifyId(infoCondition.getVariableName(), mJournals, id, messages)) + { + return false; + } + else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_Item && + !verifyItem(infoCondition.getVariableName(), id, messages)) + { + return false; + } + else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_Dead && + !verifyActor(infoCondition.getVariableName(), id, messages)) + { + return false; + } + else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotId && + !verifyActor(infoCondition.getVariableName(), id, messages)) + { + return false; + } + else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotFaction && + !verifyId(infoCondition.getVariableName(), mFactions, id, messages)) + { + return false; + } + else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotClass && + !verifyId(infoCondition.getVariableName(), mClasses, id, messages)) + { + return false; + } + else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotRace && + !verifyId(infoCondition.getVariableName(), mRaces, id, messages)) + { + return false; + } + else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotCell && + !verifyCell(infoCondition.getVariableName(), id, messages)) + { + return false; + } + + return true; +} + +bool CSMTools::TopicInfoCheckStage::verifySound(const std::string& sound, const CSMWorld::UniversalId& id, + CSMDoc::Messages& messages) +{ + const std::string specifier = "Sound File"; + + if (mSoundFiles.searchId(sound) == -1) + { + writeMissingIdError(specifier, sound, id, messages); + return false; + } + + return true; +} + +template +bool CSMTools::TopicInfoCheckStage::verifyId(const std::string& name, const CSMWorld::IdCollection& collection, + const CSMWorld::UniversalId& id, CSMDoc::Messages& messages) +{ + int index = collection.searchId(name); + + if (index == -1) + { + writeMissingIdError(T::getRecordType(), name, id, messages); + return false; + } + else if (collection.getRecord(index).isDeleted()) + { + writeDeletedRecordError(T::getRecordType(), name, id, messages); + return false; + } + + return true; +} + +// Error functions + +void CSMTools::TopicInfoCheckStage::writeMissingIdError(const std::string& specifier, const std::string& missingId, + const CSMWorld::UniversalId& id, CSMDoc::Messages& messages) +{ + std::ostringstream stream; + stream << specifier << ": ID or name \"" << missingId << "\" could not be found"; + + messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error); +} + +void CSMTools::TopicInfoCheckStage::writeDeletedRecordError(const std::string& specifier, const std::string& recordId, + const CSMWorld::UniversalId& id, CSMDoc::Messages& messages) +{ + std::ostringstream stream; + stream << specifier << ": Deleted record with ID \"" << recordId << "\" is being referenced"; + + messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error); +} + +void CSMTools::TopicInfoCheckStage::writeInvalidTypeError(const std::string& specifier, const std::string& invalidId, + CSMWorld::UniversalId::Type invalidType, const std::string& expectedType, const CSMWorld::UniversalId& id, + CSMDoc::Messages& messages) +{ + CSMWorld::UniversalId tempId(invalidType, invalidId); + + std::ostringstream stream; + stream << specifier << ": invalid type of " << tempId.getTypeName() << " was found for referencable \"" + << invalidId << "\" (can be of type " << expectedType << ")"; + + messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error); +} diff --git a/apps/opencs/model/tools/topicinfocheck.hpp b/apps/opencs/model/tools/topicinfocheck.hpp new file mode 100644 index 000000000..510901dac --- /dev/null +++ b/apps/opencs/model/tools/topicinfocheck.hpp @@ -0,0 +1,95 @@ +#ifndef CSM_TOOLS_TOPICINFOCHECK_HPP +#define CSM_TOOLS_TOPICINFOCHECK_HPP + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "../world/cell.hpp" +#include "../world/idcollection.hpp" +#include "../world/infocollection.hpp" +#include "../world/refiddata.hpp" +#include "../world/resources.hpp" + +#include "../doc/stage.hpp" + +namespace CSMTools +{ + /// \brief VerifyStage: check topics + class TopicInfoCheckStage : public CSMDoc::Stage + { + public: + + TopicInfoCheckStage( + const CSMWorld::InfoCollection& topicInfos, + const CSMWorld::IdCollection& cells, + const CSMWorld::IdCollection& classes, + const CSMWorld::IdCollection& factions, + const CSMWorld::IdCollection& gmsts, + const CSMWorld::IdCollection& globals, + const CSMWorld::IdCollection& journals, + const CSMWorld::IdCollection& races, + const CSMWorld::IdCollection& regions, + const CSMWorld::IdCollection& topics, + const CSMWorld::RefIdData& referencables, + const CSMWorld::Resources& soundFiles); + + virtual int setup(); + ///< \return number of steps + + virtual void perform(int step, CSMDoc::Messages& messages); + ///< Messages resulting from this stage will be appended to \a messages + + private: + + const CSMWorld::InfoCollection& mTopicInfos; + + const CSMWorld::IdCollection& mCells; + const CSMWorld::IdCollection& mClasses; + const CSMWorld::IdCollection& mFactions; + const CSMWorld::IdCollection& mGameSettings; + const CSMWorld::IdCollection& mGlobals; + const CSMWorld::IdCollection& mJournals; + const CSMWorld::IdCollection& mRaces; + const CSMWorld::IdCollection& mRegions; + const CSMWorld::IdCollection& mTopics; + + const CSMWorld::RefIdData& mReferencables; + const CSMWorld::Resources& mSoundFiles; + + std::set mCellNames; + + // These return false when not successful and write an error + bool verifyActor(const std::string& name, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); + bool verifyCell(const std::string& name, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); + bool verifyFactionRank(const std::string& name, int rank, const CSMWorld::UniversalId& id, + CSMDoc::Messages& messages); + bool verifyItem(const std::string& name, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); + bool verifySelectStruct(const ESM::DialInfo::SelectStruct& select, const CSMWorld::UniversalId& id, + CSMDoc::Messages& messages); + bool verifySound(const std::string& name, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); + + template + bool verifyId(const std::string& name, const CSMWorld::IdCollection& collection, + const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); + + // Common error messages + void writeMissingIdError(const std::string& specifier, const std::string& missingId, + const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); + + void writeDeletedRecordError(const std::string& specifier, const std::string& recordId, + const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); + + void writeInvalidTypeError(const std::string& specifier, const std::string& invalidId, + CSMWorld::UniversalId::Type invalidType, const std::string& expectedType, + const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); + }; +} + +#endif diff --git a/apps/opencs/model/world/cellcoordinates.cpp b/apps/opencs/model/world/cellcoordinates.cpp index 3ef3e6c69..abb3bc82e 100644 --- a/apps/opencs/model/world/cellcoordinates.cpp +++ b/apps/opencs/model/world/cellcoordinates.cpp @@ -1,5 +1,7 @@ #include "cellcoordinates.hpp" +#include + #include #include @@ -7,6 +9,9 @@ CSMWorld::CellCoordinates::CellCoordinates() : mX (0), mY (0) {} CSMWorld::CellCoordinates::CellCoordinates (int x, int y) : mX (x), mY (y) {} +CSMWorld::CellCoordinates::CellCoordinates (const std::pair& coordinates) +: mX (coordinates.first), mY (coordinates.second) {} + int CSMWorld::CellCoordinates::getX() const { return mX; @@ -32,11 +37,16 @@ std::string CSMWorld::CellCoordinates::getId (const std::string& worldspace) con return stream.str(); } +bool CSMWorld::CellCoordinates::isExteriorCell (const std::string& id) +{ + return (!id.empty() && id[0]=='#'); +} + std::pair CSMWorld::CellCoordinates::fromId ( const std::string& id) { // no worldspace for now, needs to be changed for 1.1 - if (!id.empty() && id[0]=='#') + if (isExteriorCell(id)) { int x, y; char ignore; @@ -49,6 +59,13 @@ std::pair CSMWorld::CellCoordinates::fromId ( return std::make_pair (CellCoordinates(), false); } +std::pair CSMWorld::CellCoordinates::coordinatesToCellIndex (float x, float y) +{ + const int cellSize = 8192; + + return std::make_pair (std::floor (x/cellSize), std::floor (y/cellSize)); +} + bool CSMWorld::operator== (const CellCoordinates& left, const CellCoordinates& right) { return left.getX()==right.getX() && left.getY()==right.getY(); diff --git a/apps/opencs/model/world/cellcoordinates.hpp b/apps/opencs/model/world/cellcoordinates.hpp index 63db60c29..9207aede5 100644 --- a/apps/opencs/model/world/cellcoordinates.hpp +++ b/apps/opencs/model/world/cellcoordinates.hpp @@ -3,6 +3,7 @@ #include #include +#include #include @@ -19,6 +20,8 @@ namespace CSMWorld CellCoordinates (int x, int y); + CellCoordinates (const std::pair& coordinates); + int getX() const; int getY() const; @@ -29,11 +32,16 @@ namespace CSMWorld std::string getId (const std::string& worldspace) const; ///< Return the ID for the cell at these coordinates. + static bool isExteriorCell (const std::string& id); + /// \return first: CellCoordinates (or 0, 0 if cell does not have coordinates), /// second: is cell paged? /// /// \note The worldspace part of \a id is ignored static std::pair fromId (const std::string& id); + + /// \return cell coordinates such that given world coordinates are in it. + static std::pair coordinatesToCellIndex (float x, float y); }; bool operator== (const CellCoordinates& left, const CellCoordinates& right); diff --git a/apps/opencs/model/world/columnbase.hpp b/apps/opencs/model/world/columnbase.hpp index c75a3c2a1..d609a6253 100644 --- a/apps/opencs/model/world/columnbase.hpp +++ b/apps/opencs/model/world/columnbase.hpp @@ -85,6 +85,7 @@ namespace CSMWorld Display_Enchantment, //CONCRETE TYPES ENDS HERE + Display_UnsignedInteger8, Display_Integer, Display_Float, Display_Var, @@ -130,10 +131,14 @@ namespace CSMWorld Display_InfoCondComp, Display_String32, Display_LongString256, + Display_BookType, + Display_BloodType, + Display_EmitterType, Display_EffectSkill, // must display at least one, unlike Display_Skill Display_EffectAttribute, // must display at least one, unlike Display_Attribute Display_IngredEffectId, // display none allowed, unlike Display_EffectId + Display_GenderNpc, // must display at least one, unlike Display_Gender //top level columns that nest other columns Display_NestedHeader diff --git a/apps/opencs/model/world/columnimp.hpp b/apps/opencs/model/world/columnimp.hpp index 4e608dbbd..33a71e97a 100644 --- a/apps/opencs/model/world/columnimp.hpp +++ b/apps/opencs/model/world/columnimp.hpp @@ -1757,6 +1757,41 @@ namespace CSMWorld return true; } }; + + template + struct GenderNpcColumn : public Column + { + GenderNpcColumn() + : Column(Columns::ColumnId_GenderNpc, ColumnBase::Display_GenderNpc) + {} + + virtual QVariant get(const Record& record) const + { + // Implemented this way to allow additional gender types in the future. + if ((record.get().mData.mFlags & ESM::BodyPart::BPF_Female) == ESM::BodyPart::BPF_Female) + return 1; + + return 0; + } + + virtual void set(Record& record, const QVariant& data) + { + ESXRecordT record2 = record.get(); + + // Implemented this way to allow additional gender types in the future. + if (data.toInt() == 1) + record2.mData.mFlags = (record2.mData.mFlags & ~ESM::BodyPart::BPF_Female) | ESM::BodyPart::BPF_Female; + else + record2.mData.mFlags = record2.mData.mFlags & ~ESM::BodyPart::BPF_Female; + + record.setModified(record2); + } + + virtual bool isEditable() const + { + return true; + } + }; template struct EnchantmentTypeColumn : public Column diff --git a/apps/opencs/model/world/columns.cpp b/apps/opencs/model/world/columns.cpp index d0d3a1671..aeee0d208 100644 --- a/apps/opencs/model/world/columns.cpp +++ b/apps/opencs/model/world/columns.cpp @@ -3,6 +3,7 @@ #include #include "universalid.hpp" +#include "infoselectwrapper.hpp" namespace CSMWorld { @@ -97,7 +98,7 @@ namespace CSMWorld { ColumnId_ArmorType, "Armor Type" }, { ColumnId_Health, "Health" }, { ColumnId_ArmorValue, "Armor Value" }, - { ColumnId_Scroll, "Scroll" }, + { ColumnId_BookType, "Book Type" }, { ColumnId_ClothingType, "Clothing Type" }, { ColumnId_WeightCapacity, "Weight Capacity" }, { ColumnId_OrganicContainer, "Organic Container" }, @@ -111,8 +112,8 @@ namespace CSMWorld { ColumnId_Flies, "Flies" }, { ColumnId_Walks, "Walks" }, { ColumnId_Essential, "Essential" }, - { ColumnId_SkeletonBlood, "Skeleton Blood" }, - { ColumnId_MetalBlood, "Metal Blood" }, + { ColumnId_BloodType, "Blood Type" }, + { ColumnId_OpenSound, "Open Sound" }, { ColumnId_CloseSound, "Close Sound" }, { ColumnId_Duration, "Duration" }, @@ -122,10 +123,8 @@ namespace CSMWorld { ColumnId_Dynamic, "Dynamic" }, { ColumnId_Portable, "Portable" }, { ColumnId_NegativeLight, "Negative Light" }, - { ColumnId_Flickering, "Flickering" }, - { ColumnId_SlowFlickering, "Slow Flickering" }, - { ColumnId_Pulsing, "Pulsing" }, - { ColumnId_SlowPulsing, "Slow Pulsing" }, + { ColumnId_EmitterType, "Emitter Type" }, + { ColumnId_Fire, "Fire" }, { ColumnId_OffByDefault, "Off by default" }, { ColumnId_IsKey, "Is Key" }, @@ -273,8 +272,8 @@ namespace CSMWorld { ColumnId_InfoList, "Info List" }, { ColumnId_InfoCondition, "Info Conditions" }, { ColumnId_InfoCondFunc, "Function" }, - { ColumnId_InfoCondVar, "Func/Variable" }, - { ColumnId_InfoCondComp, "Comp" }, + { ColumnId_InfoCondVar, "Variable/Object" }, + { ColumnId_InfoCondComp, "Relation" }, { ColumnId_InfoCondValue, "Values" }, { ColumnId_OriginalCell, "Original Cell" }, @@ -284,6 +283,7 @@ namespace CSMWorld { ColumnId_NpcMisc, "NPC Misc" }, { ColumnId_Level, "Level" }, { ColumnId_NpcFactionID, "Faction ID" }, + { ColumnId_GenderNpc, "Gender"}, { ColumnId_Mana, "Mana" }, { ColumnId_Fatigue, "Fatigue" }, { ColumnId_NpcDisposition, "NPC Disposition" }, @@ -325,6 +325,12 @@ namespace CSMWorld { ColumnId_Idle7, "Idle 7" }, { ColumnId_Idle8, "Idle 8" }, + { ColumnId_RegionWeather, "Weather" }, + { ColumnId_WeatherName, "Type" }, + { ColumnId_WeatherChance, "Percent Chance" }, + + { ColumnId_Text, "Text" }, + { ColumnId_UseValue1, "Use value 1" }, { ColumnId_UseValue2, "Use value 2" }, { ColumnId_UseValue3, "Use value 3" }, @@ -546,16 +552,19 @@ namespace "AI Wander", "AI Travel", "AI Follow", "AI Escort", "AI Activate", 0 }; - static const char *sInfoCondFunc[] = + static const char *sBookType[] = + { + "Book", "Scroll", 0 + }; + + static const char *sBloodType[] = { - " ", "Function", "Global", "Local", "Journal", - "Item", "Dead", "Not ID", "Not Faction", "Not Class", - "Not Race", "Not Cell", "Not Local", 0 + "Default (Red)", "Skeleton Blood (White)", "Metal Blood (Golden)", 0 }; - static const char *sInfoCondComp[] = + static const char *sEmitterType[] = { - "!=", "<", "<=", "=", ">", ">=", 0 + "", "Flickering", "Flickering (Slow)", "Pulsing", "Pulsing (Slow)", 0 }; const char **getEnumNames (CSMWorld::Columns::ColumnId column) @@ -585,10 +594,11 @@ namespace case CSMWorld::Columns::ColumnId_EffectId: return sEffectId; case CSMWorld::Columns::ColumnId_PartRefType: return sPartRefType; case CSMWorld::Columns::ColumnId_AiPackageType: return sAiPackageType; - case CSMWorld::Columns::ColumnId_InfoCondFunc: return sInfoCondFunc; - // FIXME: don't have dynamic value enum delegate, use Display_String for now - //case CSMWorld::Columns::ColumnId_InfoCond: return sInfoCond; - case CSMWorld::Columns::ColumnId_InfoCondComp: return sInfoCondComp; + case CSMWorld::Columns::ColumnId_InfoCondFunc: return CSMWorld::ConstInfoSelectWrapper::FunctionEnumStrings; + case CSMWorld::Columns::ColumnId_InfoCondComp: return CSMWorld::ConstInfoSelectWrapper::RelationEnumStrings; + case CSMWorld::Columns::ColumnId_BookType: return sBookType; + case CSMWorld::Columns::ColumnId_BloodType: return sBloodType; + case CSMWorld::Columns::ColumnId_EmitterType: return sEmitterType; default: return 0; } diff --git a/apps/opencs/model/world/columns.hpp b/apps/opencs/model/world/columns.hpp index a504e5f65..b23d86367 100644 --- a/apps/opencs/model/world/columns.hpp +++ b/apps/opencs/model/world/columns.hpp @@ -92,7 +92,7 @@ namespace CSMWorld ColumnId_ArmorType = 77, ColumnId_Health = 78, ColumnId_ArmorValue = 79, - ColumnId_Scroll = 80, + ColumnId_BookType = 80, ColumnId_ClothingType = 81, ColumnId_WeightCapacity = 82, ColumnId_OrganicContainer = 83, @@ -107,8 +107,8 @@ namespace CSMWorld ColumnId_Flies = 92, ColumnId_Walks = 93, ColumnId_Essential = 94, - ColumnId_SkeletonBlood = 95, - ColumnId_MetalBlood = 96, + ColumnId_BloodType = 95, + // unused ColumnId_OpenSound = 97, ColumnId_CloseSound = 98, ColumnId_Duration = 99, @@ -118,10 +118,8 @@ namespace CSMWorld ColumnId_Dynamic = 103, ColumnId_Portable = 104, ColumnId_NegativeLight = 105, - ColumnId_Flickering = 106, - ColumnId_SlowFlickering = 107, - ColumnId_Pulsing = 108, - ColumnId_SlowPulsing = 109, + ColumnId_EmitterType = 106, + // unused (3x) ColumnId_Fire = 110, ColumnId_OffByDefault = 111, ColumnId_IsKey = 112, @@ -278,7 +276,7 @@ namespace CSMWorld ColumnId_NpcMisc = 251, ColumnId_Level = 252, ColumnId_NpcFactionID = 253, - // unused + ColumnId_GenderNpc = 254, ColumnId_Mana = 255, ColumnId_Fatigue = 256, ColumnId_NpcDisposition = 257, @@ -325,6 +323,12 @@ namespace CSMWorld ColumnId_Idle7 = 292, ColumnId_Idle8 = 293, + ColumnId_RegionWeather = 294, + ColumnId_WeatherName = 295, + ColumnId_WeatherChance = 296, + + ColumnId_Text = 297, + // Allocated to a separate value range, so we don't get a collision should we ever need // to extend the number of use values. ColumnId_UseValue1 = 0x10000, diff --git a/apps/opencs/model/world/commanddispatcher.cpp b/apps/opencs/model/world/commanddispatcher.cpp index a1fc980eb..ffbaa3dec 100644 --- a/apps/opencs/model/world/commanddispatcher.cpp +++ b/apps/opencs/model/world/commanddispatcher.cpp @@ -11,6 +11,7 @@ #include "record.hpp" #include "commands.hpp" #include "idtableproxymodel.hpp" +#include "commandmacro.hpp" std::vector CSMWorld::CommandDispatcher::getDeletableRecords() const { @@ -171,10 +172,9 @@ void CSMWorld::CommandDispatcher::executeModify (QAbstractItemModel *model, cons if (modifyCell.get()) { - mDocument.getUndoStack().beginMacro (modifyData->text()); - mDocument.getUndoStack().push (modifyData.release()); - mDocument.getUndoStack().push (modifyCell.release()); - mDocument.getUndoStack().endMacro(); + CommandMacro macro (mDocument.getUndoStack()); + macro.push (modifyData.release()); + macro.push (modifyCell.release()); } else mDocument.getUndoStack().push (modifyData.release()); @@ -194,9 +194,7 @@ void CSMWorld::CommandDispatcher::executeDelete() int columnIndex = model.findColumnIndex (Columns::ColumnId_Id); - if (rows.size()>1) - mDocument.getUndoStack().beginMacro (tr ("Delete multiple records")); - + CommandMacro macro (mDocument.getUndoStack(), rows.size()>1 ? "Delete multiple records" : ""); for (std::vector::const_iterator iter (rows.begin()); iter!=rows.end(); ++iter) { std::string id = model.data (model.getModelIndex (*iter, columnIndex)). @@ -204,7 +202,7 @@ void CSMWorld::CommandDispatcher::executeDelete() if (mId.getType() == UniversalId::Type_Referenceables) { - mDocument.getUndoStack().push ( new CSMWorld::DeleteCommand (model, id, + macro.push (new CSMWorld::DeleteCommand (model, id, static_cast(model.data (model.index ( model.getModelIndex (id, columnIndex).row(), model.findColumnIndex (CSMWorld::Columns::ColumnId_RecordType))).toInt()))); @@ -212,9 +210,6 @@ void CSMWorld::CommandDispatcher::executeDelete() else mDocument.getUndoStack().push (new CSMWorld::DeleteCommand (model, id)); } - - if (rows.size()>1) - mDocument.getUndoStack().endMacro(); } void CSMWorld::CommandDispatcher::executeRevert() @@ -231,25 +226,19 @@ void CSMWorld::CommandDispatcher::executeRevert() int columnIndex = model.findColumnIndex (Columns::ColumnId_Id); - if (rows.size()>1) - mDocument.getUndoStack().beginMacro (tr ("Revert multiple records")); - + CommandMacro macro (mDocument.getUndoStack(), rows.size()>1 ? "Revert multiple records" : ""); for (std::vector::const_iterator iter (rows.begin()); iter!=rows.end(); ++iter) { std::string id = model.data (model.getModelIndex (*iter, columnIndex)). toString().toUtf8().constData(); - mDocument.getUndoStack().push (new CSMWorld::RevertCommand (model, id)); + macro.push (new CSMWorld::RevertCommand (model, id)); } - - if (rows.size()>1) - mDocument.getUndoStack().endMacro(); } void CSMWorld::CommandDispatcher::executeExtendedDelete() { - if (mExtendedTypes.size()>1) - mDocument.getUndoStack().beginMacro (tr ("Extended delete of multiple records")); + CommandMacro macro (mDocument.getUndoStack(), mExtendedTypes.size()>1 ? tr ("Extended delete of multiple records") : ""); for (std::vector::const_iterator iter (mExtendedTypes.begin()); iter!=mExtendedTypes.end(); ++iter) @@ -276,20 +265,15 @@ void CSMWorld::CommandDispatcher::executeExtendedDelete() Misc::StringUtils::lowerCase (record.get().mCell))) continue; - mDocument.getUndoStack().push ( - new CSMWorld::DeleteCommand (model, record.get().mId)); + macro.push (new CSMWorld::DeleteCommand (model, record.get().mId)); } } } - - if (mExtendedTypes.size()>1) - mDocument.getUndoStack().endMacro(); } void CSMWorld::CommandDispatcher::executeExtendedRevert() { - if (mExtendedTypes.size()>1) - mDocument.getUndoStack().beginMacro (tr ("Extended revert of multiple records")); + CommandMacro macro (mDocument.getUndoStack(), mExtendedTypes.size()>1 ? tr ("Extended revert of multiple records") : ""); for (std::vector::const_iterator iter (mExtendedTypes.begin()); iter!=mExtendedTypes.end(); ++iter) @@ -313,12 +297,8 @@ void CSMWorld::CommandDispatcher::executeExtendedRevert() Misc::StringUtils::lowerCase (record.get().mCell))) continue; - mDocument.getUndoStack().push ( - new CSMWorld::RevertCommand (model, record.get().mId)); + macro.push (new CSMWorld::RevertCommand (model, record.get().mId)); } } } - - if (mExtendedTypes.size()>1) - mDocument.getUndoStack().endMacro(); } diff --git a/apps/opencs/model/world/commandmacro.cpp b/apps/opencs/model/world/commandmacro.cpp new file mode 100644 index 000000000..0bd74cbe2 --- /dev/null +++ b/apps/opencs/model/world/commandmacro.cpp @@ -0,0 +1,26 @@ + +#include "commandmacro.hpp" + +#include +#include + +CSMWorld::CommandMacro::CommandMacro (QUndoStack& undoStack, const QString& description) +: mUndoStack (undoStack), mDescription (description), mStarted (false) +{} + +CSMWorld::CommandMacro::~CommandMacro() +{ + if (mStarted) + mUndoStack.endMacro(); +} + +void CSMWorld::CommandMacro::push (QUndoCommand *command) +{ + if (!mStarted) + { + mUndoStack.beginMacro (mDescription.isEmpty() ? command->text() : mDescription); + mStarted = true; + } + + mUndoStack.push (command); +} diff --git a/apps/opencs/model/world/commandmacro.hpp b/apps/opencs/model/world/commandmacro.hpp new file mode 100644 index 000000000..b1f6301d9 --- /dev/null +++ b/apps/opencs/model/world/commandmacro.hpp @@ -0,0 +1,34 @@ +#ifndef CSM_WOLRD_COMMANDMACRO_H +#define CSM_WOLRD_COMMANDMACRO_H + +class QUndoStack; +class QUndoCommand; + +#include + +namespace CSMWorld +{ + class CommandMacro + { + QUndoStack& mUndoStack; + QString mDescription; + bool mStarted; + + /// not implemented + CommandMacro (const CommandMacro&); + + /// not implemented + CommandMacro& operator= (const CommandMacro&); + + public: + + /// If \a description is empty, the description of the first command is used. + CommandMacro (QUndoStack& undoStack, const QString& description = ""); + + ~CommandMacro(); + + void push (QUndoCommand *command); + }; +} + +#endif diff --git a/apps/opencs/model/world/commands.cpp b/apps/opencs/model/world/commands.cpp index 097e83f7c..5f9422376 100644 --- a/apps/opencs/model/world/commands.cpp +++ b/apps/opencs/model/world/commands.cpp @@ -8,9 +8,12 @@ #include #include +#include "cellcoordinates.hpp" +#include "idcollection.hpp" #include "idtable.hpp" #include "idtree.hpp" #include "nestedtablewrapper.hpp" +#include "pathgrid.hpp" CSMWorld::ModifyCommand::ModifyCommand (QAbstractItemModel& model, const QModelIndex& index, const QVariant& new_, QUndoCommand* parent) @@ -68,9 +71,6 @@ void CSMWorld::ModifyCommand::undo() void CSMWorld::CreateCommand::applyModifications() { - for (std::map::const_iterator iter (mValues.begin()); iter!=mValues.end(); ++iter) - mModel.setData (mModel.getModelIndex (mId, iter->first), iter->second); - if (!mNestedValues.empty()) { CSMWorld::IdTree *tree = dynamic_cast(&mModel); @@ -114,7 +114,7 @@ void CSMWorld::CreateCommand::setType (UniversalId::Type type) void CSMWorld::CreateCommand::redo() { - mModel.addRecord (mId, mType); + mModel.addRecordWithData (mId, mValues, mType); applyModifications(); } @@ -238,6 +238,29 @@ void CSMWorld::CloneCommand::undo() mModel.removeRow (mModel.getModelIndex (mId, 0).row()); } +CSMWorld::CreatePathgridCommand::CreatePathgridCommand(IdTable& model, const std::string& id, QUndoCommand *parent) + : CreateCommand(model, id, parent) +{ + setType(UniversalId::Type_Pathgrid); +} + +void CSMWorld::CreatePathgridCommand::redo() +{ + CreateCommand::redo(); + + Record record = static_cast& >(mModel.getRecord(mId)); + record.get().blank(); + record.get().mCell = mId; + + std::pair coords = CellCoordinates::fromId(mId); + if (coords.second) + { + record.get().mData.mX = coords.first.getX(); + record.get().mData.mY = coords.first.getY(); + } + + mModel.setRecord(mId, record, mType); +} CSMWorld::UpdateCellCommand::UpdateCellCommand (IdTable& model, int row, QUndoCommand *parent) : QUndoCommand (parent), mModel (model), mRow (row) diff --git a/apps/opencs/model/world/commands.hpp b/apps/opencs/model/world/commands.hpp index 23ffccbd7..b54a1d5ac 100644 --- a/apps/opencs/model/world/commands.hpp +++ b/apps/opencs/model/world/commands.hpp @@ -153,6 +153,15 @@ namespace CSMWorld virtual void undo(); }; + class CreatePathgridCommand : public CreateCommand + { + public: + + CreatePathgridCommand(IdTable& model, const std::string& id, QUndoCommand *parent = 0); + + virtual void redo(); + }; + /// \brief Update cell ID according to x/y-coordinates /// /// \note The new value will be calculated in the first call to redo instead of the diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 6eccb7483..5a59f19f7 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -10,6 +10,8 @@ #include #include +#include + #include "idtable.hpp" #include "idtree.hpp" #include "columnimp.hpp" @@ -59,11 +61,13 @@ int CSMWorld::Data::count (RecordBase::State state, const CollectionBase& collec return number; } -CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourcesManager, const Fallback::Map* fallback) +CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourcesManager, const Fallback::Map* fallback, const boost::filesystem::path& resDir) : mEncoder (encoding), mPathgrids (mCells), mRefs (mCells), mResourcesManager (resourcesManager), mFallbackMap(fallback), - mReader (0), mDialogue (0), mReaderIndex(0), mResourceSystem(new Resource::ResourceSystem(resourcesManager.getVFS())) + mReader (0), mDialogue (0), mReaderIndex(1), mResourceSystem(new Resource::ResourceSystem(resourcesManager.getVFS())) { + mResourceSystem->getSceneManager()->setShaderPath((resDir / "shaders").string()); + int index = 0; mGlobals.addColumn (new StringIdColumn); @@ -177,6 +181,14 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc mRegions.addColumn (new NameColumn); mRegions.addColumn (new MapColourColumn); mRegions.addColumn (new SleepListColumn); + // Region Weather + mRegions.addColumn (new NestedParentColumn (Columns::ColumnId_RegionWeather)); + index = mRegions.getColumns()-1; + mRegions.addAdapter (std::make_pair(&mRegions.getColumn(index), new RegionWeatherAdapter ())); + mRegions.getNestableColumn(index)->addColumn( + new NestedChildColumn (Columns::ColumnId_WeatherName, ColumnBase::Display_String, false)); + mRegions.getNestableColumn(index)->addColumn( + new NestedChildColumn (Columns::ColumnId_WeatherChance, ColumnBase::Display_UnsignedInteger8)); // Region Sounds mRegions.addColumn (new NestedParentColumn (Columns::ColumnId_RegionSounds)); index = mRegions.getColumns()-1; @@ -271,7 +283,7 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc new NestedChildColumn (Columns::ColumnId_InfoCondFunc, ColumnBase::Display_InfoCondFunc)); // FIXME: don't have dynamic value enum delegate, use Display_String for now mTopicInfos.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_InfoCondVar, ColumnBase::Display_String)); + new NestedChildColumn (Columns::ColumnId_InfoCondVar, ColumnBase::Display_InfoCondVar)); mTopicInfos.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_InfoCondComp, ColumnBase::Display_InfoCondComp)); mTopicInfos.getNestableColumn(index)->addColumn( @@ -351,7 +363,7 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc mBodyParts.addColumn (new FixedRecordTypeColumn (UniversalId::Type_BodyPart)); mBodyParts.addColumn (new BodyPartTypeColumn); mBodyParts.addColumn (new VampireColumn); - mBodyParts.addColumn (new FlagColumn (Columns::ColumnId_Female, ESM::BodyPart::BPF_Female)); + mBodyParts.addColumn(new GenderNpcColumn); mBodyParts.addColumn (new FlagColumn (Columns::ColumnId_Playable, ESM::BodyPart::BPF_NotPlayable, ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue, true)); @@ -887,9 +899,11 @@ int CSMWorld::Data::startLoading (const boost::filesystem::path& path, bool base mReader = new ESM::ESMReader; mReader->setEncoder (&mEncoder); - mReader->setIndex(mReaderIndex++); + mReader->setIndex((project || !base) ? 0 : mReaderIndex++); mReader->open (path.string()); + mContentFileNames.insert(std::make_pair(path.filename().string(), mReader->getIndex())); + mBase = base; mProject = project; @@ -902,6 +916,20 @@ int CSMWorld::Data::startLoading (const boost::filesystem::path& path, bool base mMetaData.setRecord (0, Record (RecordBase::State_ModifiedOnly, 0, &metaData)); } + // Fix uninitialized master data index + for (std::vector::const_iterator masterData = mReader->getGameFiles().begin(); + masterData != mReader->getGameFiles().end(); ++masterData) + { + std::map::iterator nameResult = mContentFileNames.find(masterData->name); + if (nameResult != mContentFileNames.end()) + { + ESM::Header::MasterData& hackedMasterData = const_cast(*masterData); + + + hackedMasterData.index = nameResult->second; + } + } + return mReader->getRecordCount(); } @@ -934,7 +962,7 @@ bool CSMWorld::Data::continueLoading (CSMDoc::Messages& messages) bool unhandledRecord = false; - switch (n.val) + switch (n.intval) { case ESM::REC_GLOB: mGlobals.load (*mReader, mBase); break; case ESM::REC_GMST: mGmsts.load (*mReader, mBase); break; @@ -965,7 +993,7 @@ bool CSMWorld::Data::continueLoading (CSMDoc::Messages& messages) if (index!=-1/* && !mBase*/) mLand.getRecord (index).get().loadData ( ESM::Land::DATA_VHGT | ESM::Land::DATA_VNML | ESM::Land::DATA_VCLR | - ESM::Land::DATA_VTEX | ESM::Land::DATA_WNAM); + ESM::Land::DATA_VTEX); break; } @@ -1046,7 +1074,7 @@ bool CSMWorld::Data::continueLoading (CSMDoc::Messages& messages) else { mTopics.load (record, mBase); - mDialogue = &mTopics.getRecord (record.mId).get(); + mDialogue = &mTopics.getRecord (record.mId).get(); } } diff --git a/apps/opencs/model/world/data.hpp b/apps/opencs/model/world/data.hpp index e5b8229c4..dc00a33d2 100644 --- a/apps/opencs/model/world/data.hpp +++ b/apps/opencs/model/world/data.hpp @@ -123,6 +123,8 @@ namespace CSMWorld std::vector > mReaders; + std::map mContentFileNames; + // not implemented Data (const Data&); Data& operator= (const Data&); @@ -138,7 +140,7 @@ namespace CSMWorld public: - Data (ToUTF8::FromType encoding, const ResourcesManager& resourcesManager, const Fallback::Map* fallback); + Data (ToUTF8::FromType encoding, const ResourcesManager& resourcesManager, const Fallback::Map* fallback, const boost::filesystem::path& resDir); virtual ~Data(); diff --git a/apps/opencs/model/world/defaultgmsts.cpp b/apps/opencs/model/world/defaultgmsts.cpp index f44e98411..da83a86be 100644 --- a/apps/opencs/model/world/defaultgmsts.cpp +++ b/apps/opencs/model/world/defaultgmsts.cpp @@ -1627,264 +1627,264 @@ const char * CSMWorld::DefaultGmsts::OptionalStrings[CSMWorld::DefaultGmsts::Opt const float CSMWorld::DefaultGmsts::FloatsDefaultValues[CSMWorld::DefaultGmsts::FloatCount] = { - 0.3, // fAIFleeFleeMult - 7.0, // fAIFleeHealthMult - 3.0, // fAIMagicSpellMult - 1.0, // fAIMeleeArmorMult - 1.0, // fAIMeleeSummWeaponMult - 2.0, // fAIMeleeWeaponMult - 5.0, // fAIRangeMagicSpellMult - 5.0, // fAIRangeMeleeWeaponMult - 2000.0, // fAlarmRadius - 1.0, // fAthleticsRunBonus - 40.0, // fAudioDefaultMaxDistance - 5.0, // fAudioDefaultMinDistance - 50.0, // fAudioMaxDistanceMult - 20.0, // fAudioMinDistanceMult - 60.0, // fAudioVoiceDefaultMaxDistance - 10.0, // fAudioVoiceDefaultMinDistance - 50.0, // fAutoPCSpellChance - 80.0, // fAutoSpellChance - 50.0, // fBargainOfferBase - -4.0, // fBargainOfferMulti - 24.0, // fBarterGoldResetDelay - 1.75, // fBaseRunMultiplier - 1.25, // fBlockStillBonus - 150.0, // fBribe1000Mod - 75.0, // fBribe100Mod - 35.0, // fBribe10Mod - 60.0, // fCombatAngleXY - 60.0, // fCombatAngleZ - 0.25, // fCombatArmorMinMult - -90.0, // fCombatBlockLeftAngle - 30.0, // fCombatBlockRightAngle - 4.0, // fCombatCriticalStrikeMult - 0.1, // fCombatDelayCreature - 0.1, // fCombatDelayNPC - 128.0, // fCombatDistance - 0.3, // fCombatDistanceWerewolfMod - 30.0, // fCombatForceSideAngle - 0.2, // fCombatInvisoMult - 1.5, // fCombatKODamageMult - 45.0, // fCombatTorsoSideAngle - 0.3, // fCombatTorsoStartPercent - 0.8, // fCombatTorsoStopPercent - 15.0, // fConstantEffectMult - 72.0, // fCorpseClearDelay - 72.0, // fCorpseRespawnDelay - 0.5, // fCrimeGoldDiscountMult - 0.9, // fCrimeGoldTurnInMult - 1.0, // fCrimeStealing - 0.5, // fDamageStrengthBase - 0.1, // fDamageStrengthMult - 5.0, // fDifficultyMult - 2.5, // fDiseaseXferChance - -10.0, // fDispAttacking - -1.0, // fDispBargainFailMod - 1.0, // fDispBargainSuccessMod - 0.0, // fDispCrimeMod - -10.0, // fDispDiseaseMod - 3.0, // fDispFactionMod - 1.0, // fDispFactionRankBase - 0.5, // fDispFactionRankMult - 1.0, // fDispositionMod - 50.0, // fDispPersonalityBase - 0.5, // fDispPersonalityMult - -25.0, // fDispPickPocketMod - 5.0, // fDispRaceMod - -0.5, // fDispStealing - -5.0, // fDispWeaponDrawn - 0.5, // fEffectCostMult - 0.1, // fElementalShieldMult - 3.0, // fEnchantmentChanceMult - 0.5, // fEnchantmentConstantChanceMult - 100.0, // fEnchantmentConstantDurationMult - 0.1, // fEnchantmentMult - 1000.0, // fEnchantmentValueMult - 0.3, // fEncumberedMoveEffect - 5.0, // fEncumbranceStrMult - 0.04, // fEndFatigueMult - 0.25, // fFallAcroBase - 0.01, // fFallAcroMult - 400.0, // fFallDamageDistanceMin - 0.0, // fFallDistanceBase - 0.07, // fFallDistanceMult - 2.0, // fFatigueAttackBase - 0.0, // fFatigueAttackMult - 1.25, // fFatigueBase - 4.0, // fFatigueBlockBase - 0.0, // fFatigueBlockMult - 5.0, // fFatigueJumpBase - 0.0, // fFatigueJumpMult - 0.5, // fFatigueMult - 2.5, // fFatigueReturnBase - 0.02, // fFatigueReturnMult - 5.0, // fFatigueRunBase - 2.0, // fFatigueRunMult - 1.5, // fFatigueSneakBase - 1.5, // fFatigueSneakMult - 0.0, // fFatigueSpellBase - 0.0, // fFatigueSpellCostMult - 0.0, // fFatigueSpellMult - 7.0, // fFatigueSwimRunBase - 0.0, // fFatigueSwimRunMult - 2.5, // fFatigueSwimWalkBase - 0.0, // fFatigueSwimWalkMult - 0.2, // fFightDispMult - 0.005, // fFightDistanceMultiplier - 50.0, // fFightStealing - 3000.0, // fFleeDistance - 512.0, // fGreetDistanceReset - 0.1, // fHandtoHandHealthPer - 1.0, // fHandToHandReach - 0.5, // fHoldBreathEndMult - 20.0, // fHoldBreathTime - 0.75, // fIdleChanceMultiplier - 1.0, // fIngredientMult - 0.5, // fInteriorHeadTrackMult - 128.0, // fJumpAcrobaticsBase - 4.0, // fJumpAcroMultiplier - 0.5, // fJumpEncumbranceBase - 1.0, // fJumpEncumbranceMultiplier - 0.5, // fJumpMoveBase - 0.5, // fJumpMoveMult - 1.0, // fJumpRunMultiplier - 0.5, // fKnockDownMult - 5.0, // fLevelMod - 0.1, // fLevelUpHealthEndMult - 0.6, // fLightMaxMod - 10.0, // fLuckMod - 10.0, // fMagesGuildTravel - 1.5, // fMagicCreatureCastDelay - 0.0167, // fMagicDetectRefreshRate - 1.0, // fMagicItemConstantMult - 1.0, // fMagicItemCostMult - 1.0, // fMagicItemOnceMult - 1.0, // fMagicItemPriceMult - 0.05, // fMagicItemRechargePerSecond - 1.0, // fMagicItemStrikeMult - 1.0, // fMagicItemUsedMult - 3.0, // fMagicStartIconBlink - 0.5, // fMagicSunBlockedMult - 0.75, // fMajorSkillBonus - 300.0, // fMaxFlySpeed - 0.5, // fMaxHandToHandMult - 400.0, // fMaxHeadTrackDistance - 200.0, // fMaxWalkSpeed - 300.0, // fMaxWalkSpeedCreature - 0.9, // fMedMaxMod - 0.1, // fMessageTimePerChar - 5.0, // fMinFlySpeed - 0.1, // fMinHandToHandMult - 1.0, // fMinorSkillBonus - 100.0, // fMinWalkSpeed - 5.0, // fMinWalkSpeedCreature - 1.25, // fMiscSkillBonus - 2.0, // fNPCbaseMagickaMult - 0.5, // fNPCHealthBarFade - 3.0, // fNPCHealthBarTime - 1.0, // fPCbaseMagickaMult - 0.3, // fPerDieRollMult - 5.0, // fPersonalityMod - 1.0, // fPerTempMult - -1.0, // fPickLockMult - 0.3, // fPickPocketMod - 20.0, // fPotionMinUsefulDuration - 0.5, // fPotionStrengthMult - 0.5, // fPotionT1DurMult - 1.5, // fPotionT1MagMult - 20.0, // fPotionT4BaseStrengthMult - 12.0, // fPotionT4EquipStrengthMult - 3000.0, // fProjectileMaxSpeed - 400.0, // fProjectileMinSpeed - 25.0, // fProjectileThrownStoreChance - 3.0, // fRepairAmountMult - 1.0, // fRepairMult - 1.0, // fReputationMod - 0.15, // fRestMagicMult - 0.0, // fSeriousWoundMult - 0.25, // fSleepRandMod - 0.3, // fSleepRestMod - -1.0, // fSneakBootMult - 0.5, // fSneakDistanceBase - 0.002, // fSneakDistanceMultiplier - 0.5, // fSneakNoViewMult - 1.0, // fSneakSkillMult - 0.75, // fSneakSpeedMultiplier - 1.0, // fSneakUseDelay - 500.0, // fSneakUseDist - 1.5, // fSneakViewMult - 3.0, // fSoulGemMult - 0.8, // fSpecialSkillBonus - 7.0, // fSpellMakingValueMult - 2.0, // fSpellPriceMult - 10.0, // fSpellValueMult - 0.25, // fStromWalkMult - 0.7, // fStromWindSpeed - 3.0, // fSuffocationDamage - 0.9, // fSwimHeightScale - 0.1, // fSwimRunAthleticsMult - 0.5, // fSwimRunBase - 0.02, // fSwimWalkAthleticsMult - 0.5, // fSwimWalkBase - 1.0, // fSwingBlockBase - 1.0, // fSwingBlockMult - 1000.0, // fTargetSpellMaxSpeed - 1000.0, // fThrownWeaponMaxSpeed - 300.0, // fThrownWeaponMinSpeed - 0.0, // fTrapCostMult - 4000.0, // fTravelMult - 16000.0,// fTravelTimeMult - 0.1, // fUnarmoredBase1 - 0.065, // fUnarmoredBase2 - 30.0, // fVanityDelay - 10.0, // fVoiceIdleOdds - 0.0, // fWaterReflectUpdateAlways - 10.0, // fWaterReflectUpdateSeldom - 0.1, // fWeaponDamageMult - 1.0, // fWeaponFatigueBlockMult - 0.25, // fWeaponFatigueMult - 150.0, // fWereWolfAcrobatics - 150.0, // fWereWolfAgility - 1.0, // fWereWolfAlchemy - 1.0, // fWereWolfAlteration - 1.0, // fWereWolfArmorer - 150.0, // fWereWolfAthletics - 1.0, // fWereWolfAxe - 1.0, // fWereWolfBlock - 1.0, // fWereWolfBluntWeapon - 1.0, // fWereWolfConjuration - 1.0, // fWereWolfDestruction - 1.0, // fWereWolfEnchant - 150.0, // fWereWolfEndurance - 400.0, // fWereWolfFatigue - 100.0, // fWereWolfHandtoHand - 2.0, // fWereWolfHealth - 1.0, // fWereWolfHeavyArmor - 1.0, // fWereWolfIllusion - 1.0, // fWereWolfIntellegence - 1.0, // fWereWolfLightArmor - 1.0, // fWereWolfLongBlade - 1.0, // fWereWolfLuck - 100.0, // fWereWolfMagicka - 1.0, // fWereWolfMarksman - 1.0, // fWereWolfMediumArmor - 1.0, // fWereWolfMerchantile - 1.0, // fWereWolfMysticism - 1.0, // fWereWolfPersonality - 1.0, // fWereWolfRestoration - 1.5, // fWereWolfRunMult - 1.0, // fWereWolfSecurity - 1.0, // fWereWolfShortBlade - 1.5, // fWereWolfSilverWeaponDamageMult - 1.0, // fWereWolfSneak - 1.0, // fWereWolfSpear - 1.0, // fWereWolfSpeechcraft - 150.0, // fWereWolfSpeed - 150.0, // fWereWolfStrength - 100.0, // fWereWolfUnarmored - 1.0, // fWereWolfWillPower - 15.0 // fWortChanceValue + 0.3f, // fAIFleeFleeMult + 7.0f, // fAIFleeHealthMult + 3.0f, // fAIMagicSpellMult + 1.0f, // fAIMeleeArmorMult + 1.0f, // fAIMeleeSummWeaponMult + 2.0f, // fAIMeleeWeaponMult + 5.0f, // fAIRangeMagicSpellMult + 5.0f, // fAIRangeMeleeWeaponMult + 2000.0f, // fAlarmRadius + 1.0f, // fAthleticsRunBonus + 40.0f, // fAudioDefaultMaxDistance + 5.0f, // fAudioDefaultMinDistance + 50.0f, // fAudioMaxDistanceMult + 20.0f, // fAudioMinDistanceMult + 60.0f, // fAudioVoiceDefaultMaxDistance + 10.0f, // fAudioVoiceDefaultMinDistance + 50.0f, // fAutoPCSpellChance + 80.0f, // fAutoSpellChance + 50.0f, // fBargainOfferBase + -4.0f, // fBargainOfferMulti + 24.0f, // fBarterGoldResetDelay + 1.75f, // fBaseRunMultiplier + 1.25f, // fBlockStillBonus + 150.0f, // fBribe1000Mod + 75.0f, // fBribe100Mod + 35.0f, // fBribe10Mod + 60.0f, // fCombatAngleXY + 60.0f, // fCombatAngleZ + 0.25f, // fCombatArmorMinMult + -90.0f, // fCombatBlockLeftAngle + 30.0f, // fCombatBlockRightAngle + 4.0f, // fCombatCriticalStrikeMult + 0.1f, // fCombatDelayCreature + 0.1f, // fCombatDelayNPC + 128.0f, // fCombatDistance + 0.3f, // fCombatDistanceWerewolfMod + 30.0f, // fCombatForceSideAngle + 0.2f, // fCombatInvisoMult + 1.5f, // fCombatKODamageMult + 45.0f, // fCombatTorsoSideAngle + 0.3f, // fCombatTorsoStartPercent + 0.8f, // fCombatTorsoStopPercent + 15.0f, // fConstantEffectMult + 72.0f, // fCorpseClearDelay + 72.0f, // fCorpseRespawnDelay + 0.5f, // fCrimeGoldDiscountMult + 0.9f, // fCrimeGoldTurnInMult + 1.0f, // fCrimeStealing + 0.5f, // fDamageStrengthBase + 0.1f, // fDamageStrengthMult + 5.0f, // fDifficultyMult + 2.5f, // fDiseaseXferChance + -10.0f, // fDispAttacking + -1.0f, // fDispBargainFailMod + 1.0f, // fDispBargainSuccessMod + 0.0f, // fDispCrimeMod + -10.0f, // fDispDiseaseMod + 3.0f, // fDispFactionMod + 1.0f, // fDispFactionRankBase + 0.5f, // fDispFactionRankMult + 1.0f, // fDispositionMod + 50.0f, // fDispPersonalityBase + 0.5f, // fDispPersonalityMult + -25.0f, // fDispPickPocketMod + 5.0f, // fDispRaceMod + -0.5f, // fDispStealing + -5.0f, // fDispWeaponDrawn + 0.5f, // fEffectCostMult + 0.1f, // fElementalShieldMult + 3.0f, // fEnchantmentChanceMult + 0.5f, // fEnchantmentConstantChanceMult + 100.0f, // fEnchantmentConstantDurationMult + 0.1f, // fEnchantmentMult + 1000.0f, // fEnchantmentValueMult + 0.3f, // fEncumberedMoveEffect + 5.0f, // fEncumbranceStrMult + 0.04f, // fEndFatigueMult + 0.25f, // fFallAcroBase + 0.01f, // fFallAcroMult + 400.0f, // fFallDamageDistanceMin + 0.0f, // fFallDistanceBase + 0.07f, // fFallDistanceMult + 2.0f, // fFatigueAttackBase + 0.0f, // fFatigueAttackMult + 1.25f, // fFatigueBase + 4.0f, // fFatigueBlockBase + 0.0f, // fFatigueBlockMult + 5.0f, // fFatigueJumpBase + 0.0f, // fFatigueJumpMult + 0.5f, // fFatigueMult + 2.5f, // fFatigueReturnBase + 0.02f, // fFatigueReturnMult + 5.0f, // fFatigueRunBase + 2.0f, // fFatigueRunMult + 1.5f, // fFatigueSneakBase + 1.5f, // fFatigueSneakMult + 0.0f, // fFatigueSpellBase + 0.0f, // fFatigueSpellCostMult + 0.0f, // fFatigueSpellMult + 7.0f, // fFatigueSwimRunBase + 0.0f, // fFatigueSwimRunMult + 2.5f, // fFatigueSwimWalkBase + 0.0f, // fFatigueSwimWalkMult + 0.2f, // fFightDispMult + 0.005f, // fFightDistanceMultiplier + 50.0f, // fFightStealing + 3000.0f, // fFleeDistance + 512.0f, // fGreetDistanceReset + 0.1f, // fHandtoHandHealthPer + 1.0f, // fHandToHandReach + 0.5f, // fHoldBreathEndMult + 20.0f, // fHoldBreathTime + 0.75f, // fIdleChanceMultiplier + 1.0f, // fIngredientMult + 0.5f, // fInteriorHeadTrackMult + 128.0f, // fJumpAcrobaticsBase + 4.0f, // fJumpAcroMultiplier + 0.5f, // fJumpEncumbranceBase + 1.0f, // fJumpEncumbranceMultiplier + 0.5f, // fJumpMoveBase + 0.5f, // fJumpMoveMult + 1.0f, // fJumpRunMultiplier + 0.5f, // fKnockDownMult + 5.0f, // fLevelMod + 0.1f, // fLevelUpHealthEndMult + 0.6f, // fLightMaxMod + 10.0f, // fLuckMod + 10.0f, // fMagesGuildTravel + 1.5f, // fMagicCreatureCastDelay + 0.0167f, // fMagicDetectRefreshRate + 1.0f, // fMagicItemConstantMult + 1.0f, // fMagicItemCostMult + 1.0f, // fMagicItemOnceMult + 1.0f, // fMagicItemPriceMult + 0.05f, // fMagicItemRechargePerSecond + 1.0f, // fMagicItemStrikeMult + 1.0f, // fMagicItemUsedMult + 3.0f, // fMagicStartIconBlink + 0.5f, // fMagicSunBlockedMult + 0.75f, // fMajorSkillBonus + 300.0f, // fMaxFlySpeed + 0.5f, // fMaxHandToHandMult + 400.0f, // fMaxHeadTrackDistance + 200.0f, // fMaxWalkSpeed + 300.0f, // fMaxWalkSpeedCreature + 0.9f, // fMedMaxMod + 0.1f, // fMessageTimePerChar + 5.0f, // fMinFlySpeed + 0.1f, // fMinHandToHandMult + 1.0f, // fMinorSkillBonus + 100.0f, // fMinWalkSpeed + 5.0f, // fMinWalkSpeedCreature + 1.25f, // fMiscSkillBonus + 2.0f, // fNPCbaseMagickaMult + 0.5f, // fNPCHealthBarFade + 3.0f, // fNPCHealthBarTime + 1.0f, // fPCbaseMagickaMult + 0.3f, // fPerDieRollMult + 5.0f, // fPersonalityMod + 1.0f, // fPerTempMult + -1.0f, // fPickLockMult + 0.3f, // fPickPocketMod + 20.0f, // fPotionMinUsefulDuration + 0.5f, // fPotionStrengthMult + 0.5f, // fPotionT1DurMult + 1.5f, // fPotionT1MagMult + 20.0f, // fPotionT4BaseStrengthMult + 12.0f, // fPotionT4EquipStrengthMult + 3000.0f, // fProjectileMaxSpeed + 400.0f, // fProjectileMinSpeed + 25.0f, // fProjectileThrownStoreChance + 3.0f, // fRepairAmountMult + 1.0f, // fRepairMult + 1.0f, // fReputationMod + 0.15f, // fRestMagicMult + 0.0f, // fSeriousWoundMult + 0.25f, // fSleepRandMod + 0.3f, // fSleepRestMod + -1.0f, // fSneakBootMult + 0.5f, // fSneakDistanceBase + 0.002f, // fSneakDistanceMultiplier + 0.5f, // fSneakNoViewMult + 1.0f, // fSneakSkillMult + 0.75f, // fSneakSpeedMultiplier + 1.0f, // fSneakUseDelay + 500.0f, // fSneakUseDist + 1.5f, // fSneakViewMult + 3.0f, // fSoulGemMult + 0.8f, // fSpecialSkillBonus + 7.0f, // fSpellMakingValueMult + 2.0f, // fSpellPriceMult + 10.0f, // fSpellValueMult + 0.25f, // fStromWalkMult + 0.7f, // fStromWindSpeed + 3.0f, // fSuffocationDamage + 0.9f, // fSwimHeightScale + 0.1f, // fSwimRunAthleticsMult + 0.5f, // fSwimRunBase + 0.02f, // fSwimWalkAthleticsMult + 0.5f, // fSwimWalkBase + 1.0f, // fSwingBlockBase + 1.0f, // fSwingBlockMult + 1000.0f, // fTargetSpellMaxSpeed + 1000.0f, // fThrownWeaponMaxSpeed + 300.0f, // fThrownWeaponMinSpeed + 0.0f, // fTrapCostMult + 4000.0f, // fTravelMult + 16000.0f,// fTravelTimeMult + 0.1f, // fUnarmoredBase1 + 0.065f, // fUnarmoredBase2 + 30.0f, // fVanityDelay + 10.0f, // fVoiceIdleOdds + 0.0f, // fWaterReflectUpdateAlways + 10.0f, // fWaterReflectUpdateSeldom + 0.1f, // fWeaponDamageMult + 1.0f, // fWeaponFatigueBlockMult + 0.25f, // fWeaponFatigueMult + 150.0f, // fWereWolfAcrobatics + 150.0f, // fWereWolfAgility + 1.0f, // fWereWolfAlchemy + 1.0f, // fWereWolfAlteration + 1.0f, // fWereWolfArmorer + 150.0f, // fWereWolfAthletics + 1.0f, // fWereWolfAxe + 1.0f, // fWereWolfBlock + 1.0f, // fWereWolfBluntWeapon + 1.0f, // fWereWolfConjuration + 1.0f, // fWereWolfDestruction + 1.0f, // fWereWolfEnchant + 150.0f, // fWereWolfEndurance + 400.0f, // fWereWolfFatigue + 100.0f, // fWereWolfHandtoHand + 2.0f, // fWereWolfHealth + 1.0f, // fWereWolfHeavyArmor + 1.0f, // fWereWolfIllusion + 1.0f, // fWereWolfIntellegence + 1.0f, // fWereWolfLightArmor + 1.0f, // fWereWolfLongBlade + 1.0f, // fWereWolfLuck + 100.0f, // fWereWolfMagicka + 1.0f, // fWereWolfMarksman + 1.0f, // fWereWolfMediumArmor + 1.0f, // fWereWolfMerchantile + 1.0f, // fWereWolfMysticism + 1.0f, // fWereWolfPersonality + 1.0f, // fWereWolfRestoration + 1.5f, // fWereWolfRunMult + 1.0f, // fWereWolfSecurity + 1.0f, // fWereWolfShortBlade + 1.5f, // fWereWolfSilverWeaponDamageMult + 1.0f, // fWereWolfSneak + 1.0f, // fWereWolfSpear + 1.0f, // fWereWolfSpeechcraft + 150.0f, // fWereWolfSpeed + 150.0f, // fWereWolfStrength + 100.0f, // fWereWolfUnarmored + 1.0f, // fWereWolfWillPower + 15.0f // fWortChanceValue }; const int CSMWorld::DefaultGmsts::IntsDefaultValues[CSMWorld::DefaultGmsts::IntCount] = diff --git a/apps/opencs/model/world/idcompletionmanager.cpp b/apps/opencs/model/world/idcompletionmanager.cpp index 20cd8652c..7f3221342 100644 --- a/apps/opencs/model/world/idcompletionmanager.cpp +++ b/apps/opencs/model/world/idcompletionmanager.cpp @@ -60,6 +60,10 @@ std::vector CSMWorld::IdCompletionManager::getDis { types.push_back(current->first); } + + // Hack for Display_InfoCondVar + types.push_back(CSMWorld::ColumnBase::Display_InfoCondVar); + return types; } @@ -104,7 +108,7 @@ void CSMWorld::IdCompletionManager::generateCompleters(CSMWorld::Data &data) QAbstractItemView *popup = new CSVWidget::CompleterPopup(); completer->setPopup(popup); // The completer takes ownership of the popup completer->setMaxVisibleItems(10); - + mCompleters[current->first] = completer; } } diff --git a/apps/opencs/model/world/idtable.cpp b/apps/opencs/model/world/idtable.cpp index bd1179cea..7975e05ea 100644 --- a/apps/opencs/model/world/idtable.cpp +++ b/apps/opencs/model/world/idtable.cpp @@ -2,6 +2,8 @@ #include +#include + #include "collectionbase.hpp" #include "columnbase.hpp" @@ -149,6 +151,23 @@ void CSMWorld::IdTable::addRecord (const std::string& id, UniversalId::Type type endInsertRows(); } +void CSMWorld::IdTable::addRecordWithData (const std::string& id, + const std::map& data, UniversalId::Type type) +{ + int index = mIdCollection->getAppendIndex (id, type); + + beginInsertRows (QModelIndex(), index, index); + + mIdCollection->appendBlankRecord (id, type); + + for (std::map::const_iterator iter (data.begin()); iter!=data.end(); ++iter) + { + mIdCollection->setData(index, iter->first, iter->second); + } + + endInsertRows(); +} + void CSMWorld::IdTable::cloneRecord(const std::string& origin, const std::string& destination, CSMWorld::UniversalId::Type type) @@ -172,7 +191,7 @@ void CSMWorld::IdTable::setRecord (const std::string& id, const RecordBase& reco if (index==-1) { - int index = mIdCollection->getAppendIndex (id, type); + index = mIdCollection->getAppendIndex (id, type); beginInsertRows (QModelIndex(), index, index); @@ -242,7 +261,7 @@ std::pair CSMWorld::IdTable::view (int row) return std::make_pair (UniversalId::Type_None, ""); if (id[0]=='#') - id = "sys::default"; + id = ESM::CellId::sDefaultWorldspace; return std::make_pair (UniversalId (UniversalId::Type_Scene, id), hint); } diff --git a/apps/opencs/model/world/idtable.hpp b/apps/opencs/model/world/idtable.hpp index 9ecba0214..9faf64d71 100644 --- a/apps/opencs/model/world/idtable.hpp +++ b/apps/opencs/model/world/idtable.hpp @@ -53,6 +53,10 @@ namespace CSMWorld void addRecord (const std::string& id, UniversalId::Type type = UniversalId::Type_None); ///< \param type Will be ignored, unless the collection supports multiple record types + void addRecordWithData (const std::string& id, const std::map& data, + UniversalId::Type type = UniversalId::Type_None); + ///< \param type Will be ignored, unless the collection supports multiple record types + void cloneRecord(const std::string& origin, const std::string& destination, UniversalId::Type type = UniversalId::Type_None); @@ -61,7 +65,7 @@ namespace CSMWorld void setRecord (const std::string& id, const RecordBase& record, UniversalId::Type type = UniversalId::Type_None); - ///< Add record or overwrite existing recrod. + ///< Add record or overwrite existing record. const RecordBase& getRecord (const std::string& id) const; diff --git a/apps/opencs/model/world/idtree.cpp b/apps/opencs/model/world/idtree.cpp index 124a94e8c..7f43a9201 100644 --- a/apps/opencs/model/world/idtree.cpp +++ b/apps/opencs/model/world/idtree.cpp @@ -198,12 +198,12 @@ QModelIndex CSMWorld::IdTree::parent (const QModelIndex& index) const return QModelIndex(); unsigned int id = index.internalId(); - const std::pair& adress(unfoldIndexAddress(id)); + const std::pair& address(unfoldIndexAddress(id)); - if (adress.first >= this->rowCount() || adress.second >= this->columnCount()) + if (address.first >= this->rowCount() || address.second >= this->columnCount()) throw "Parent index is not present in the model"; - return createIndex(adress.first, adress.second); + return createIndex(address.first, address.second); } unsigned int CSMWorld::IdTree::foldIndexAddress (const QModelIndex& index) const @@ -264,7 +264,7 @@ void CSMWorld::IdTree::setNestedTable(const QModelIndex& index, const CSMWorld:: CSMWorld::NestedTableWrapperBase* CSMWorld::IdTree::nestedTable(const QModelIndex& index) const { if (!hasChildren(index)) - throw std::logic_error("Tried to retrive nested table, but index has no children"); + throw std::logic_error("Tried to retrieve nested table, but index has no children"); return mNestedCollection->nestedTable(index.row(), index.column()); } diff --git a/apps/opencs/model/world/idtree.hpp b/apps/opencs/model/world/idtree.hpp index 79e93fc3d..1539bd4a2 100644 --- a/apps/opencs/model/world/idtree.hpp +++ b/apps/opencs/model/world/idtree.hpp @@ -8,9 +8,9 @@ /*! \brief * Class for holding the model. Uses typical qt table abstraction/interface for granting access * to the individiual fields of the records, Some records are holding nested data (for instance - * inventory list of the npc). In casses like this, table model offers interface to access + * inventory list of the npc). In cases like this, table model offers interface to access * nested data in the qt way - that is specify parent. Since some of those nested data require - * multiple columns to represent informations, single int (default way to index model in the + * multiple columns to represent information, single int (default way to index model in the * qmodelindex) is not sufficiant. Therefore tablemodelindex class can hold two ints for the * sake of indexing two dimensions of the table. This model does not support multiple levels of * the nested data. Vast majority of methods makes sense only for the top level data. diff --git a/apps/opencs/model/world/infocollection.cpp b/apps/opencs/model/world/infocollection.cpp index f5ec4d458..be73aece6 100644 --- a/apps/opencs/model/world/infocollection.cpp +++ b/apps/opencs/model/world/infocollection.cpp @@ -19,8 +19,6 @@ void CSMWorld::InfoCollection::load (const Info& record, bool base) record2.mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly; (base ? record2.mBase : record2.mModified) = record; - int index = -1; - std::string topic = Misc::StringUtils::lowerCase (record2.get().mTopicId); if (!record2.get().mPrev.empty()) diff --git a/apps/opencs/model/world/infoselectwrapper.cpp b/apps/opencs/model/world/infoselectwrapper.cpp new file mode 100644 index 000000000..3bc9bf4d2 --- /dev/null +++ b/apps/opencs/model/world/infoselectwrapper.cpp @@ -0,0 +1,893 @@ +#include "infoselectwrapper.hpp" + +#include +#include +#include + +const size_t CSMWorld::ConstInfoSelectWrapper::RuleMinSize = 5; + +const size_t CSMWorld::ConstInfoSelectWrapper::FunctionPrefixOffset = 1; +const size_t CSMWorld::ConstInfoSelectWrapper::FunctionIndexOffset = 2; +const size_t CSMWorld::ConstInfoSelectWrapper::RelationIndexOffset = 4; +const size_t CSMWorld::ConstInfoSelectWrapper::VarNameOffset = 5; + +const char* CSMWorld::ConstInfoSelectWrapper::FunctionEnumStrings[] = +{ + "Rank Low", + "Rank High", + "Rank Requirement", + "Reputation", + "Health Percent", + "PC Reputation", + "PC Level", + "PC Health Percent", + "PC Magicka", + "PC Fatigue", + "PC Strength", + "PC Block", + "PC Armorer", + "PC Medium Armor", + "PC Heavy Armor", + "PC Blunt Weapon", + "PC Long Blade", + "PC Axe", + "PC Spear", + "PC Athletics", + "PC Enchant", + "PC Detruction", + "PC Alteration", + "PC Illusion", + "PC Conjuration", + "PC Mysticism", + "PC Restoration", + "PC Alchemy", + "PC Unarmored", + "PC Security", + "PC Sneak", + "PC Acrobatics", + "PC Light Armor", + "PC Short Blade", + "PC Marksman", + "PC Merchantile", + "PC Speechcraft", + "PC Hand to Hand", + "PC Sex", + "PC Expelled", + "PC Common Disease", + "PC Blight Disease", + "PC Clothing Modifier", + "PC Crime Level", + "Same Sex", + "Same Race", + "Same Faction", + "Faction Rank Difference", + "Detected", + "Alarmed", + "Choice", + "PC Intelligence", + "PC Willpower", + "PC Agility", + "PC Speed", + "PC Endurance", + "PC Personality", + "PC Luck", + "PC Corpus", + "Weather", + "PC Vampire", + "Level", + "Attacked", + "Talked to PC", + "PC Health", + "Creature Target", + "Friend Hit", + "Fight", + "Hello", + "Alarm", + "Flee", + "Should Attack", + "Werewolf", + "PC Werewolf Kills", + "Global", + "Local", + "Journal", + "Item", + "Dead", + "Not Id", + "Not Faction", + "Not Class", + "Not Race", + "Not Cell", + "Not Local", + 0 +}; + +const char* CSMWorld::ConstInfoSelectWrapper::RelationEnumStrings[] = +{ + "=", + "!=", + ">", + ">=", + "<", + "<=", + 0 +}; + +const char* CSMWorld::ConstInfoSelectWrapper::ComparisonEnumStrings[] = +{ + "Boolean", + "Integer", + "Numeric", + 0 +}; + +// static functions + +std::string CSMWorld::ConstInfoSelectWrapper::convertToString(FunctionName name) +{ + if (name < Function_None) + return FunctionEnumStrings[name]; + else + return "(Invalid Data: Function)"; +} + +std::string CSMWorld::ConstInfoSelectWrapper::convertToString(RelationType type) +{ + if (type < Relation_None) + return RelationEnumStrings[type]; + else + return "(Invalid Data: Relation)"; +} + +std::string CSMWorld::ConstInfoSelectWrapper::convertToString(ComparisonType type) +{ + if (type < Comparison_None) + return ComparisonEnumStrings[type]; + else + return "(Invalid Data: Comparison)"; +} + +// ConstInfoSelectWrapper + +CSMWorld::ConstInfoSelectWrapper::ConstInfoSelectWrapper(const ESM::DialInfo::SelectStruct& select) + : mConstSelect(select) +{ + readRule(); +} + +CSMWorld::ConstInfoSelectWrapper::FunctionName CSMWorld::ConstInfoSelectWrapper::getFunctionName() const +{ + return mFunctionName; +} + +CSMWorld::ConstInfoSelectWrapper::RelationType CSMWorld::ConstInfoSelectWrapper::getRelationType() const +{ + return mRelationType; +} + +CSMWorld::ConstInfoSelectWrapper::ComparisonType CSMWorld::ConstInfoSelectWrapper::getComparisonType() const +{ + return mComparisonType; +} + +bool CSMWorld::ConstInfoSelectWrapper::hasVariable() const +{ + return mHasVariable; +} + +const std::string& CSMWorld::ConstInfoSelectWrapper::getVariableName() const +{ + return mVariableName; +} + +bool CSMWorld::ConstInfoSelectWrapper::conditionIsAlwaysTrue() const +{ + if (!variantTypeIsValid()) + return false; + + if (mComparisonType == Comparison_Boolean || mComparisonType == Comparison_Integer) + { + if (mConstSelect.mValue.getType() == ESM::VT_Float) + return conditionIsAlwaysTrue(getConditionFloatRange(), getValidIntRange()); + else + return conditionIsAlwaysTrue(getConditionIntRange(), getValidIntRange()); + } + else if (mComparisonType == Comparison_Numeric) + { + if (mConstSelect.mValue.getType() == ESM::VT_Float) + return conditionIsAlwaysTrue(getConditionFloatRange(), getValidFloatRange()); + else + return conditionIsAlwaysTrue(getConditionIntRange(), getValidFloatRange()); + } + + return false; +} + +bool CSMWorld::ConstInfoSelectWrapper::conditionIsNeverTrue() const +{ + if (!variantTypeIsValid()) + return false; + + if (mComparisonType == Comparison_Boolean || mComparisonType == Comparison_Integer) + { + if (mConstSelect.mValue.getType() == ESM::VT_Float) + return conditionIsNeverTrue(getConditionFloatRange(), getValidIntRange()); + else + return conditionIsNeverTrue(getConditionIntRange(), getValidIntRange()); + } + else if (mComparisonType == Comparison_Numeric) + { + if (mConstSelect.mValue.getType() == ESM::VT_Float) + return conditionIsNeverTrue(getConditionFloatRange(), getValidFloatRange()); + else + return conditionIsNeverTrue(getConditionIntRange(), getValidFloatRange()); + } + + return false; +} + +bool CSMWorld::ConstInfoSelectWrapper::variantTypeIsValid() const +{ + return (mConstSelect.mValue.getType() == ESM::VT_Int || mConstSelect.mValue.getType() == ESM::VT_Float); +} + +const ESM::Variant& CSMWorld::ConstInfoSelectWrapper::getVariant() const +{ + return mConstSelect.mValue; +} + +std::string CSMWorld::ConstInfoSelectWrapper::toString() const +{ + std::ostringstream stream; + stream << convertToString(mFunctionName) << " "; + + if (mHasVariable) + stream << mVariableName << " "; + + stream << convertToString(mRelationType) << " "; + + switch (mConstSelect.mValue.getType()) + { + case ESM::VT_Int: + stream << mConstSelect.mValue.getInteger(); + break; + + case ESM::VT_Float: + stream << mConstSelect.mValue.getFloat(); + break; + + default: + stream << "(Invalid value type)"; + break; + } + + return stream.str(); +} + +void CSMWorld::ConstInfoSelectWrapper::readRule() +{ + if (mConstSelect.mSelectRule.size() < RuleMinSize) + throw std::runtime_error("InfoSelectWrapper: rule is to small"); + + readFunctionName(); + readRelationType(); + readVariableName(); + updateHasVariable(); + updateComparisonType(); +} + +void CSMWorld::ConstInfoSelectWrapper::readFunctionName() +{ + char functionPrefix = mConstSelect.mSelectRule[FunctionPrefixOffset]; + std::string functionIndex = mConstSelect.mSelectRule.substr(FunctionIndexOffset, 2); + int convertedIndex = -1; + + // Read in function index, form ## from 00 .. 73, skip leading zero + if (functionIndex[0] == '0') + functionIndex = functionIndex[1]; + + std::stringstream stream; + stream << functionIndex; + stream >> convertedIndex; + + switch (functionPrefix) + { + case '1': + if (convertedIndex >= 0 && convertedIndex <= 73) + mFunctionName = static_cast(convertedIndex); + else + mFunctionName = Function_None; + break; + + case '2': mFunctionName = Function_Global; break; + case '3': mFunctionName = Function_Local; break; + case '4': mFunctionName = Function_Journal; break; + case '5': mFunctionName = Function_Item; break; + case '6': mFunctionName = Function_Dead; break; + case '7': mFunctionName = Function_NotId; break; + case '8': mFunctionName = Function_NotFaction; break; + case '9': mFunctionName = Function_NotClass; break; + case 'A': mFunctionName = Function_NotRace; break; + case 'B': mFunctionName = Function_NotCell; break; + case 'C': mFunctionName = Function_NotLocal; break; + default: mFunctionName = Function_None; break; + } +} + +void CSMWorld::ConstInfoSelectWrapper::readRelationType() +{ + char relationIndex = mConstSelect.mSelectRule[RelationIndexOffset]; + + switch (relationIndex) + { + case '0': mRelationType = Relation_Equal; break; + case '1': mRelationType = Relation_NotEqual; break; + case '2': mRelationType = Relation_Greater; break; + case '3': mRelationType = Relation_GreaterOrEqual; break; + case '4': mRelationType = Relation_Less; break; + case '5': mRelationType = Relation_LessOrEqual; break; + default: mRelationType = Relation_None; + } +} + +void CSMWorld::ConstInfoSelectWrapper::readVariableName() +{ + if (mConstSelect.mSelectRule.size() >= VarNameOffset) + mVariableName = mConstSelect.mSelectRule.substr(VarNameOffset); + else + mVariableName.clear(); +} + +void CSMWorld::ConstInfoSelectWrapper::updateHasVariable() +{ + switch (mFunctionName) + { + case Function_Global: + case Function_Local: + case Function_Journal: + case Function_Item: + case Function_Dead: + case Function_NotId: + case Function_NotFaction: + case Function_NotClass: + case Function_NotRace: + case Function_NotCell: + case Function_NotLocal: + mHasVariable = true; + break; + + default: + mHasVariable = false; + break; + } +} + +void CSMWorld::ConstInfoSelectWrapper::updateComparisonType() +{ + switch (mFunctionName) + { + // Boolean + case Function_NotId: + case Function_NotFaction: + case Function_NotClass: + case Function_NotRace: + case Function_NotCell: + case Function_PcExpelled: + case Function_PcCommonDisease: + case Function_PcBlightDisease: + case Function_SameSex: + case Function_SameRace: + case Function_SameFaction: + case Function_Detected: + case Function_Alarmed: + case Function_PcCorpus: + case Function_PcVampire: + case Function_Attacked: + case Function_TalkedToPc: + case Function_ShouldAttack: + case Function_Werewolf: + mComparisonType = Comparison_Boolean; + break; + + // Integer + case Function_Journal: + case Function_Item: + case Function_Dead: + case Function_RankLow: + case Function_RankHigh: + case Function_RankRequirement: + case Function_Reputation: + case Function_PcReputation: + case Function_PcLevel: + case Function_PcStrength: + case Function_PcBlock: + case Function_PcArmorer: + case Function_PcMediumArmor: + case Function_PcHeavyArmor: + case Function_PcBluntWeapon: + case Function_PcLongBlade: + case Function_PcAxe: + case Function_PcSpear: + case Function_PcAthletics: + case Function_PcEnchant: + case Function_PcDestruction: + case Function_PcAlteration: + case Function_PcIllusion: + case Function_PcConjuration: + case Function_PcMysticism: + case Function_PcRestoration: + case Function_PcAlchemy: + case Function_PcUnarmored: + case Function_PcSecurity: + case Function_PcSneak: + case Function_PcAcrobatics: + case Function_PcLightArmor: + case Function_PcShortBlade: + case Function_PcMarksman: + case Function_PcMerchantile: + case Function_PcSpeechcraft: + case Function_PcHandToHand: + case Function_PcGender: + case Function_PcClothingModifier: + case Function_PcCrimeLevel: + case Function_FactionRankDifference: + case Function_Choice: + case Function_PcIntelligence: + case Function_PcWillpower: + case Function_PcAgility: + case Function_PcSpeed: + case Function_PcEndurance: + case Function_PcPersonality: + case Function_PcLuck: + case Function_Weather: + case Function_Level: + case Function_CreatureTarget: + case Function_FriendHit: + case Function_Fight: + case Function_Hello: + case Function_Alarm: + case Function_Flee: + case Function_PcWerewolfKills: + mComparisonType = Comparison_Integer; + break; + + // Numeric + case Function_Global: + case Function_Local: + case Function_NotLocal: + + case Function_Health_Percent: + case Function_PcHealthPercent: + case Function_PcMagicka: + case Function_PcFatigue: + case Function_PcHealth: + mComparisonType = Comparison_Numeric; + break; + + default: + mComparisonType = Comparison_None; + break; + } +} + +std::pair CSMWorld::ConstInfoSelectWrapper::getConditionIntRange() const +{ + const int IntMax = std::numeric_limits::max(); + const int IntMin = std::numeric_limits::min(); + const std::pair InvalidRange(IntMax, IntMin); + + int value = mConstSelect.mValue.getInteger(); + + switch (mRelationType) + { + case Relation_Equal: + case Relation_NotEqual: + return std::pair(value, value); + + case Relation_Greater: + if (value == IntMax) + { + return InvalidRange; + } + else + { + return std::pair(value + 1, IntMax); + } + break; + + case Relation_GreaterOrEqual: + return std::pair(value, IntMax); + + case Relation_Less: + if (value == IntMin) + { + return InvalidRange; + } + else + { + return std::pair(IntMin, value - 1); + } + + case Relation_LessOrEqual: + return std::pair(IntMin, value); + + default: + throw std::logic_error("InfoSelectWrapper: relation does not have a range"); + } +} + +std::pair CSMWorld::ConstInfoSelectWrapper::getConditionFloatRange() const +{ + const float FloatMax = std::numeric_limits::infinity(); + const float FloatMin = -std::numeric_limits::infinity(); + const float Epsilon = std::numeric_limits::epsilon(); + const std::pair InvalidRange(FloatMax, FloatMin); + + float value = mConstSelect.mValue.getFloat(); + + switch (mRelationType) + { + case Relation_Equal: + case Relation_NotEqual: + return std::pair(value, value); + + case Relation_Greater: + return std::pair(value + Epsilon, FloatMax); + + case Relation_GreaterOrEqual: + return std::pair(value, FloatMax); + + case Relation_Less: + return std::pair(FloatMin, value - Epsilon); + + case Relation_LessOrEqual: + return std::pair(FloatMin, value); + + default: + throw std::logic_error("InfoSelectWrapper: given relation does not have a range"); + } +} + +std::pair CSMWorld::ConstInfoSelectWrapper::getValidIntRange() const +{ + const int IntMax = std::numeric_limits::max(); + const int IntMin = std::numeric_limits::min(); + + switch (mFunctionName) + { + // Boolean + case Function_NotId: + case Function_NotFaction: + case Function_NotClass: + case Function_NotRace: + case Function_NotCell: + case Function_PcExpelled: + case Function_PcCommonDisease: + case Function_PcBlightDisease: + case Function_SameSex: + case Function_SameRace: + case Function_SameFaction: + case Function_Detected: + case Function_Alarmed: + case Function_PcCorpus: + case Function_PcVampire: + case Function_Attacked: + case Function_TalkedToPc: + case Function_ShouldAttack: + case Function_Werewolf: + return std::pair(0, 1); + + // Integer + case Function_RankLow: + case Function_RankHigh: + case Function_Reputation: + case Function_PcReputation: + case Function_Journal: + return std::pair(IntMin, IntMax); + + case Function_Item: + case Function_Dead: + case Function_PcLevel: + case Function_PcStrength: + case Function_PcBlock: + case Function_PcArmorer: + case Function_PcMediumArmor: + case Function_PcHeavyArmor: + case Function_PcBluntWeapon: + case Function_PcLongBlade: + case Function_PcAxe: + case Function_PcSpear: + case Function_PcAthletics: + case Function_PcEnchant: + case Function_PcDestruction: + case Function_PcAlteration: + case Function_PcIllusion: + case Function_PcConjuration: + case Function_PcMysticism: + case Function_PcRestoration: + case Function_PcAlchemy: + case Function_PcUnarmored: + case Function_PcSecurity: + case Function_PcSneak: + case Function_PcAcrobatics: + case Function_PcLightArmor: + case Function_PcShortBlade: + case Function_PcMarksman: + case Function_PcMerchantile: + case Function_PcSpeechcraft: + case Function_PcHandToHand: + case Function_PcClothingModifier: + case Function_PcCrimeLevel: + case Function_Choice: + case Function_PcIntelligence: + case Function_PcWillpower: + case Function_PcAgility: + case Function_PcSpeed: + case Function_PcEndurance: + case Function_PcPersonality: + case Function_PcLuck: + case Function_Level: + case Function_PcWerewolfKills: + return std::pair(0, IntMax); + + case Function_Fight: + case Function_Hello: + case Function_Alarm: + case Function_Flee: + return std::pair(0, 100); + + case Function_Weather: + return std::pair(0, 9); + + case Function_FriendHit: + return std::pair(0, 4); + + case Function_RankRequirement: + return std::pair(0, 3); + + case Function_CreatureTarget: + return std::pair(0, 2); + + case Function_PcGender: + return std::pair(0, 1); + + case Function_FactionRankDifference: + return std::pair(-9, 9); + + // Numeric + case Function_Global: + case Function_Local: + case Function_NotLocal: + return std::pair(IntMin, IntMax); + + case Function_PcMagicka: + case Function_PcFatigue: + case Function_PcHealth: + return std::pair(0, IntMax); + + case Function_Health_Percent: + case Function_PcHealthPercent: + return std::pair(0, 100); + + default: + throw std::runtime_error("InfoSelectWrapper: function does not exist"); + } +} + +std::pair CSMWorld::ConstInfoSelectWrapper::getValidFloatRange() const +{ + const float FloatMax = std::numeric_limits::infinity(); + const float FloatMin = -std::numeric_limits::infinity(); + + switch (mFunctionName) + { + // Numeric + case Function_Global: + case Function_Local: + case Function_NotLocal: + return std::pair(FloatMin, FloatMax); + + case Function_PcMagicka: + case Function_PcFatigue: + case Function_PcHealth: + return std::pair(0, FloatMax); + + case Function_Health_Percent: + case Function_PcHealthPercent: + return std::pair(0, 100); + + default: + throw std::runtime_error("InfoSelectWrapper: function does not exist or is not numeric"); + } +} + +template +bool CSMWorld::ConstInfoSelectWrapper::rangeContains(T1 value, std::pair range) const +{ + return (value >= range.first && value <= range.second); +} + +template +bool CSMWorld::ConstInfoSelectWrapper::rangeFullyContains(std::pair containingRange, + std::pair testRange) const +{ + return (containingRange.first <= testRange.first) && (testRange.second <= containingRange.second); +} + +template +bool CSMWorld::ConstInfoSelectWrapper::rangesOverlap(std::pair range1, std::pair range2) const +{ + // One of the bounds of either range should fall within the other range + return + (range1.first <= range2.first && range2.first <= range1.second) || + (range1.first <= range2.second && range2.second <= range1.second) || + (range2.first <= range1.first && range1.first <= range2.second) || + (range2.first <= range1.second && range1.second <= range2.second); +} + +template +bool CSMWorld::ConstInfoSelectWrapper::rangesMatch(std::pair range1, std::pair range2) const +{ + return (range1.first == range2.first && range1.second == range2.second); +} + +template +bool CSMWorld::ConstInfoSelectWrapper::conditionIsAlwaysTrue(std::pair conditionRange, + std::pair validRange) const +{ + switch (mRelationType) + { + case Relation_Equal: + return false; + + case Relation_NotEqual: + // If value is not within range, it will always be true + return !rangeContains(conditionRange.first, validRange); + + case Relation_Greater: + case Relation_GreaterOrEqual: + case Relation_Less: + case Relation_LessOrEqual: + // If the valid range is completely within the condition range, it will always be true + return rangeFullyContains(conditionRange, validRange); + + default: + throw std::logic_error("InfoCondition: operator can not be used to compare"); + } + + return false; +} + +template +bool CSMWorld::ConstInfoSelectWrapper::conditionIsNeverTrue(std::pair conditionRange, + std::pair validRange) const +{ + switch (mRelationType) + { + case Relation_Equal: + return !rangeContains(conditionRange.first, validRange); + + case Relation_NotEqual: + return false; + + case Relation_Greater: + case Relation_GreaterOrEqual: + case Relation_Less: + case Relation_LessOrEqual: + // If ranges do not overlap, it will never be true + return !rangesOverlap(conditionRange, validRange); + + default: + throw std::logic_error("InfoCondition: operator can not be used to compare"); + } + + return false; +} + +// InfoSelectWrapper + +CSMWorld::InfoSelectWrapper::InfoSelectWrapper(ESM::DialInfo::SelectStruct& select) + : CSMWorld::ConstInfoSelectWrapper(select), mSelect(select) +{ +} + +void CSMWorld::InfoSelectWrapper::setFunctionName(FunctionName name) +{ + mFunctionName = name; + updateHasVariable(); + updateComparisonType(); +} + +void CSMWorld::InfoSelectWrapper::setRelationType(RelationType type) +{ + mRelationType = type; +} + +void CSMWorld::InfoSelectWrapper::setVariableName(const std::string& name) +{ + mVariableName = name; +} + +void CSMWorld::InfoSelectWrapper::setDefaults() +{ + if (!variantTypeIsValid()) + mSelect.mValue.setType(ESM::VT_Int); + + switch (mComparisonType) + { + case Comparison_Boolean: + setRelationType(Relation_Equal); + mSelect.mValue.setInteger(1); + break; + + case Comparison_Integer: + case Comparison_Numeric: + setRelationType(Relation_Greater); + mSelect.mValue.setInteger(0); + break; + + default: + // Do nothing + break; + } + + update(); +} + +void CSMWorld::InfoSelectWrapper::update() +{ + std::ostringstream stream; + + // Leading 0 + stream << '0'; + + // Write Function + + bool writeIndex = false; + size_t functionIndex = static_cast(mFunctionName); + + switch (mFunctionName) + { + case Function_None: stream << '0'; break; + case Function_Global: stream << '2'; break; + case Function_Local: stream << '3'; break; + case Function_Journal: stream << '4'; break; + case Function_Item: stream << '5'; break; + case Function_Dead: stream << '6'; break; + case Function_NotId: stream << '7'; break; + case Function_NotFaction: stream << '8'; break; + case Function_NotClass: stream << '9'; break; + case Function_NotRace: stream << 'A'; break; + case Function_NotCell: stream << 'B'; break; + case Function_NotLocal: stream << 'C'; break; + default: stream << '1'; writeIndex = true; break; + } + + if (writeIndex && functionIndex < 10) // leading 0 + stream << '0' << functionIndex; + else if (writeIndex) + stream << functionIndex; + else + stream << "00"; + + // Write Relation + switch (mRelationType) + { + case Relation_Equal: stream << '0'; break; + case Relation_NotEqual: stream << '1'; break; + case Relation_Greater: stream << '2'; break; + case Relation_GreaterOrEqual: stream << '3'; break; + case Relation_Less: stream << '4'; break; + case Relation_LessOrEqual: stream << '5'; break; + default: stream << '0'; break; + } + + if (mHasVariable) + stream << mVariableName; + + mSelect.mSelectRule = stream.str(); +} + +ESM::Variant& CSMWorld::InfoSelectWrapper::getVariant() +{ + return mSelect.mValue; +} diff --git a/apps/opencs/model/world/infoselectwrapper.hpp b/apps/opencs/model/world/infoselectwrapper.hpp new file mode 100644 index 000000000..ce26a46dc --- /dev/null +++ b/apps/opencs/model/world/infoselectwrapper.hpp @@ -0,0 +1,243 @@ +#ifndef CSM_WORLD_INFOSELECTWRAPPER_H +#define CSM_WORLD_INFOSELECTWRAPPER_H + +#include + +namespace CSMWorld +{ + // ESM::DialInfo::SelectStruct.mSelectRule + // 012345... + // ^^^ ^^ + // ||| || + // ||| |+------------- condition variable string + // ||| +-------------- comparison type, ['0'..'5']; e.g. !=, <, >=, etc + // ||+---------------- function index (encoded, where function == '1') + // |+----------------- function, ['1'..'C']; e.g. Global, Local, Not ID, etc + // +------------------ unknown + // + + // Wrapper for DialInfo::SelectStruct + class ConstInfoSelectWrapper + { + public: + + // Order matters + enum FunctionName + { + Function_RankLow=0, + Function_RankHigh, + Function_RankRequirement, + Function_Reputation, + Function_Health_Percent, + Function_PcReputation, + Function_PcLevel, + Function_PcHealthPercent, + Function_PcMagicka, + Function_PcFatigue, + Function_PcStrength, + Function_PcBlock, + Function_PcArmorer, + Function_PcMediumArmor, + Function_PcHeavyArmor, + Function_PcBluntWeapon, + Function_PcLongBlade, + Function_PcAxe, + Function_PcSpear, + Function_PcAthletics, + Function_PcEnchant, + Function_PcDestruction, + Function_PcAlteration, + Function_PcIllusion, + Function_PcConjuration, + Function_PcMysticism, + Function_PcRestoration, + Function_PcAlchemy, + Function_PcUnarmored, + Function_PcSecurity, + Function_PcSneak, + Function_PcAcrobatics, + Function_PcLightArmor, + Function_PcShortBlade, + Function_PcMarksman, + Function_PcMerchantile, + Function_PcSpeechcraft, + Function_PcHandToHand, + Function_PcGender, + Function_PcExpelled, + Function_PcCommonDisease, + Function_PcBlightDisease, + Function_PcClothingModifier, + Function_PcCrimeLevel, + Function_SameSex, + Function_SameRace, + Function_SameFaction, + Function_FactionRankDifference, + Function_Detected, + Function_Alarmed, + Function_Choice, + Function_PcIntelligence, + Function_PcWillpower, + Function_PcAgility, + Function_PcSpeed, + Function_PcEndurance, + Function_PcPersonality, + Function_PcLuck, + Function_PcCorpus, + Function_Weather, + Function_PcVampire, + Function_Level, + Function_Attacked, + Function_TalkedToPc, + Function_PcHealth, + Function_CreatureTarget, + Function_FriendHit, + Function_Fight, + Function_Hello, + Function_Alarm, + Function_Flee, + Function_ShouldAttack, + Function_Werewolf, + Function_PcWerewolfKills=73, + + Function_Global, + Function_Local, + Function_Journal, + Function_Item, + Function_Dead, + Function_NotId, + Function_NotFaction, + Function_NotClass, + Function_NotRace, + Function_NotCell, + Function_NotLocal, + + Function_None + }; + + enum RelationType + { + Relation_Equal, + Relation_NotEqual, + Relation_Greater, + Relation_GreaterOrEqual, + Relation_Less, + Relation_LessOrEqual, + + Relation_None + }; + + enum ComparisonType + { + Comparison_Boolean, + Comparison_Integer, + Comparison_Numeric, + + Comparison_None + }; + + static const size_t RuleMinSize; + + static const size_t FunctionPrefixOffset; + static const size_t FunctionIndexOffset; + static const size_t RelationIndexOffset; + static const size_t VarNameOffset; + + static const char* FunctionEnumStrings[]; + static const char* RelationEnumStrings[]; + static const char* ComparisonEnumStrings[]; + + static std::string convertToString(FunctionName name); + static std::string convertToString(RelationType type); + static std::string convertToString(ComparisonType type); + + ConstInfoSelectWrapper(const ESM::DialInfo::SelectStruct& select); + + FunctionName getFunctionName() const; + RelationType getRelationType() const; + ComparisonType getComparisonType() const; + + bool hasVariable() const; + const std::string& getVariableName() const; + + bool conditionIsAlwaysTrue() const; + bool conditionIsNeverTrue() const; + bool variantTypeIsValid() const; + + const ESM::Variant& getVariant() const; + + std::string toString() const; + + protected: + + void readRule(); + void readFunctionName(); + void readRelationType(); + void readVariableName(); + void updateHasVariable(); + void updateComparisonType(); + + std::pair getConditionIntRange() const; + std::pair getConditionFloatRange() const; + + std::pair getValidIntRange() const; + std::pair getValidFloatRange() const; + + template + bool rangeContains(Type1 value, std::pair range) const; + + template + bool rangesOverlap(std::pair range1, std::pair range2) const; + + template + bool rangeFullyContains(std::pair containing, std::pair test) const; + + template + bool rangesMatch(std::pair range1, std::pair range2) const; + + template + bool conditionIsAlwaysTrue(std::pair conditionRange, std::pair validRange) const; + + template + bool conditionIsNeverTrue(std::pair conditionRange, std::pair validRange) const; + + FunctionName mFunctionName; + RelationType mRelationType; + ComparisonType mComparisonType; + + bool mHasVariable; + std::string mVariableName; + + private: + + const ESM::DialInfo::SelectStruct& mConstSelect; + }; + + // Wrapper for DialInfo::SelectStruct that can modify the wrapped select struct + class InfoSelectWrapper : public ConstInfoSelectWrapper + { + public: + + InfoSelectWrapper(ESM::DialInfo::SelectStruct& select); + + // Wrapped SelectStruct will not be modified until update() is called + void setFunctionName(FunctionName name); + void setRelationType(RelationType type); + void setVariableName(const std::string& name); + + // Modified wrapped SelectStruct + void update(); + + // This sets properties based on the function name to its defaults and updates the wrapped object + void setDefaults(); + + ESM::Variant& getVariant(); + + private: + + ESM::DialInfo::SelectStruct& mSelect; + + void writeRule(); + }; +} + +#endif diff --git a/apps/opencs/model/world/nestedcoladapterimp.cpp b/apps/opencs/model/world/nestedcoladapterimp.cpp index 92b4b9e62..464757cd4 100644 --- a/apps/opencs/model/world/nestedcoladapterimp.cpp +++ b/apps/opencs/model/world/nestedcoladapterimp.cpp @@ -6,6 +6,7 @@ #include "idcollection.hpp" #include "pathgrid.hpp" #include "info.hpp" +#include "infoselectwrapper.hpp" namespace CSMWorld { @@ -26,20 +27,8 @@ namespace CSMWorld point.mConnectionNum = 0; point.mUnknown = 0; - // inserting a point should trigger re-indexing of the edges - // - // FIXME: does not auto refresh edges table view - std::vector::iterator iter = pathgrid.mEdges.begin(); - for (;iter != pathgrid.mEdges.end(); ++iter) - { - if ((*iter).mV0 >= position) - (*iter).mV0++; - if ((*iter).mV1 >= position) - (*iter).mV1++; - } - points.insert(points.begin()+position, point); - pathgrid.mData.mS2 += 1; // increment the number of points + pathgrid.mData.mS2 = pathgrid.mPoints.size(); record.setModified (pathgrid); } @@ -53,28 +42,10 @@ namespace CSMWorld if (rowToRemove < 0 || rowToRemove >= static_cast (points.size())) throw std::runtime_error ("index out of range"); - // deleting a point should trigger re-indexing of the edges - // dangling edges are not allowed and hence removed - // - // FIXME: does not auto refresh edges table view - std::vector::iterator iter = pathgrid.mEdges.begin(); - for (; iter != pathgrid.mEdges.end();) - { - if (((*iter).mV0 == rowToRemove) || ((*iter).mV1 == rowToRemove)) - iter = pathgrid.mEdges.erase(iter); - else - { - if ((*iter).mV0 > rowToRemove) - (*iter).mV0--; - - if ((*iter).mV1 > rowToRemove) - (*iter).mV1--; - - ++iter; - } - } + // Do not remove dangling edges, does not work with current undo mechanism + // Do not automatically adjust indices, what would be done with dangling edges? points.erase(points.begin()+rowToRemove); - pathgrid.mData.mS2 -= 1; // decrement the number of points + pathgrid.mData.mS2 = pathgrid.mPoints.size(); record.setModified (pathgrid); } @@ -83,14 +54,8 @@ namespace CSMWorld const NestedTableWrapperBase& nestedTable) const { Pathgrid pathgrid = record.get(); - - pathgrid.mPoints = - static_cast(nestedTable).mRecord.mPoints; - pathgrid.mData.mS2 = - static_cast(nestedTable).mRecord.mData.mS2; - // also update edges in case points were added/removed - pathgrid.mEdges = - static_cast(nestedTable).mRecord.mEdges; + pathgrid.mPoints = static_cast &>(nestedTable).mNestedTable; + pathgrid.mData.mS2 = pathgrid.mPoints.size(); record.setModified (pathgrid); } @@ -98,7 +63,7 @@ namespace CSMWorld NestedTableWrapperBase* PathgridPointListAdapter::table(const Record& record) const { // deleted by dtor of NestedTableStoring - return new PathgridPointsWrap(record.get()); + return new NestedTableWrapper(record.get().mPoints); } QVariant PathgridPointListAdapter::getData(const Record& record, @@ -146,7 +111,6 @@ namespace CSMWorld PathgridEdgeListAdapter::PathgridEdgeListAdapter () {} - // ToDo: seems to be auto-sorted in the dialog table display after insertion void PathgridEdgeListAdapter::addRow(Record& record, int position) const { Pathgrid pathgrid = record.get(); @@ -217,7 +181,6 @@ namespace CSMWorld } } - // ToDo: detect duplicates in mEdges void PathgridEdgeListAdapter::setData(Record& record, const QVariant& value, int subRowIndex, int subColIndex) const { @@ -529,16 +492,6 @@ namespace CSMWorld return 1; // fixed at size 1 } - // ESM::DialInfo::SelectStruct.mSelectRule - // 012345... - // ^^^ ^^ - // ||| || - // ||| |+------------- condition variable string - // ||| +-------------- comparison type, ['0'..'5']; e.g. !=, <, >=, etc - // ||+---------------- function index (encoded, where function == '1') - // |+----------------- function, ['1'..'C']; e.g. Global, Local, Not ID, etc - // +------------------ unknown - // InfoConditionAdapter::InfoConditionAdapter () {} void InfoConditionAdapter::addRow(Record& record, int position) const @@ -547,11 +500,11 @@ namespace CSMWorld std::vector& conditions = info.mSelects; - // blank row + // default row ESM::DialInfo::SelectStruct condStruct; - condStruct.mSelectRule = "00000"; + condStruct.mSelectRule = "01000"; condStruct.mValue = ESM::Variant(); - condStruct.mValue.setType(ESM::VT_Int); // default to ints + condStruct.mValue.setType(ESM::VT_Int); conditions.insert(conditions.begin()+position, condStruct); @@ -589,89 +542,6 @@ namespace CSMWorld return new NestedTableWrapper >(record.get().mSelects); } - // See the mappings in MWDialogue::SelectWrapper::getArgument - // from ESM::Attribute, ESM::Skill and MWMechanics::CreatureStats (for AI) - static std::map populateEncToInfoFunc() - { - std::map funcMap; - funcMap["00"] = "Rank Low"; - funcMap["01"] = "Rank High"; - funcMap["02"] = "Rank Requirement"; - funcMap["03"] = "Reputation"; - funcMap["04"] = "Health Percent"; - funcMap["05"] = "PC Reputation"; - funcMap["06"] = "PC Level"; - funcMap["07"] = "PC Health Percent"; - funcMap["08"] = "PC Magicka"; - funcMap["09"] = "PC Fatigue"; - funcMap["10"] = "PC Strength"; - funcMap["11"] = "PC Block"; - funcMap["12"] = "PC Armorer"; - funcMap["13"] = "PC Medium Armor"; - funcMap["14"] = "PC Heavy Armor"; - funcMap["15"] = "PC Blunt Weapon"; - funcMap["16"] = "PC Long Blade"; - funcMap["17"] = "PC Axe"; - funcMap["18"] = "PC Spear"; - funcMap["19"] = "PC Athletics"; - funcMap["20"] = "PC Enchant"; - funcMap["21"] = "PC Destruction"; - funcMap["22"] = "PC Alteration"; - funcMap["23"] = "PC Illusion"; - funcMap["24"] = "PC Conjuration"; - funcMap["25"] = "PC Mysticism"; - funcMap["26"] = "PC Restoration"; - funcMap["27"] = "PC Alchemy"; - funcMap["28"] = "PC Unarmored"; - funcMap["29"] = "PC Security"; - funcMap["30"] = "PC Sneak"; - funcMap["31"] = "PC Acrobatics"; - funcMap["32"] = "PC Light Armor"; - funcMap["33"] = "PC Short Blade"; - funcMap["34"] = "PC Marksman"; - funcMap["35"] = "PC Merchantile"; - funcMap["36"] = "PC Speechcraft"; - funcMap["37"] = "PC Hand To Hand"; - funcMap["38"] = "PC Sex"; - funcMap["39"] = "PC Expelled"; - funcMap["40"] = "PC Common Disease"; - funcMap["41"] = "PC Blight Disease"; - funcMap["42"] = "PC Clothing Modifier"; - funcMap["43"] = "PC Crime Level"; - funcMap["44"] = "Same Sex"; - funcMap["45"] = "Same Race"; - funcMap["46"] = "Same Faction"; - funcMap["47"] = "Faction Rank Difference"; - funcMap["48"] = "Detected"; - funcMap["49"] = "Alarmed"; - funcMap["50"] = "Choice"; - funcMap["51"] = "PC Intelligence"; - funcMap["52"] = "PC Willpower"; - funcMap["53"] = "PC Agility"; - funcMap["54"] = "PC Speed"; - funcMap["55"] = "PC Endurance"; - funcMap["56"] = "PC Personality"; - funcMap["57"] = "PC Luck"; - funcMap["58"] = "PC Corpus"; - funcMap["59"] = "Weather"; - funcMap["60"] = "PC Vampire"; - funcMap["61"] = "Level"; - funcMap["62"] = "Attacked"; - funcMap["63"] = "Talked To PC"; - funcMap["64"] = "PC Health"; - funcMap["65"] = "Creature Target"; - funcMap["66"] = "Friend Hit"; - funcMap["67"] = "Fight"; - funcMap["68"] = "Hello"; - funcMap["69"] = "Alarm"; - funcMap["70"] = "Flee"; - funcMap["71"] = "Should Attack"; - funcMap["72"] = "Werewolf"; - funcMap["73"] = "PC Werewolf Kills"; - return funcMap; - } - static const std::map sEncToInfoFunc = populateEncToInfoFunc(); - QVariant InfoConditionAdapter::getData(const Record& record, int subRowIndex, int subColIndex) const { @@ -682,70 +552,36 @@ namespace CSMWorld if (subRowIndex < 0 || subRowIndex >= static_cast (conditions.size())) throw std::runtime_error ("index out of range"); + ConstInfoSelectWrapper infoSelectWrapper(conditions[subRowIndex]); + switch (subColIndex) { case 0: { - char condType = conditions[subRowIndex].mSelectRule[1]; - switch (condType) - { - case '0': return 0; // blank space - case '1': return 1; // Function - case '2': return 2; // Global - case '3': return 3; // Local - case '4': return 4; // Journal - case '5': return 5; // Item - case '6': return 6; // Dead - case '7': return 7; // Not ID - case '8': return 8; // Not Factio - case '9': return 9; // Not Class - case 'A': return 10; // Not Race - case 'B': return 11; // Not Cell - case 'C': return 12; // Not Local - default: return QVariant(); // TODO: log an error? - } + return infoSelectWrapper.getFunctionName(); } case 1: { - if (conditions[subRowIndex].mSelectRule[1] == '1') - { - // throws an exception if the encoding is not found - return sEncToInfoFunc.at(conditions[subRowIndex].mSelectRule.substr(2, 2)).c_str(); - } + if (infoSelectWrapper.hasVariable()) + return QString(infoSelectWrapper.getVariableName().c_str()); else - return QString(conditions[subRowIndex].mSelectRule.substr(5).c_str()); + return ""; } case 2: { - char compType = conditions[subRowIndex].mSelectRule[4]; - switch (compType) - { - case '0': return 3; // = - case '1': return 0; // != - case '2': return 4; // > - case '3': return 5; // >= - case '4': return 1; // < - case '5': return 2; // <= - default: return QVariant(); // TODO: log an error? - } + return infoSelectWrapper.getRelationType(); } case 3: { - switch (conditions[subRowIndex].mValue.getType()) + switch (infoSelectWrapper.getVariant().getType()) { - case ESM::VT_String: - { - return QString::fromUtf8 (conditions[subRowIndex].mValue.getString().c_str()); - } case ESM::VT_Int: - case ESM::VT_Short: - case ESM::VT_Long: { - return conditions[subRowIndex].mValue.getInteger(); + return infoSelectWrapper.getVariant().getInteger(); } case ESM::VT_Float: { - return conditions[subRowIndex].mValue.getFloat(); + return infoSelectWrapper.getVariant().getFloat(); } default: return QVariant(); } @@ -764,101 +600,63 @@ namespace CSMWorld if (subRowIndex < 0 || subRowIndex >= static_cast (conditions.size())) throw std::runtime_error ("index out of range"); + InfoSelectWrapper infoSelectWrapper(conditions[subRowIndex]); + bool conversionResult = false; + switch (subColIndex) { - case 0: + case 0: // Function { - // See sInfoCondFunc in columns.cpp for the enum values - switch (value.toInt()) + infoSelectWrapper.setFunctionName(static_cast(value.toInt())); + + if (infoSelectWrapper.getComparisonType() != ConstInfoSelectWrapper::Comparison_Numeric && + infoSelectWrapper.getVariant().getType() != ESM::VT_Int) { - // FIXME: when these change the values of the other columns need to change - // correspondingly (and automatically) - case 1: - { - conditions[subRowIndex].mSelectRule[1] = '1'; // Function - // default to "Rank Low" - conditions[subRowIndex].mSelectRule[2] = '0'; - conditions[subRowIndex].mSelectRule[3] = '0'; - break; - } - case 2: conditions[subRowIndex].mSelectRule[1] = '2'; break; // Global - case 3: conditions[subRowIndex].mSelectRule[1] = '3'; break; // Local - case 4: conditions[subRowIndex].mSelectRule[1] = '4'; break; // Journal - case 5: conditions[subRowIndex].mSelectRule[1] = '5'; break; // Item - case 6: conditions[subRowIndex].mSelectRule[1] = '6'; break; // Dead - case 7: conditions[subRowIndex].mSelectRule[1] = '7'; break; // Not ID - case 8: conditions[subRowIndex].mSelectRule[1] = '8'; break; // Not Faction - case 9: conditions[subRowIndex].mSelectRule[1] = '9'; break; // Not Class - case 10: conditions[subRowIndex].mSelectRule[1] = 'A'; break; // Not Race - case 11: conditions[subRowIndex].mSelectRule[1] = 'B'; break; // Not Cell - case 12: conditions[subRowIndex].mSelectRule[1] = 'C'; break; // Not Local - default: return; // return without saving + infoSelectWrapper.getVariant().setType(ESM::VT_Int); } + + infoSelectWrapper.update(); break; } - case 1: + case 1: // Variable { - if (conditions[subRowIndex].mSelectRule[1] == '1') - { - std::map::const_iterator it = sEncToInfoFunc.begin(); - for (;it != sEncToInfoFunc.end(); ++it) - { - if (it->second == value.toString().toUtf8().constData()) - { - std::string rule = conditions[subRowIndex].mSelectRule.substr(0, 2); - rule.append(it->first); - // leave old values for undo (NOTE: may not be vanilla's behaviour) - rule.append(conditions[subRowIndex].mSelectRule.substr(4)); - conditions[subRowIndex].mSelectRule = rule; - break; - } - } - - if (it == sEncToInfoFunc.end()) - return; // return without saving; TODO: maybe log an error here - } - else - { - // FIXME: validate the string values before saving, based on the current function - std::string rule = conditions[subRowIndex].mSelectRule.substr(0, 5); - conditions[subRowIndex].mSelectRule = rule.append(value.toString().toUtf8().constData()); - } + infoSelectWrapper.setVariableName(value.toString().toUtf8().constData()); + infoSelectWrapper.update(); break; } - case 2: + case 2: // Relation { - // See sInfoCondComp in columns.cpp for the enum values - switch (value.toInt()) - { - case 0: conditions[subRowIndex].mSelectRule[4] = '1'; break; // != - case 1: conditions[subRowIndex].mSelectRule[4] = '4'; break; // < - case 2: conditions[subRowIndex].mSelectRule[4] = '5'; break; // <= - case 3: conditions[subRowIndex].mSelectRule[4] = '0'; break; // = - case 4: conditions[subRowIndex].mSelectRule[4] = '2'; break; // > - case 5: conditions[subRowIndex].mSelectRule[4] = '3'; break; // >= - default: return; // return without saving - } + infoSelectWrapper.setRelationType(static_cast(value.toInt())); + infoSelectWrapper.update(); break; } - case 3: + case 3: // Value { - switch (conditions[subRowIndex].mValue.getType()) + switch (infoSelectWrapper.getComparisonType()) { - case ESM::VT_String: - { - conditions[subRowIndex].mValue.setString (value.toString().toUtf8().constData()); - break; - } - case ESM::VT_Int: - case ESM::VT_Short: - case ESM::VT_Long: + case ConstInfoSelectWrapper::Comparison_Numeric: { - conditions[subRowIndex].mValue.setInteger (value.toInt()); + // QVariant seems to have issues converting 0 + if ((value.toInt(&conversionResult) && conversionResult) || value.toString().compare("0") == 0) + { + infoSelectWrapper.getVariant().setType(ESM::VT_Int); + infoSelectWrapper.getVariant().setInteger(value.toInt()); + } + else if (value.toFloat(&conversionResult) && conversionResult) + { + infoSelectWrapper.getVariant().setType(ESM::VT_Float); + infoSelectWrapper.getVariant().setFloat(value.toFloat()); + } break; } - case ESM::VT_Float: + case ConstInfoSelectWrapper::Comparison_Boolean: + case ConstInfoSelectWrapper::Comparison_Integer: { - conditions[subRowIndex].mValue.setFloat (value.toFloat()); + if ((value.toInt(&conversionResult) && conversionResult) || value.toString().compare("0") == 0) + { + infoSelectWrapper.getVariant().setType(ESM::VT_Int); + infoSelectWrapper.getVariant().setInteger(value.toInt()); + } break; } default: break; @@ -1079,7 +877,7 @@ namespace CSMWorld cell.mAmbi.mFogDensity : QVariant(QVariant::UserType); case 5: { - if (isInterior && !behaveLikeExterior && interiorWater) + if (isInterior && interiorWater) return cell.mWater; else return QVariant(QVariant::UserType); @@ -1145,7 +943,7 @@ namespace CSMWorld } case 5: { - if (isInterior && !behaveLikeExterior && interiorWater) + if (isInterior && interiorWater) cell.mWater = value.toFloat(); else return; // return without saving @@ -1191,4 +989,105 @@ namespace CSMWorld { return 1; // fixed at size 1 } + + RegionWeatherAdapter::RegionWeatherAdapter () {} + + void RegionWeatherAdapter::addRow(Record& record, int position) const + { + throw std::logic_error ("cannot add a row to a fixed table"); + } + + void RegionWeatherAdapter::removeRow(Record& record, int rowToRemove) const + { + throw std::logic_error ("cannot remove a row from a fixed table"); + } + + void RegionWeatherAdapter::setTable(Record& record, const NestedTableWrapperBase& nestedTable) const + { + throw std::logic_error ("table operation not supported"); + } + + NestedTableWrapperBase* RegionWeatherAdapter::table(const Record& record) const + { + throw std::logic_error ("table operation not supported"); + } + + QVariant RegionWeatherAdapter::getData(const Record& record, int subRowIndex, int subColIndex) const + { + const char* WeatherNames[] = { + "Clear", + "Cloudy", + "Fog", + "Overcast", + "Rain", + "Thunder", + "Ash", + "Blight", + "Snow", + "Blizzard" + }; + + const ESM::Region& region = record.get(); + + if (subColIndex == 0 && subRowIndex >= 0 && subRowIndex < 10) + { + return WeatherNames[subRowIndex]; + } + else if (subColIndex == 1) + { + switch (subRowIndex) + { + case 0: return region.mData.mClear; + case 1: return region.mData.mCloudy; + case 2: return region.mData.mFoggy; + case 3: return region.mData.mOvercast; + case 4: return region.mData.mRain; + case 5: return region.mData.mThunder; + case 6: return region.mData.mAsh; + case 7: return region.mData.mBlight; + case 8: return region.mData.mA; // Snow + case 9: return region.mData.mB; // Blizzard + default: break; + } + } + + throw std::runtime_error("index out of range"); + } + + void RegionWeatherAdapter::setData(Record& record, const QVariant& value, int subRowIndex, + int subColIndex) const + { + ESM::Region region = record.get(); + unsigned char chance = static_cast(value.toInt()); + + if (subColIndex == 1) + { + switch (subRowIndex) + { + case 0: region.mData.mClear = chance; break; + case 1: region.mData.mCloudy = chance; break; + case 2: region.mData.mFoggy = chance; break; + case 3: region.mData.mOvercast = chance; break; + case 4: region.mData.mRain = chance; break; + case 5: region.mData.mThunder = chance; break; + case 6: region.mData.mAsh = chance; break; + case 7: region.mData.mBlight = chance; break; + case 8: region.mData.mA = chance; break; + case 9: region.mData.mB = chance; break; + default: throw std::runtime_error("index out of range"); + } + + record.setModified (region); + } + } + + int RegionWeatherAdapter::getColumnsCount(const Record& record) const + { + return 2; + } + + int RegionWeatherAdapter::getRowsCount(const Record& record) const + { + return 10; + } } diff --git a/apps/opencs/model/world/nestedcoladapterimp.hpp b/apps/opencs/model/world/nestedcoladapterimp.hpp index 2fd569bd0..131f547a5 100644 --- a/apps/opencs/model/world/nestedcoladapterimp.hpp +++ b/apps/opencs/model/world/nestedcoladapterimp.hpp @@ -25,21 +25,6 @@ namespace CSMWorld struct Pathgrid; struct Info; - struct PathgridPointsWrap : public NestedTableWrapperBase - { - ESM::Pathgrid mRecord; - - PathgridPointsWrap(ESM::Pathgrid pathgrid) - : mRecord(pathgrid) {} - - virtual ~PathgridPointsWrap() {} - - virtual int size() const - { - return mRecord.mPoints.size(); // used in IdTree::setNestedTable() - } - }; - class PathgridPointListAdapter : public NestedColumnAdapter { public: @@ -540,6 +525,31 @@ namespace CSMWorld virtual int getRowsCount(const Record& record) const; }; + + class RegionWeatherAdapter : public NestedColumnAdapter + { + public: + RegionWeatherAdapter (); + + virtual void addRow(Record& record, int position) const; + + virtual void removeRow(Record& record, int rowToRemove) const; + + virtual void setTable(Record& record, + const NestedTableWrapperBase& nestedTable) const; + + virtual NestedTableWrapperBase* table(const Record& record) const; + + virtual QVariant getData(const Record& record, + int subRowIndex, int subColIndex) const; + + virtual void setData(Record& record, + const QVariant& value, int subRowIndex, int subColIndex) const; + + virtual int getColumnsCount(const Record& record) const; + + virtual int getRowsCount(const Record& record) const; + }; } #endif // CSM_WOLRD_NESTEDCOLADAPTERIMP_H diff --git a/apps/opencs/model/world/ref.cpp b/apps/opencs/model/world/ref.cpp index 638f7ec9c..0439b9448 100644 --- a/apps/opencs/model/world/ref.cpp +++ b/apps/opencs/model/world/ref.cpp @@ -2,7 +2,11 @@ #include -CSMWorld::CellRef::CellRef() +#include + +#include "cellcoordinates.hpp" + +CSMWorld::CellRef::CellRef() : mNew (true) { mRefNum.mIndex = 0; mRefNum.mContentFile = 0; @@ -10,8 +14,5 @@ CSMWorld::CellRef::CellRef() std::pair CSMWorld::CellRef::getCellIndex() const { - const int cellSize = 8192; - - return std::make_pair ( - std::floor (mPos.pos[0]/cellSize), std::floor (mPos.pos[1]/cellSize)); + return CellCoordinates::coordinatesToCellIndex (mPos.pos[0], mPos.pos[1]); } diff --git a/apps/opencs/model/world/ref.hpp b/apps/opencs/model/world/ref.hpp index c60392221..5d10a3a1b 100644 --- a/apps/opencs/model/world/ref.hpp +++ b/apps/opencs/model/world/ref.hpp @@ -13,6 +13,7 @@ namespace CSMWorld std::string mId; std::string mCell; std::string mOriginalCell; + bool mNew; // new reference, not counted yet, ref num not assigned yet CellRef(); diff --git a/apps/opencs/model/world/refcollection.cpp b/apps/opencs/model/world/refcollection.cpp index 65251a81d..6b586dcec 100644 --- a/apps/opencs/model/world/refcollection.cpp +++ b/apps/opencs/model/world/refcollection.cpp @@ -19,6 +19,7 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool Cell& cell2 = base ? cell.mBase : cell.mModified; CellRef ref; + ref.mNew = false; ESM::MovedCellRef mref; bool isDeleted = false; @@ -62,7 +63,7 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool std::cerr << "Position: #" << index.first << " " << index.second <<", Target #"<< mref.mTarget[0] << " " << mref.mTarget[1] << std::endl; - std::ostringstream stream; + stream.clear(); stream << "#" << mref.mTarget[0] << " " << mref.mTarget[1]; ref.mCell = stream.str(); // overwrite } diff --git a/apps/opencs/model/world/refidadapter.hpp b/apps/opencs/model/world/refidadapter.hpp index ba9da577d..116adb69a 100644 --- a/apps/opencs/model/world/refidadapter.hpp +++ b/apps/opencs/model/world/refidadapter.hpp @@ -8,7 +8,7 @@ * Adapters acts as indirection layer, abstracting details of the record types (in the wrappers) from the higher levels of model. * Please notice that nested adaptor uses helper classes for actually performing any actions. Different record types require different helpers (needs to be created in the subclass and then fetched via member function). * - * Important point: don't forget to make sure that getData on the nestedColumn returns true (otherwise code will not treat the index pointing to the column as having childs! + * Important point: don't forget to make sure that getData on the nestedColumn returns true (otherwise code will not treat the index pointing to the column as having children! */ class QVariant; diff --git a/apps/opencs/model/world/refidadapterimp.cpp b/apps/opencs/model/world/refidadapterimp.cpp index 039624c84..e8f921580 100644 --- a/apps/opencs/model/world/refidadapterimp.cpp +++ b/apps/opencs/model/world/refidadapterimp.cpp @@ -108,7 +108,7 @@ void CSMWorld::IngredEffectRefIdAdapter::setNestedTable (const RefIdColumn* colu ESM::Ingredient ingredient = record.get(); ingredient.mData = - static_cast >&>(nestedTable).mNestedTable.at(0); + static_cast >&>(nestedTable).mNestedTable.at(0); record.setModified (ingredient); } @@ -120,11 +120,11 @@ CSMWorld::NestedTableWrapperBase* CSMWorld::IngredEffectRefIdAdapter::nestedTabl static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); // return the whole struct - std::vector wrap; + std::vector wrap; wrap.push_back(record.get().mData); // deleted by dtor of NestedTableStoring - return new NestedTableWrapper >(wrap); + return new NestedTableWrapper >(wrap); } QVariant CSMWorld::IngredEffectRefIdAdapter::getNestedData (const RefIdColumn *column, @@ -301,9 +301,9 @@ void CSMWorld::ArmorRefIdAdapter::setData (const RefIdColumn *column, RefIdData& } CSMWorld::BookRefIdAdapter::BookRefIdAdapter (const EnchantableColumns& columns, - const RefIdColumn *scroll, const RefIdColumn *skill) + const RefIdColumn *bookType, const RefIdColumn *skill, const RefIdColumn *text) : EnchantableRefIdAdapter (UniversalId::Type_Book, columns), - mScroll (scroll), mSkill (skill) + mBookType (bookType), mSkill (skill), mText (text) {} QVariant CSMWorld::BookRefIdAdapter::getData (const RefIdColumn *column, @@ -312,11 +312,14 @@ QVariant CSMWorld::BookRefIdAdapter::getData (const RefIdColumn *column, const Record& record = static_cast&> ( data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Book))); - if (column==mScroll) - return record.get().mData.mIsScroll!=0; + if (column==mBookType) + return record.get().mData.mIsScroll; if (column==mSkill) - return record.get().mData.mSkillID; + return record.get().mData.mSkillId; + + if (column==mText) + return QString::fromUtf8 (record.get().mText.c_str()); return EnchantableRefIdAdapter::getData (column, data, index); } @@ -329,10 +332,12 @@ void CSMWorld::BookRefIdAdapter::setData (const RefIdColumn *column, RefIdData& ESM::Book book = record.get(); - if (column==mScroll) + if (column==mBookType) book.mData.mIsScroll = value.toInt(); else if (column==mSkill) - book.mData.mSkillID = value.toInt(); + book.mData.mSkillId = value.toInt(); + else if (column==mText) + book.mText = value.toString().toUtf8().data(); else { EnchantableRefIdAdapter::setData (column, data, index, value); @@ -453,7 +458,8 @@ CSMWorld::CreatureColumns::CreatureColumns (const ActorColumns& actorColumns) mOriginal(NULL), mAttributes(NULL), mAttacks(NULL), - mMisc(NULL) + mMisc(NULL), + mBloodType(NULL) {} CSMWorld::CreatureRefIdAdapter::CreatureRefIdAdapter (const CreatureColumns& columns) @@ -484,6 +490,19 @@ QVariant CSMWorld::CreatureRefIdAdapter::getData (const RefIdColumn *column, con if (column==mColumns.mMisc) return QVariant::fromValue(ColumnBase::TableEdit_Full); + if (column == mColumns.mBloodType) + { + int mask = ESM::Creature::Skeleton | ESM::Creature::Metal; + + if ((record.get().mFlags & mask) == ESM::Creature::Skeleton) + return 1; + + if ((record.get().mFlags & mask) == ESM::Creature::Metal) + return 2; + + return 0; + } + std::map::const_iterator iter = mColumns.mFlags.find (column); @@ -507,6 +526,17 @@ void CSMWorld::CreatureRefIdAdapter::setData (const RefIdColumn *column, RefIdDa creature.mScale = value.toFloat(); else if (column==mColumns.mOriginal) creature.mOriginal = value.toString().toUtf8().constData(); + else if (column == mColumns.mBloodType) + { + int mask = ~(ESM::Creature::Skeleton | ESM::Creature::Metal); + + if (value.toInt() == 1) + creature.mFlags = (creature.mFlags & mask) | ESM::Creature::Skeleton; + else if (value.toInt() == 2) + creature.mFlags = (creature.mFlags & mask) | ESM::Creature::Metal; + else + creature.mFlags = creature.mFlags & mask; + } else { std::map::const_iterator iter = @@ -598,6 +628,25 @@ QVariant CSMWorld::LightRefIdAdapter::getData (const RefIdColumn *column, const if (column==mColumns.mSound) return QString::fromUtf8 (record.get().mSound.c_str()); + if (column == mColumns.mEmitterType) + { + int mask = ESM::Light::Flicker | ESM::Light::FlickerSlow | ESM::Light::Pulse | ESM::Light::PulseSlow; + + if ((record.get().mData.mFlags & mask) == ESM::Light::Flicker) + return 1; + + if ((record.get().mData.mFlags & mask) == ESM::Light::FlickerSlow) + return 2; + + if ((record.get().mData.mFlags & mask) == ESM::Light::Pulse) + return 3; + + if ((record.get().mData.mFlags & mask) == ESM::Light::PulseSlow) + return 4; + + return 0; + } + std::map::const_iterator iter = mColumns.mFlags.find (column); @@ -623,6 +672,21 @@ void CSMWorld::LightRefIdAdapter::setData (const RefIdColumn *column, RefIdData& light.mData.mColor = value.toInt(); else if (column==mColumns.mSound) light.mSound = value.toString().toUtf8().constData(); + else if (column == mColumns.mEmitterType) + { + int mask = ~(ESM::Light::Flicker | ESM::Light::FlickerSlow | ESM::Light::Pulse | ESM::Light::PulseSlow); + + if (value.toInt() == 0) + light.mData.mFlags = light.mData.mFlags & mask; + else if (value.toInt() == 1) + light.mData.mFlags = (light.mData.mFlags & mask) | ESM::Light::Flicker; + else if (value.toInt() == 2) + light.mData.mFlags = (light.mData.mFlags & mask) | ESM::Light::FlickerSlow; + else if (value.toInt() == 3) + light.mData.mFlags = (light.mData.mFlags & mask) | ESM::Light::Pulse; + else + light.mData.mFlags = (light.mData.mFlags & mask) | ESM::Light::PulseSlow; + } else { std::map::const_iterator iter = @@ -691,7 +755,9 @@ CSMWorld::NpcColumns::NpcColumns (const ActorColumns& actorColumns) mHead(NULL), mAttributes(NULL), mSkills(NULL), - mMisc(NULL) + mMisc(NULL), + mBloodType(NULL), + mGender(NULL) {} CSMWorld::NpcRefIdAdapter::NpcRefIdAdapter (const NpcColumns& columns) @@ -730,6 +796,28 @@ QVariant CSMWorld::NpcRefIdAdapter::getData (const RefIdColumn *column, const Re if (column==mColumns.mMisc) return QVariant::fromValue(ColumnBase::TableEdit_Full); + if (column == mColumns.mBloodType) + { + int mask = ESM::NPC::Skeleton | ESM::NPC::Metal; + + if ((record.get().mFlags & mask) == ESM::NPC::Skeleton) + return 1; + + if ((record.get().mFlags & mask) == ESM::NPC::Metal) + return 2; + + return 0; + } + + if (column == mColumns.mGender) + { + // Implemented this way to allow additional gender types in the future. + if ((record.get().mFlags & ESM::NPC::Female) == ESM::NPC::Female) + return 1; + + return 0; + } + std::map::const_iterator iter = mColumns.mFlags.find (column); @@ -757,6 +845,25 @@ void CSMWorld::NpcRefIdAdapter::setData (const RefIdColumn *column, RefIdData& d npc.mHair = value.toString().toUtf8().constData(); else if (column==mColumns.mHead) npc.mHead = value.toString().toUtf8().constData(); + else if (column == mColumns.mBloodType) + { + int mask = ~(ESM::NPC::Skeleton | ESM::NPC::Metal); + + if (value.toInt() == 1) + npc.mFlags = (npc.mFlags & mask) | ESM::NPC::Skeleton; + else if (value.toInt() == 2) + npc.mFlags = (npc.mFlags & mask) | ESM::NPC::Metal; + else + npc.mFlags = npc.mFlags & mask; + } + else if (column == mColumns.mGender) + { + // Implemented this way to allow additional gender types in the future. + if (value.toInt() == 1) + npc.mFlags = (npc.mFlags & ~ESM::NPC::Female) | ESM::NPC::Female; + else + npc.mFlags = npc.mFlags & ~ESM::NPC::Female; + } else { std::map::const_iterator iter = @@ -1129,7 +1236,7 @@ void CSMWorld::CreatureAttributesRefIdAdapter::setNestedTable (const RefIdColumn // store the whole struct creature.mData = - static_cast > &>(nestedTable).mNestedTable.at(0); + static_cast > &>(nestedTable).mNestedTable.at(0); record.setModified (creature); } @@ -1141,10 +1248,10 @@ CSMWorld::NestedTableWrapperBase* CSMWorld::CreatureAttributesRefIdAdapter::nest static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Creature))); // return the whole struct - std::vector wrap; + std::vector wrap; wrap.push_back(record.get().mData); // deleted by dtor of NestedTableStoring - return new NestedTableWrapper >(wrap); + return new NestedTableWrapper >(wrap); } QVariant CSMWorld::CreatureAttributesRefIdAdapter::getNestedData (const RefIdColumn *column, @@ -1235,7 +1342,7 @@ void CSMWorld::CreatureAttackRefIdAdapter::setNestedTable (const RefIdColumn* co // store the whole struct creature.mData = - static_cast > &>(nestedTable).mNestedTable.at(0); + static_cast > &>(nestedTable).mNestedTable.at(0); record.setModified (creature); } @@ -1247,10 +1354,10 @@ CSMWorld::NestedTableWrapperBase* CSMWorld::CreatureAttackRefIdAdapter::nestedTa static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Creature))); // return the whole struct - std::vector wrap; + std::vector wrap; wrap.push_back(record.get().mData); // deleted by dtor of NestedTableStoring - return new NestedTableWrapper >(wrap); + return new NestedTableWrapper >(wrap); } QVariant CSMWorld::CreatureAttackRefIdAdapter::getNestedData (const RefIdColumn *column, @@ -1440,26 +1547,28 @@ void CSMWorld::WeaponRefIdAdapter::setData (const RefIdColumn *column, RefIdData Record& record = static_cast&> ( data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Weapon))); + ESM::Weapon weapon = record.get(); + if (column==mColumns.mType) - record.get().mData.mType = value.toInt(); + weapon.mData.mType = value.toInt(); else if (column==mColumns.mHealth) - record.get().mData.mHealth = value.toInt(); + weapon.mData.mHealth = value.toInt(); else if (column==mColumns.mSpeed) - record.get().mData.mSpeed = value.toFloat(); + weapon.mData.mSpeed = value.toFloat(); else if (column==mColumns.mReach) - record.get().mData.mReach = value.toFloat(); + weapon.mData.mReach = value.toFloat(); else if (column==mColumns.mChop[0]) - record.get().mData.mChop[0] = value.toInt(); + weapon.mData.mChop[0] = value.toInt(); else if (column==mColumns.mChop[1]) - record.get().mData.mChop[1] = value.toInt(); + weapon.mData.mChop[1] = value.toInt(); else if (column==mColumns.mSlash[0]) - record.get().mData.mSlash[0] = value.toInt(); + weapon.mData.mSlash[0] = value.toInt(); else if (column==mColumns.mSlash[1]) - record.get().mData.mSlash[1] = value.toInt(); + weapon.mData.mSlash[1] = value.toInt(); else if (column==mColumns.mThrust[0]) - record.get().mData.mThrust[0] = value.toInt(); + weapon.mData.mThrust[0] = value.toInt(); else if (column==mColumns.mThrust[1]) - record.get().mData.mThrust[1] = value.toInt(); + weapon.mData.mThrust[1] = value.toInt(); else { std::map::const_iterator iter = @@ -1468,11 +1577,16 @@ void CSMWorld::WeaponRefIdAdapter::setData (const RefIdColumn *column, RefIdData if (iter!=mColumns.mFlags.end()) { if (value.toInt()!=0) - record.get().mData.mFlags |= iter->second; + weapon.mData.mFlags |= iter->second; else - record.get().mData.mFlags &= ~iter->second; + weapon.mData.mFlags &= ~iter->second; } else + { EnchantableRefIdAdapter::setData (column, data, index, value); + return; // Don't overwrite changes made by base class + } } + + record.setModified(weapon); } diff --git a/apps/opencs/model/world/refidadapterimp.hpp b/apps/opencs/model/world/refidadapterimp.hpp index eff7167de..c05a20d07 100644 --- a/apps/opencs/model/world/refidadapterimp.hpp +++ b/apps/opencs/model/world/refidadapterimp.hpp @@ -694,13 +694,14 @@ namespace CSMWorld class BookRefIdAdapter : public EnchantableRefIdAdapter { - const RefIdColumn *mScroll; + const RefIdColumn *mBookType; const RefIdColumn *mSkill; + const RefIdColumn *mText; public: - BookRefIdAdapter (const EnchantableColumns& columns, const RefIdColumn *scroll, - const RefIdColumn *skill); + BookRefIdAdapter (const EnchantableColumns& columns, const RefIdColumn *bookType, + const RefIdColumn *skill, const RefIdColumn *text); virtual QVariant getData (const RefIdColumn *column, const RefIdData& data, int index) const; @@ -756,6 +757,7 @@ namespace CSMWorld const RefIdColumn *mAttributes; const RefIdColumn *mAttacks; const RefIdColumn *mMisc; + const RefIdColumn *mBloodType; CreatureColumns (const ActorColumns& actorColumns); }; @@ -800,6 +802,7 @@ namespace CSMWorld const RefIdColumn *mRadius; const RefIdColumn *mColor; const RefIdColumn *mSound; + const RefIdColumn *mEmitterType; std::map mFlags; LightColumns (const InventoryColumns& columns); @@ -848,6 +851,8 @@ namespace CSMWorld const RefIdColumn *mAttributes; // depends on npc type const RefIdColumn *mSkills; // depends on npc type const RefIdColumn *mMisc; // may depend on npc type, e.g. FactionID + const RefIdColumn *mBloodType; + const RefIdColumn *mGender; NpcColumns (const ActorColumns& actorColumns); }; @@ -1187,7 +1192,7 @@ namespace CSMWorld std::vector& list = container.mInventory.mList; - ESM::ContItem newRow = {0, {""}}; + ESM::ContItem newRow = ESM::ContItem(); if (position >= (int)list.size()) list.push_back(newRow); diff --git a/apps/opencs/model/world/refidcollection.cpp b/apps/opencs/model/world/refidcollection.cpp index c4c8f8605..c0ce575b7 100644 --- a/apps/opencs/model/world/refidcollection.cpp +++ b/apps/opencs/model/world/refidcollection.cpp @@ -291,11 +291,14 @@ CSMWorld::RefIdCollection::RefIdCollection() mColumns.push_back (RefIdColumn (Columns::ColumnId_ArmorValue, ColumnBase::Display_Integer)); const RefIdColumn *armor = &mColumns.back(); - mColumns.push_back (RefIdColumn (Columns::ColumnId_Scroll, ColumnBase::Display_Boolean)); - const RefIdColumn *scroll = &mColumns.back(); + mColumns.push_back (RefIdColumn (Columns::ColumnId_BookType, ColumnBase::Display_BookType)); + const RefIdColumn *bookType = &mColumns.back(); - mColumns.push_back (RefIdColumn (Columns::ColumnId_Attribute, ColumnBase::Display_Attribute)); - const RefIdColumn *attribute = &mColumns.back(); + mColumns.push_back (RefIdColumn (Columns::ColumnId_Skill, ColumnBase::Display_SkillId)); + const RefIdColumn *skill = &mColumns.back(); + + mColumns.push_back (RefIdColumn (Columns::ColumnId_Text, ColumnBase::Display_LongString)); + const RefIdColumn *text = &mColumns.back(); mColumns.push_back (RefIdColumn (Columns::ColumnId_ClothingType, ColumnBase::Display_ClothingType)); const RefIdColumn *clothingType = &mColumns.back(); @@ -343,15 +346,11 @@ CSMWorld::RefIdCollection::RefIdCollection() { Columns::ColumnId_Flies, ESM::Creature::Flies }, { Columns::ColumnId_Walks, ESM::Creature::Walks }, { Columns::ColumnId_Essential, ESM::Creature::Essential }, - { Columns::ColumnId_SkeletonBlood, ESM::Creature::Skeleton }, - { Columns::ColumnId_MetalBlood, ESM::Creature::Metal }, { -1, 0 } }; // for re-use in NPC records const RefIdColumn *essential = 0; - const RefIdColumn *skeletonBlood = 0; - const RefIdColumn *metalBlood = 0; for (int i=0; sCreatureFlagTable[i].mName!=-1; ++i) { @@ -361,11 +360,14 @@ CSMWorld::RefIdCollection::RefIdCollection() switch (sCreatureFlagTable[i].mFlag) { case ESM::Creature::Essential: essential = &mColumns.back(); break; - case ESM::Creature::Skeleton: skeletonBlood = &mColumns.back(); break; - case ESM::Creature::Metal: metalBlood = &mColumns.back(); break; } } + mColumns.push_back(RefIdColumn(Columns::ColumnId_BloodType, ColumnBase::Display_BloodType)); + // For re-use in NPC records. + const RefIdColumn *bloodType = &mColumns.back(); + creatureColumns.mBloodType = bloodType; + creatureColumns.mFlags.insert (std::make_pair (respawn, ESM::Creature::Respawn)); // Nested table @@ -441,6 +443,9 @@ CSMWorld::RefIdCollection::RefIdCollection() mColumns.push_back (RefIdColumn (Columns::ColumnId_Sound, ColumnBase::Display_Sound)); lightColumns.mSound = &mColumns.back(); + mColumns.push_back(RefIdColumn(Columns::ColumnId_EmitterType, ColumnBase::Display_EmitterType)); + lightColumns.mEmitterType = &mColumns.back(); + static const struct { int mName; @@ -450,10 +455,6 @@ CSMWorld::RefIdCollection::RefIdCollection() { Columns::ColumnId_Dynamic, ESM::Light::Dynamic }, { Columns::ColumnId_Portable, ESM::Light::Carry }, { Columns::ColumnId_NegativeLight, ESM::Light::Negative }, - { Columns::ColumnId_Flickering, ESM::Light::Flicker }, - { Columns::ColumnId_SlowFlickering, ESM::Light::FlickerSlow }, - { Columns::ColumnId_Pulsing, ESM::Light::Pulse }, - { Columns::ColumnId_SlowPulsing, ESM::Light::PulseSlow }, { Columns::ColumnId_Fire, ESM::Light::Fire }, { Columns::ColumnId_OffByDefault, ESM::Light::OffDefault }, { -1, 0 } @@ -485,8 +486,8 @@ CSMWorld::RefIdCollection::RefIdCollection() mColumns.push_back (RefIdColumn (Columns::ColumnId_Head, ColumnBase::Display_BodyPart)); npcColumns.mHead = &mColumns.back(); - mColumns.push_back (RefIdColumn (Columns::ColumnId_Female, ColumnBase::Display_Boolean)); - npcColumns.mFlags.insert (std::make_pair (&mColumns.back(), ESM::NPC::Female)); + mColumns.push_back (RefIdColumn (Columns::ColumnId_GenderNpc, ColumnBase::Display_GenderNpc)); + npcColumns.mGender = &mColumns.back(); npcColumns.mFlags.insert (std::make_pair (essential, ESM::NPC::Essential)); @@ -494,9 +495,8 @@ CSMWorld::RefIdCollection::RefIdCollection() npcColumns.mFlags.insert (std::make_pair (autoCalc, ESM::NPC::Autocalc)); - npcColumns.mFlags.insert (std::make_pair (skeletonBlood, ESM::NPC::Skeleton)); - - npcColumns.mFlags.insert (std::make_pair (metalBlood, ESM::NPC::Metal)); + // Re-used from Creature records. + npcColumns.mBloodType = bloodType; // Need a way to add a table of stats and values (rather than adding a long list of // entries in the dialogue subview) E.g. attributes+stats(health, mana, fatigue), skills @@ -656,7 +656,7 @@ CSMWorld::RefIdCollection::RefIdCollection() mAdapters.insert (std::make_pair (UniversalId::Type_Armor, new ArmorRefIdAdapter (enchantableColumns, armorType, health, armor, partRef))); mAdapters.insert (std::make_pair (UniversalId::Type_Book, - new BookRefIdAdapter (enchantableColumns, scroll, attribute))); + new BookRefIdAdapter (enchantableColumns, bookType, skill, text))); mAdapters.insert (std::make_pair (UniversalId::Type_Clothing, new ClothingRefIdAdapter (enchantableColumns, clothingType, partRef))); mAdapters.insert (std::make_pair (UniversalId::Type_Container, diff --git a/apps/opencs/model/world/refiddata.hpp b/apps/opencs/model/world/refiddata.hpp index 59cad6a66..94f641edc 100644 --- a/apps/opencs/model/world/refiddata.hpp +++ b/apps/opencs/model/world/refiddata.hpp @@ -175,6 +175,12 @@ namespace CSMWorld { mContainer[index].setModified(record); } + else + { + // Overwrite + mContainer[index].setModified(record); + mContainer[index].merge(); + } } return index; diff --git a/apps/opencs/model/world/resources.cpp b/apps/opencs/model/world/resources.cpp index b31e211ee..5fe9194d7 100644 --- a/apps/opencs/model/world/resources.cpp +++ b/apps/opencs/model/world/resources.cpp @@ -25,12 +25,12 @@ CSMWorld::Resources::Resources (const VFS::Manager* vfs, const std::string& base if (extensions) { - std::string::size_type index = filepath.find_last_of ('.'); + std::string::size_type extensionIndex = filepath.find_last_of ('.'); - if (index==std::string::npos) + if (extensionIndex==std::string::npos) continue; - std::string extension = filepath.substr (index+1); + std::string extension = filepath.substr (extensionIndex+1); int i = 0; diff --git a/apps/opencs/model/world/tablemimedata.hpp b/apps/opencs/model/world/tablemimedata.hpp index a42e97561..3be8054bd 100644 --- a/apps/opencs/model/world/tablemimedata.hpp +++ b/apps/opencs/model/world/tablemimedata.hpp @@ -21,7 +21,7 @@ namespace CSMWorld /// /// This class provides way to construct mimedata object holding the universalid copy /// Universalid is used in the majority of the tables to store type, id, argument types. -/// This way universalid grants a way to retrive record from the concrete table. +/// This way universalid grants a way to retrieve record from the concrete table. /// Please note, that tablemimedata object can hold multiple universalIds in the vector. class TableMimeData : public QMimeData diff --git a/apps/opencs/view/doc/subview.cpp b/apps/opencs/view/doc/subview.cpp index 67a8f8c70..82cbe835e 100644 --- a/apps/opencs/view/doc/subview.cpp +++ b/apps/opencs/view/doc/subview.cpp @@ -47,7 +47,7 @@ void CSVDoc::SubView::setUniversalId (const CSMWorld::UniversalId& id) void CSVDoc::SubView::closeEvent (QCloseEvent *event) { - emit updateSubViewIndicies (this); + emit updateSubViewIndices (this); } std::string CSVDoc::SubView::getTitle() const diff --git a/apps/opencs/view/doc/subview.hpp b/apps/opencs/view/doc/subview.hpp index 1c5f8a786..8402bf79a 100644 --- a/apps/opencs/view/doc/subview.hpp +++ b/apps/opencs/view/doc/subview.hpp @@ -64,7 +64,7 @@ namespace CSVDoc void updateTitle(); - void updateSubViewIndicies (SubView *view = 0); + void updateSubViewIndices (SubView *view = NULL); void universalIdChanged (const CSMWorld::UniversalId& universalId); diff --git a/apps/opencs/view/doc/view.cpp b/apps/opencs/view/doc/view.cpp index 3491c9de2..ff49a90fd 100644 --- a/apps/opencs/view/doc/view.cpp +++ b/apps/opencs/view/doc/view.cpp @@ -16,6 +16,7 @@ #include "../../model/doc/document.hpp" #include "../../model/prefs/state.hpp" +#include "../../model/prefs/shortcut.hpp" #include "../../model/world/idtable.hpp" @@ -44,83 +45,98 @@ void CSVDoc::View::closeEvent (QCloseEvent *event) void CSVDoc::View::setupFileMenu() { - QMenu *file = menuBar()->addMenu (tr ("&File")); + QMenu *file = menuBar()->addMenu (tr ("File")); QAction *newGame = new QAction (tr ("New Game"), this); connect (newGame, SIGNAL (triggered()), this, SIGNAL (newGameRequest())); + setupShortcut("document-file-newgame", newGame); file->addAction (newGame); + QAction *newAddon = new QAction (tr ("New Addon"), this); connect (newAddon, SIGNAL (triggered()), this, SIGNAL (newAddonRequest())); + setupShortcut("document-file-newaddon", newAddon); file->addAction (newAddon); - QAction *open = new QAction (tr ("&Open"), this); + QAction *open = new QAction (tr ("Open"), this); connect (open, SIGNAL (triggered()), this, SIGNAL (loadDocumentRequest())); + setupShortcut("document-file-open", open); file->addAction (open); - mSave = new QAction (tr ("&Save"), this); + mSave = new QAction (tr ("Save"), this); connect (mSave, SIGNAL (triggered()), this, SLOT (save())); + setupShortcut("document-file-save", mSave); file->addAction (mSave); - mVerify = new QAction (tr ("&Verify"), this); + mVerify = new QAction (tr ("Verify"), this); connect (mVerify, SIGNAL (triggered()), this, SLOT (verify())); + setupShortcut("document-file-verify", mVerify); file->addAction (mVerify); mMerge = new QAction (tr ("Merge"), this); connect (mMerge, SIGNAL (triggered()), this, SLOT (merge())); + setupShortcut("document-file-merge", mMerge); file->addAction (mMerge); - QAction *loadErrors = new QAction (tr ("Load Error Log"), this); + QAction *loadErrors = new QAction (tr ("Open Load Error Log"), this); connect (loadErrors, SIGNAL (triggered()), this, SLOT (loadErrorLog())); + setupShortcut("document-file-errorlog", loadErrors); file->addAction (loadErrors); QAction *meta = new QAction (tr ("Meta Data"), this); connect (meta, SIGNAL (triggered()), this, SLOT (addMetaDataSubView())); + setupShortcut("document-file-metadata", meta); file->addAction (meta); - QAction *close = new QAction (tr ("&Close"), this); + QAction *close = new QAction (tr ("Close Document"), this); connect (close, SIGNAL (triggered()), this, SLOT (close())); + setupShortcut("document-file-close", close); file->addAction(close); - QAction *exit = new QAction (tr ("&Exit"), this); + QAction *exit = new QAction (tr ("Exit Application"), this); connect (exit, SIGNAL (triggered()), this, SLOT (exit())); connect (this, SIGNAL(exitApplicationRequest(CSVDoc::View *)), &mViewManager, SLOT(exitApplication(CSVDoc::View *))); + setupShortcut("document-file-exit", exit); file->addAction(exit); } void CSVDoc::View::setupEditMenu() { - QMenu *edit = menuBar()->addMenu (tr ("&Edit")); + QMenu *edit = menuBar()->addMenu (tr ("Edit")); - mUndo = mDocument->getUndoStack().createUndoAction (this, tr("&Undo")); - mUndo->setShortcuts (QKeySequence::Undo); + mUndo = mDocument->getUndoStack().createUndoAction (this, tr("Undo")); + setupShortcut("document-edit-undo", mUndo); edit->addAction (mUndo); - mRedo= mDocument->getUndoStack().createRedoAction (this, tr("&Redo")); - mRedo->setShortcuts (QKeySequence::Redo); + mRedo= mDocument->getUndoStack().createRedoAction (this, tr("Redo")); + setupShortcut("document-edit-redo", mRedo); edit->addAction (mRedo); - QAction *userSettings = new QAction (tr ("&Preferences"), this); + QAction *userSettings = new QAction (tr ("Preferences"), this); connect (userSettings, SIGNAL (triggered()), this, SIGNAL (editSettingsRequest())); + setupShortcut("document-edit-preferences", userSettings); edit->addAction (userSettings); QAction *search = new QAction (tr ("Search"), this); connect (search, SIGNAL (triggered()), this, SLOT (addSearchSubView())); + setupShortcut("document-edit-search", search); edit->addAction (search); } void CSVDoc::View::setupViewMenu() { - QMenu *view = menuBar()->addMenu (tr ("&View")); + QMenu *view = menuBar()->addMenu (tr ("View")); - QAction *newWindow = new QAction (tr ("&New View"), this); + QAction *newWindow = new QAction (tr ("New View"), this); connect (newWindow, SIGNAL (triggered()), this, SLOT (newView())); + setupShortcut("document-view-newview", newWindow); view->addAction (newWindow); - mShowStatusBar = new QAction (tr ("Show Status Bar"), this); + mShowStatusBar = new QAction (tr ("Toggle Status Bar"), this); mShowStatusBar->setCheckable (true); connect (mShowStatusBar, SIGNAL (toggled (bool)), this, SLOT (toggleShowStatusBar (bool))); + setupShortcut("document-view-statusbar", mShowStatusBar); mShowStatusBar->setChecked (CSMPrefs::get()["Windows"]["show-statusbar"].isTrue()); @@ -128,70 +144,84 @@ void CSVDoc::View::setupViewMenu() QAction *filters = new QAction (tr ("Filters"), this); connect (filters, SIGNAL (triggered()), this, SLOT (addFiltersSubView())); + setupShortcut("document-view-filters", filters); view->addAction (filters); } void CSVDoc::View::setupWorldMenu() { - QMenu *world = menuBar()->addMenu (tr ("&World")); + QMenu *world = menuBar()->addMenu (tr ("World")); QAction *regions = new QAction (tr ("Regions"), this); connect (regions, SIGNAL (triggered()), this, SLOT (addRegionsSubView())); + setupShortcut("document-world-regions", regions); world->addAction (regions); QAction *cells = new QAction (tr ("Cells"), this); connect (cells, SIGNAL (triggered()), this, SLOT (addCellsSubView())); + setupShortcut("document-world-cells", cells); world->addAction (cells); QAction *referenceables = new QAction (tr ("Objects"), this); connect (referenceables, SIGNAL (triggered()), this, SLOT (addReferenceablesSubView())); + setupShortcut("document-world-referencables", referenceables); world->addAction (referenceables); QAction *references = new QAction (tr ("Instances"), this); connect (references, SIGNAL (triggered()), this, SLOT (addReferencesSubView())); + setupShortcut("document-world-references", references); world->addAction (references); QAction *grid = new QAction (tr ("Pathgrid"), this); connect (grid, SIGNAL (triggered()), this, SLOT (addPathgridSubView())); + setupShortcut("document-world-pathgrid", grid); world->addAction (grid); world->addSeparator(); // items that don't represent single record lists follow here QAction *regionMap = new QAction (tr ("Region Map"), this); connect (regionMap, SIGNAL (triggered()), this, SLOT (addRegionMapSubView())); + setupShortcut("document-world-regionmap", regionMap); world->addAction (regionMap); } void CSVDoc::View::setupMechanicsMenu() { - QMenu *mechanics = menuBar()->addMenu (tr ("&Mechanics")); + QMenu *mechanics = menuBar()->addMenu (tr ("Mechanics")); QAction *globals = new QAction (tr ("Globals"), this); connect (globals, SIGNAL (triggered()), this, SLOT (addGlobalsSubView())); + setupShortcut("document-mechanics-globals", globals); mechanics->addAction (globals); - QAction *gmsts = new QAction (tr ("Game settings"), this); + QAction *gmsts = new QAction (tr ("Game Settings"), this); connect (gmsts, SIGNAL (triggered()), this, SLOT (addGmstsSubView())); + setupShortcut("document-mechanics-gamesettings", gmsts); mechanics->addAction (gmsts); QAction *scripts = new QAction (tr ("Scripts"), this); connect (scripts, SIGNAL (triggered()), this, SLOT (addScriptsSubView())); + setupShortcut("document-mechanics-scripts", scripts); mechanics->addAction (scripts); QAction *spells = new QAction (tr ("Spells"), this); connect (spells, SIGNAL (triggered()), this, SLOT (addSpellsSubView())); + setupShortcut("document-mechanics-spells", spells); mechanics->addAction (spells); QAction *enchantments = new QAction (tr ("Enchantments"), this); connect (enchantments, SIGNAL (triggered()), this, SLOT (addEnchantmentsSubView())); + setupShortcut("document-mechanics-enchantments", enchantments); mechanics->addAction (enchantments); QAction *effects = new QAction (tr ("Magic Effects"), this); connect (effects, SIGNAL (triggered()), this, SLOT (addMagicEffectsSubView())); + setupShortcut("document-mechanics-magiceffects", effects); mechanics->addAction (effects); QAction *startScripts = new QAction (tr ("Start Scripts"), this); connect (startScripts, SIGNAL (triggered()), this, SLOT (addStartScriptsSubView())); + setupShortcut("document-mechanics-startscripts", startScripts); mechanics->addAction (startScripts); } @@ -201,81 +231,99 @@ void CSVDoc::View::setupCharacterMenu() QAction *skills = new QAction (tr ("Skills"), this); connect (skills, SIGNAL (triggered()), this, SLOT (addSkillsSubView())); + setupShortcut("document-character-skills", skills); characters->addAction (skills); QAction *classes = new QAction (tr ("Classes"), this); connect (classes, SIGNAL (triggered()), this, SLOT (addClassesSubView())); + setupShortcut("document-character-classes", classes); characters->addAction (classes); QAction *factions = new QAction (tr ("Factions"), this); connect (factions, SIGNAL (triggered()), this, SLOT (addFactionsSubView())); + setupShortcut("document-character-factions", factions); characters->addAction (factions); QAction *races = new QAction (tr ("Races"), this); connect (races, SIGNAL (triggered()), this, SLOT (addRacesSubView())); + setupShortcut("document-character-races", races); characters->addAction (races); QAction *birthsigns = new QAction (tr ("Birthsigns"), this); connect (birthsigns, SIGNAL (triggered()), this, SLOT (addBirthsignsSubView())); + setupShortcut("document-character-birthsigns", birthsigns); characters->addAction (birthsigns); QAction *topics = new QAction (tr ("Topics"), this); connect (topics, SIGNAL (triggered()), this, SLOT (addTopicsSubView())); + setupShortcut("document-character-topics", topics); characters->addAction (topics); QAction *journals = new QAction (tr ("Journals"), this); connect (journals, SIGNAL (triggered()), this, SLOT (addJournalsSubView())); + setupShortcut("document-character-journals", journals); characters->addAction (journals); QAction *topicInfos = new QAction (tr ("Topic Infos"), this); connect (topicInfos, SIGNAL (triggered()), this, SLOT (addTopicInfosSubView())); + setupShortcut("document-character-topicinfos", topicInfos); characters->addAction (topicInfos); QAction *journalInfos = new QAction (tr ("Journal Infos"), this); connect (journalInfos, SIGNAL (triggered()), this, SLOT (addJournalInfosSubView())); + setupShortcut("document-character-journalinfos", journalInfos); characters->addAction (journalInfos); QAction *bodyParts = new QAction (tr ("Body Parts"), this); connect (bodyParts, SIGNAL (triggered()), this, SLOT (addBodyPartsSubView())); + setupShortcut("document-character-bodyparts", bodyParts); characters->addAction (bodyParts); } void CSVDoc::View::setupAssetsMenu() { - QMenu *assets = menuBar()->addMenu (tr ("&Assets")); + QMenu *assets = menuBar()->addMenu (tr ("Assets")); QAction *sounds = new QAction (tr ("Sounds"), this); connect (sounds, SIGNAL (triggered()), this, SLOT (addSoundsSubView())); + setupShortcut("document-assets-sounds", sounds); assets->addAction (sounds); QAction *soundGens = new QAction (tr ("Sound Generators"), this); connect (soundGens, SIGNAL (triggered()), this, SLOT (addSoundGensSubView())); + setupShortcut("document-assets-soundgens", soundGens); assets->addAction (soundGens); assets->addSeparator(); // resources follow here QAction *meshes = new QAction (tr ("Meshes"), this); connect (meshes, SIGNAL (triggered()), this, SLOT (addMeshesSubView())); + setupShortcut("document-assets-meshes", meshes); assets->addAction (meshes); QAction *icons = new QAction (tr ("Icons"), this); connect (icons, SIGNAL (triggered()), this, SLOT (addIconsSubView())); + setupShortcut("document-assets-icons", icons); assets->addAction (icons); QAction *musics = new QAction (tr ("Music"), this); connect (musics, SIGNAL (triggered()), this, SLOT (addMusicsSubView())); + setupShortcut("document-assets-music", musics); assets->addAction (musics); QAction *soundsRes = new QAction (tr ("Sound Files"), this); connect (soundsRes, SIGNAL (triggered()), this, SLOT (addSoundsResSubView())); + setupShortcut("document-assets-soundres", soundsRes); assets->addAction (soundsRes); QAction *textures = new QAction (tr ("Textures"), this); connect (textures, SIGNAL (triggered()), this, SLOT (addTexturesSubView())); + setupShortcut("document-assets-textures", textures); assets->addAction (textures); QAction *videos = new QAction (tr ("Videos"), this); connect (videos, SIGNAL (triggered()), this, SLOT (addVideosSubView())); + setupShortcut("document-assets-videos", videos); assets->addAction (videos); } @@ -299,12 +347,16 @@ void CSVDoc::View::setupDebugMenu() QAction *runDebug = debug->addMenu (mGlobalDebugProfileMenu); runDebug->setText (tr ("Run OpenMW")); + setupShortcut("document-debug-run", runDebug); + mStopDebug = new QAction (tr ("Shutdown OpenMW"), this); connect (mStopDebug, SIGNAL (triggered()), this, SLOT (stop())); + setupShortcut("document-debug-shutdown", mStopDebug); debug->addAction (mStopDebug); - QAction *runLog = new QAction (tr ("Run Log"), this); + QAction *runLog = new QAction (tr ("Open Run Log"), this); connect (runLog, SIGNAL (triggered()), this, SLOT (addRunLogSubView())); + setupShortcut("document-debug-runlog", runLog); debug->addAction (runLog); } @@ -320,6 +372,12 @@ void CSVDoc::View::setupUi() setupDebugMenu(); } +void CSVDoc::View::setupShortcut(const char* name, QAction* action) +{ + CSMPrefs::Shortcut* shortcut = new CSMPrefs::Shortcut(name, this); + shortcut->associateAction(action); +} + void CSVDoc::View::updateTitle() { std::ostringstream stream; @@ -343,7 +401,7 @@ void CSVDoc::View::updateTitle() setWindowTitle (QString::fromUtf8(stream.str().c_str())); } -void CSVDoc::View::updateSubViewIndicies(SubView *view) +void CSVDoc::View::updateSubViewIndices(SubView *view) { CSMPrefs::Category& windows = CSMPrefs::State::get()["Windows"]; @@ -373,7 +431,7 @@ void CSVDoc::View::updateSubViewIndicies(SubView *view) else { delete subView->titleBarWidget(); - subView->setTitleBarWidget (0); + subView->setTitleBarWidget (NULL); } } } @@ -402,7 +460,7 @@ void CSVDoc::View::updateActions() CSVDoc::View::View (ViewManager& viewManager, CSMDoc::Document *document, int totalViews) : mViewManager (viewManager), mDocument (document), mViewIndex (totalViews-1), - mViewTotal (totalViews), mScroll(0), mScrollbarOnly(false) + mViewTotal (totalViews), mScroll(NULL), mScrollbarOnly(false) { CSMPrefs::Category& windows = CSMPrefs::State::get()["Windows"]; @@ -419,10 +477,7 @@ CSVDoc::View::View (ViewManager& viewManager, CSMDoc::Document *document, int to } else { - mScroll = new QScrollArea(this); - mScroll->setWidgetResizable(true); - mScroll->setWidget(&mSubViewWindow); - setCentralWidget(mScroll); + createScrollArea(); } mOperations = new Operations; @@ -570,36 +625,11 @@ void CSVDoc::View::addSubView (const CSMWorld::UniversalId& id, const std::strin // mScrollbarOnly = windows["mainwindow-scrollbar"].toString() == "Scrollbar Only"; - QDesktopWidget *dw = QApplication::desktop(); - QRect rect; - if (windows["grow-limit"].isTrue()) - rect = dw->screenGeometry(this); - else - rect = dw->screenGeometry(dw->screen(dw->screenNumber(this))); - - if (!mScrollbarOnly && mScroll && mSubViews.size() > 1) - { - int newWidth = width()+minWidth; - int frameWidth = frameGeometry().width() - width(); - if (newWidth+frameWidth <= rect.width()) - { - resize(newWidth, height()); - // WARNING: below code assumes that new subviews are added to the right - if (x() > rect.width()-(newWidth+frameWidth)) - move(rect.width()-(newWidth+frameWidth), y()); // shift left to stay within the screen - } - else - { - // full width - resize(rect.width()-frameWidth, height()); - mSubViewWindow.setMinimumWidth(mSubViewWindow.width()+minWidth); - move(0, y()); - } - } + updateWidth(windows["grow-limit"].isTrue(), minWidth); mSubViewWindow.addDockWidget (Qt::TopDockWidgetArea, view); - updateSubViewIndicies(); + updateSubViewIndices(); connect (view, SIGNAL (focusId (const CSMWorld::UniversalId&, const std::string&)), this, SLOT (addSubView (const CSMWorld::UniversalId&, const std::string&))); @@ -608,8 +638,8 @@ void CSVDoc::View::addSubView (const CSMWorld::UniversalId& id, const std::strin connect (view, SIGNAL (updateTitle()), this, SLOT (updateTitle())); - connect (view, SIGNAL (updateSubViewIndicies (SubView *)), - this, SLOT (updateSubViewIndicies (SubView *))); + connect (view, SIGNAL (updateSubViewIndices (SubView *)), + this, SLOT (updateSubViewIndices (SubView *))); view->show(); @@ -631,7 +661,7 @@ void CSVDoc::View::moveScrollBarToEnd(int min, int max) void CSVDoc::View::settingChanged (const CSMPrefs::Setting *setting) { if (*setting=="Windows/hide-subview") - updateSubViewIndicies (0); + updateSubViewIndices (NULL); else if (*setting=="Windows/mainwindow-scrollbar") { if (setting->toString()!="Grow Only") @@ -651,10 +681,7 @@ void CSVDoc::View::settingChanged (const CSMPrefs::Setting *setting) } else { - mScroll = new QScrollArea(this); - mScroll->setWidgetResizable(true); - mScroll->setWidget(&mSubViewWindow); - setCentralWidget(mScroll); + createScrollArea(); } } else if (mScroll) @@ -662,7 +689,7 @@ void CSVDoc::View::settingChanged (const CSMPrefs::Setting *setting) mScroll->takeWidget(); setCentralWidget (&mSubViewWindow); mScroll->deleteLater(); - mScroll = 0; + mScroll = NULL; } } } @@ -959,3 +986,41 @@ void CSVDoc::View::merge() { emit mergeDocument (mDocument); } + +void CSVDoc::View::updateWidth(bool isGrowLimit, int minSubViewWidth) +{ + QDesktopWidget *dw = QApplication::desktop(); + QRect rect; + if (isGrowLimit) + rect = dw->screenGeometry(this); + else + rect = dw->screenGeometry(dw->screen(dw->screenNumber(this))); + + if (!mScrollbarOnly && mScroll && mSubViews.size() > 1) + { + int newWidth = width()+minSubViewWidth; + int frameWidth = frameGeometry().width() - width(); + if (newWidth+frameWidth <= rect.width()) + { + resize(newWidth, height()); + // WARNING: below code assumes that new subviews are added to the right + if (x() > rect.width()-(newWidth+frameWidth)) + move(rect.width()-(newWidth+frameWidth), y()); // shift left to stay within the screen + } + else + { + // full width + resize(rect.width()-frameWidth, height()); + mSubViewWindow.setMinimumWidth(mSubViewWindow.width()+minSubViewWidth); + move(0, y()); + } + } +} + +void CSVDoc::View::createScrollArea() +{ + mScroll = new QScrollArea(this); + mScroll->setWidgetResizable(true); + mScroll->setWidget(&mSubViewWindow); + setCentralWidget(mScroll); +} diff --git a/apps/opencs/view/doc/view.hpp b/apps/opencs/view/doc/view.hpp index 7d5304269..834407be0 100644 --- a/apps/opencs/view/doc/view.hpp +++ b/apps/opencs/view/doc/view.hpp @@ -84,6 +84,8 @@ namespace CSVDoc void setupUi(); + void setupShortcut(const char* name, QAction* action); + void updateActions(); void exitApplication(); @@ -95,7 +97,8 @@ namespace CSVDoc void resizeViewHeight (int height); void updateScrollbar(); - + void updateWidth(bool isGrowLimit, int minSubViewWidth); + void createScrollArea(); public: View (ViewManager& viewManager, CSMDoc::Document *document, int totalViews); @@ -143,7 +146,7 @@ namespace CSVDoc void updateTitle(); // called when subviews are added or removed - void updateSubViewIndicies (SubView *view = 0); + void updateSubViewIndices (SubView *view = NULL); private slots: diff --git a/apps/opencs/view/doc/viewmanager.cpp b/apps/opencs/view/doc/viewmanager.cpp index 024636f34..8cca3a849 100644 --- a/apps/opencs/view/doc/viewmanager.cpp +++ b/apps/opencs/view/doc/viewmanager.cpp @@ -107,6 +107,10 @@ CSVDoc::ViewManager::ViewManager (CSMDoc::DocumentManager& documentManager) { CSMWorld::ColumnBase::Display_IngredEffectId, CSMWorld::Columns::ColumnId_EffectId, true }, { CSMWorld::ColumnBase::Display_EffectSkill, CSMWorld::Columns::ColumnId_Skill, false }, { CSMWorld::ColumnBase::Display_EffectAttribute, CSMWorld::Columns::ColumnId_Attribute, false }, + { CSMWorld::ColumnBase::Display_BookType, CSMWorld::Columns::ColumnId_BookType, false }, + { CSMWorld::ColumnBase::Display_BloodType, CSMWorld::Columns::ColumnId_BloodType, false }, + { CSMWorld::ColumnBase::Display_EmitterType, CSMWorld::Columns::ColumnId_EmitterType, false }, + { CSMWorld::ColumnBase::Display_GenderNpc, CSMWorld::Columns::ColumnId_Gender, false } }; for (std::size_t i=0; i data = mime->getData(); + std::vector universalIdData = mime->getData(); - emit recordDropped(data, event->proposedAction()); + emit recordDropped(universalIdData, event->proposedAction()); } void CSVFilter::FilterBox::dragEnterEvent (QDragEnterEvent* event) diff --git a/apps/opencs/view/prefs/dialogue.cpp b/apps/opencs/view/prefs/dialogue.cpp index f04092653..6848bcaba 100644 --- a/apps/opencs/view/prefs/dialogue.cpp +++ b/apps/opencs/view/prefs/dialogue.cpp @@ -11,6 +11,7 @@ #include "../../model/prefs/state.hpp" #include "page.hpp" +#include "keybindingpage.hpp" void CSVPrefs::Dialogue::buildCategorySelector (QSplitter *main) { @@ -52,8 +53,10 @@ void CSVPrefs::Dialogue::buildContentArea (QSplitter *main) CSVPrefs::PageBase *CSVPrefs::Dialogue::makePage (const std::string& key) { // special case page code goes here - - return new Page (CSMPrefs::get()[key], mContent); + if (key == "Key Bindings") + return new KeyBindingPage(CSMPrefs::get()[key], mContent); + else + return new Page (CSMPrefs::get()[key], mContent); } CSVPrefs::Dialogue::Dialogue() diff --git a/apps/opencs/view/prefs/keybindingpage.cpp b/apps/opencs/view/prefs/keybindingpage.cpp new file mode 100644 index 000000000..143665f4a --- /dev/null +++ b/apps/opencs/view/prefs/keybindingpage.cpp @@ -0,0 +1,88 @@ +#include "keybindingpage.hpp" + +#include + +#include +#include +#include +#include + +#include "../../model/prefs/setting.hpp" +#include "../../model/prefs/category.hpp" + +namespace CSVPrefs +{ + KeyBindingPage::KeyBindingPage(CSMPrefs::Category& category, QWidget* parent) + : PageBase(category, parent) + , mStackedLayout(0) + , mPageLayout(0) + , mPageSelector(0) + { + // Need one widget for scroll area + QWidget* topWidget = new QWidget(); + QVBoxLayout* topLayout = new QVBoxLayout(topWidget); + + // Allows switching between "pages" + QWidget* stackedWidget = new QWidget(); + mStackedLayout = new QStackedLayout(stackedWidget); + + mPageSelector = new QComboBox(); + connect(mPageSelector, SIGNAL(currentIndexChanged(int)), mStackedLayout, SLOT(setCurrentIndex(int))); + + topLayout->addWidget(mPageSelector); + topLayout->addWidget(stackedWidget); + topLayout->setSizeConstraint(QLayout::SetMinAndMaxSize); + + // Add each option + for (CSMPrefs::Category::Iterator iter = category.begin(); iter!=category.end(); ++iter) + addSetting (*iter); + + setWidgetResizable(true); + setWidget(topWidget); + } + + void KeyBindingPage::addSetting(CSMPrefs::Setting *setting) + { + std::pair widgets = setting->makeWidgets (this); + + if (widgets.first) + { + // Label, Option widgets + assert(mPageLayout); + + int next = mPageLayout->rowCount(); + mPageLayout->addWidget(widgets.first, next, 0); + mPageLayout->addWidget(widgets.second, next, 1); + } + else if (widgets.second) + { + // Wide single widget + assert(mPageLayout); + + int next = mPageLayout->rowCount(); + mPageLayout->addWidget(widgets.second, next, 0, 1, 2); + } + else + { + if (setting->getLabel().empty()) + { + // Insert empty space + assert(mPageLayout); + + int next = mPageLayout->rowCount(); + mPageLayout->addWidget(new QWidget(), next, 0); + } + else + { + // Create new page + QWidget* pageWidget = new QWidget(); + mPageLayout = new QGridLayout(pageWidget); + mPageLayout->setSizeConstraint(QLayout::SetMinAndMaxSize); + + mStackedLayout->addWidget(pageWidget); + + mPageSelector->addItem(QString::fromUtf8(setting->getLabel().c_str())); + } + } + } +} diff --git a/apps/opencs/view/prefs/keybindingpage.hpp b/apps/opencs/view/prefs/keybindingpage.hpp new file mode 100644 index 000000000..8a0cb2952 --- /dev/null +++ b/apps/opencs/view/prefs/keybindingpage.hpp @@ -0,0 +1,35 @@ +#ifndef CSV_PREFS_KEYBINDINGPAGE_H +#define CSV_PREFS_KEYBINDINGPAGE_H + +#include "pagebase.hpp" + +class QComboBox; +class QGridLayout; +class QStackedLayout; + +namespace CSMPrefs +{ + class Setting; +} + +namespace CSVPrefs +{ + class KeyBindingPage : public PageBase + { + Q_OBJECT + + public: + + KeyBindingPage(CSMPrefs::Category& category, QWidget* parent); + + void addSetting(CSMPrefs::Setting* setting); + + private: + + QStackedLayout* mStackedLayout; + QGridLayout* mPageLayout; + QComboBox* mPageSelector; + }; +} + +#endif diff --git a/apps/opencs/view/render/cameracontroller.cpp b/apps/opencs/view/render/cameracontroller.cpp new file mode 100644 index 000000000..7e3570657 --- /dev/null +++ b/apps/opencs/view/render/cameracontroller.cpp @@ -0,0 +1,776 @@ +#include "cameracontroller.hpp" + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "../../model/prefs/shortcut.hpp" + +#include "scenewidget.hpp" + +namespace CSVRender +{ + + /* + Camera Controller + */ + + const osg::Vec3d CameraController::WorldUp = osg::Vec3d(0, 0, 1); + + const osg::Vec3d CameraController::LocalUp = osg::Vec3d(0, 1, 0); + const osg::Vec3d CameraController::LocalLeft = osg::Vec3d(1, 0, 0); + const osg::Vec3d CameraController::LocalForward = osg::Vec3d(0, 0, 1); + + CameraController::CameraController(QObject* parent) + : QObject(parent) + , mActive(false) + , mInverted(false) + , mCameraSensitivity(1/650.f) + , mSecondaryMoveMult(50) + , mWheelMoveMult(8) + , mCamera(NULL) + { + } + + CameraController::~CameraController() + { + } + + bool CameraController::isActive() const + { + return mActive; + } + + osg::Camera* CameraController::getCamera() const + { + return mCamera; + } + + double CameraController::getCameraSensitivity() const + { + return mCameraSensitivity; + } + + bool CameraController::getInverted() const + { + return mInverted; + } + + double CameraController::getSecondaryMovementMultiplier() const + { + return mSecondaryMoveMult; + } + + double CameraController::getWheelMovementMultiplier() const + { + return mWheelMoveMult; + } + + void CameraController::setCamera(osg::Camera* camera) + { + bool wasActive = mActive; + + mCamera = camera; + mActive = (mCamera != NULL); + + if (mActive != wasActive) + { + for (std::vector::iterator it = mShortcuts.begin(); it != mShortcuts.end(); ++it) + { + CSMPrefs::Shortcut* shortcut = *it; + shortcut->enable(mActive); + } + } + } + + void CameraController::setCameraSensitivity(double value) + { + mCameraSensitivity = value; + } + + void CameraController::setInverted(bool value) + { + mInverted = value; + } + + void CameraController::setSecondaryMovementMultiplier(double value) + { + mSecondaryMoveMult = value; + } + + void CameraController::setWheelMovementMultiplier(double value) + { + mWheelMoveMult = value; + } + + void CameraController::setup(osg::Group* root, unsigned int mask, const osg::Vec3d& up) + { + // Find World bounds + osg::ComputeBoundsVisitor boundsVisitor; + osg::BoundingBox& boundingBox = boundsVisitor.getBoundingBox(); + + boundsVisitor.setTraversalMask(mask); + root->accept(boundsVisitor); + + if (!boundingBox.valid()) + { + // Try again without any mask + boundsVisitor.reset(); + boundsVisitor.setTraversalMask(~0); + root->accept(boundsVisitor); + + // Last resort, set a default + if (!boundingBox.valid()) + { + boundingBox.set(-1, -1, -1, 1, 1, 1); + } + } + + // Calculate a good starting position + osg::Vec3d minBounds = boundingBox.corner(0) - boundingBox.center(); + osg::Vec3d maxBounds = boundingBox.corner(7) - boundingBox.center(); + + osg::Vec3d camOffset = up * maxBounds > 0 ? maxBounds : minBounds; + camOffset *= 2; + + osg::Vec3d eye = camOffset + boundingBox.center(); + osg::Vec3d center = boundingBox.center(); + + getCamera()->setViewMatrixAsLookAt(eye, center, up); + } + + void CameraController::addShortcut(CSMPrefs::Shortcut* shortcut) + { + mShortcuts.push_back(shortcut); + } + + /* + Free Camera Controller + */ + + FreeCameraController::FreeCameraController(QWidget* widget) + : CameraController(widget) + , mLockUpright(false) + , mModified(false) + , mNaviPrimary(false) + , mNaviSecondary(false) + , mFast(false) + , mFastAlternate(false) + , mLeft(false) + , mRight(false) + , mForward(false) + , mBackward(false) + , mRollLeft(false) + , mRollRight(false) + , mUp(LocalUp) + , mLinSpeed(1000) + , mRotSpeed(osg::PI / 2) + , mSpeedMult(8) + { + CSMPrefs::Shortcut* naviPrimaryShortcut = new CSMPrefs::Shortcut("scene-navi-primary", widget); + naviPrimaryShortcut->enable(false); + connect(naviPrimaryShortcut, SIGNAL(activated(bool)), this, SLOT(naviPrimary(bool))); + + addShortcut(naviPrimaryShortcut); + + CSMPrefs::Shortcut* naviSecondaryShortcut = new CSMPrefs::Shortcut("scene-navi-secondary", widget); + naviSecondaryShortcut->enable(false); + connect(naviSecondaryShortcut, SIGNAL(activated(bool)), this, SLOT(naviSecondary(bool))); + + addShortcut(naviSecondaryShortcut); + + CSMPrefs::Shortcut* forwardShortcut = new CSMPrefs::Shortcut("free-forward", "scene-speed-modifier", + CSMPrefs::Shortcut::SM_Detach, widget); + forwardShortcut->enable(false); + connect(forwardShortcut, SIGNAL(activated(bool)), this, SLOT(forward(bool))); + connect(forwardShortcut, SIGNAL(secondary(bool)), this, SLOT(alternateFast(bool))); + + addShortcut(forwardShortcut); + + CSMPrefs::Shortcut* leftShortcut = new CSMPrefs::Shortcut("free-left", widget); + leftShortcut->enable(false); + connect(leftShortcut, SIGNAL(activated(bool)), this, SLOT(left(bool))); + + addShortcut(leftShortcut); + + CSMPrefs::Shortcut* backShortcut = new CSMPrefs::Shortcut("free-backward", widget); + backShortcut->enable(false); + connect(backShortcut, SIGNAL(activated(bool)), this, SLOT(backward(bool))); + + addShortcut(backShortcut); + + CSMPrefs::Shortcut* rightShortcut = new CSMPrefs::Shortcut("free-right", widget); + rightShortcut->enable(false); + connect(rightShortcut, SIGNAL(activated(bool)), this, SLOT(right(bool))); + + addShortcut(rightShortcut); + + CSMPrefs::Shortcut* rollLeftShortcut = new CSMPrefs::Shortcut("free-roll-left", widget); + rollLeftShortcut->enable(false); + connect(rollLeftShortcut, SIGNAL(activated(bool)), this, SLOT(rollLeft(bool))); + + addShortcut(rollLeftShortcut); + + CSMPrefs::Shortcut* rollRightShortcut = new CSMPrefs::Shortcut("free-roll-right", widget); + rollRightShortcut->enable(false); + connect(rollRightShortcut, SIGNAL(activated(bool)), this, SLOT(rollRight(bool))); + + addShortcut(rollRightShortcut); + + CSMPrefs::Shortcut* speedModeShortcut = new CSMPrefs::Shortcut("free-speed-mode", widget); + speedModeShortcut->enable(false); + connect(speedModeShortcut, SIGNAL(activated()), this, SLOT(swapSpeedMode())); + + addShortcut(speedModeShortcut); + } + + double FreeCameraController::getLinearSpeed() const + { + return mLinSpeed; + } + + double FreeCameraController::getRotationalSpeed() const + { + return mRotSpeed; + } + + double FreeCameraController::getSpeedMultiplier() const + { + return mSpeedMult; + } + + void FreeCameraController::setLinearSpeed(double value) + { + mLinSpeed = value; + } + + void FreeCameraController::setRotationalSpeed(double value) + { + mRotSpeed = value; + } + + void FreeCameraController::setSpeedMultiplier(double value) + { + mSpeedMult = value; + } + + void FreeCameraController::fixUpAxis(const osg::Vec3d& up) + { + mLockUpright = true; + mUp = up; + mModified = true; + } + + void FreeCameraController::unfixUpAxis() + { + mLockUpright = false; + } + + void FreeCameraController::handleMouseMoveEvent(int x, int y) + { + if (!isActive()) + return; + + if (mNaviPrimary) + { + double scalar = getCameraSensitivity() * (getInverted() ? -1.0 : 1.0); + yaw(x * scalar); + pitch(y * scalar); + } + else if (mNaviSecondary) + { + osg::Vec3d movement; + movement += LocalLeft * -x * getSecondaryMovementMultiplier(); + movement += LocalUp * y * getSecondaryMovementMultiplier(); + + translate(movement); + } + } + + void FreeCameraController::handleMouseScrollEvent(int x) + { + if (!isActive()) + return; + + translate(LocalForward * x * ((mFast ^ mFastAlternate) ? getWheelMovementMultiplier() : 1)); + } + + void FreeCameraController::update(double dt) + { + if (!isActive()) + return; + + double linDist = mLinSpeed * dt; + double rotDist = mRotSpeed * dt; + + if (mFast ^ mFastAlternate) + linDist *= mSpeedMult; + + if (mLeft) + translate(LocalLeft * linDist); + if (mRight) + translate(LocalLeft * -linDist); + if (mForward) + translate(LocalForward * linDist); + if (mBackward) + translate(LocalForward * -linDist); + + if (!mLockUpright) + { + if (mRollLeft) + roll(-rotDist); + if (mRollRight) + roll(rotDist); + } + else if(mModified) + { + stabilize(); + mModified = false; + } + + // Normalize the matrix to counter drift + getCamera()->getViewMatrix().orthoNormal(getCamera()->getViewMatrix()); + } + + void FreeCameraController::yaw(double value) + { + getCamera()->getViewMatrix() *= osg::Matrixd::rotate(value, LocalUp); + mModified = true; + } + + void FreeCameraController::pitch(double value) + { + const double Constraint = osg::PI / 2 - 0.1; + + if (mLockUpright) + { + osg::Vec3d eye, center, up; + getCamera()->getViewMatrixAsLookAt(eye, center, up); + + osg::Vec3d forward = center - eye; + osg::Vec3d left = up ^ forward; + + double pitchAngle = std::acos(up * mUp); + if ((mUp ^ up) * left < 0) + pitchAngle *= -1; + + if (std::abs(pitchAngle + value) > Constraint) + value = (pitchAngle > 0 ? 1 : -1) * Constraint - pitchAngle; + } + + getCamera()->getViewMatrix() *= osg::Matrixd::rotate(value, LocalLeft); + mModified = true; + } + + void FreeCameraController::roll(double value) + { + getCamera()->getViewMatrix() *= osg::Matrixd::rotate(value, LocalForward); + mModified = true; + } + + void FreeCameraController::translate(const osg::Vec3d& offset) + { + getCamera()->getViewMatrix() *= osg::Matrixd::translate(offset); + mModified = true; + } + + void FreeCameraController::stabilize() + { + osg::Vec3d eye, center, up; + getCamera()->getViewMatrixAsLookAt(eye, center, up); + getCamera()->setViewMatrixAsLookAt(eye, center, mUp); + } + + void FreeCameraController::naviPrimary(bool active) + { + mNaviPrimary = active; + } + + void FreeCameraController::naviSecondary(bool active) + { + mNaviSecondary = active; + } + + void FreeCameraController::forward(bool active) + { + mForward = active; + } + + void FreeCameraController::left(bool active) + { + mLeft = active; + } + + void FreeCameraController::backward(bool active) + { + mBackward = active; + } + + void FreeCameraController::right(bool active) + { + mRight = active; + } + + void FreeCameraController::rollLeft(bool active) + { + mRollLeft = active; + } + + void FreeCameraController::rollRight(bool active) + { + mRollRight = active; + } + + void FreeCameraController::alternateFast(bool active) + { + mFastAlternate = active; + } + + void FreeCameraController::swapSpeedMode() + { + mFast = !mFast; + } + + /* + Orbit Camera Controller + */ + + OrbitCameraController::OrbitCameraController(QWidget* widget) + : CameraController(widget) + , mInitialized(false) + , mNaviPrimary(false) + , mNaviSecondary(false) + , mFast(false) + , mFastAlternate(false) + , mLeft(false) + , mRight(false) + , mUp(false) + , mDown(false) + , mRollLeft(false) + , mRollRight(false) + , mPickingMask(~0) + , mCenter(0,0,0) + , mDistance(0) + , mOrbitSpeed(osg::PI / 4) + , mOrbitSpeedMult(4) + { + CSMPrefs::Shortcut* naviPrimaryShortcut = new CSMPrefs::Shortcut("scene-navi-primary", widget); + naviPrimaryShortcut->enable(false); + connect(naviPrimaryShortcut, SIGNAL(activated(bool)), this, SLOT(naviPrimary(bool))); + + addShortcut(naviPrimaryShortcut); + + CSMPrefs::Shortcut* naviSecondaryShortcut = new CSMPrefs::Shortcut("scene-navi-secondary", widget); + naviSecondaryShortcut->enable(false); + connect(naviSecondaryShortcut, SIGNAL(activated(bool)), this, SLOT(naviSecondary(bool))); + + addShortcut(naviSecondaryShortcut); + + CSMPrefs::Shortcut* upShortcut = new CSMPrefs::Shortcut("orbit-up", "scene-speed-modifier", + CSMPrefs::Shortcut::SM_Detach, widget); + upShortcut->enable(false); + connect(upShortcut, SIGNAL(activated(bool)), this, SLOT(up(bool))); + connect(upShortcut, SIGNAL(secondary(bool)), this, SLOT(alternateFast(bool))); + + addShortcut(upShortcut); + + CSMPrefs::Shortcut* leftShortcut = new CSMPrefs::Shortcut("orbit-left", widget); + leftShortcut->enable(false); + connect(leftShortcut, SIGNAL(activated(bool)), this, SLOT(left(bool))); + + addShortcut(leftShortcut); + + CSMPrefs::Shortcut* downShortcut = new CSMPrefs::Shortcut("orbit-down", widget); + downShortcut->enable(false); + connect(downShortcut, SIGNAL(activated(bool)), this, SLOT(down(bool))); + + addShortcut(downShortcut); + + CSMPrefs::Shortcut* rightShortcut = new CSMPrefs::Shortcut("orbit-right", widget); + rightShortcut->enable(false); + connect(rightShortcut, SIGNAL(activated(bool)), this, SLOT(right(bool))); + + addShortcut(rightShortcut); + + CSMPrefs::Shortcut* rollLeftShortcut = new CSMPrefs::Shortcut("orbit-roll-left", widget); + rollLeftShortcut->enable(false); + connect(rollLeftShortcut, SIGNAL(activated(bool)), this, SLOT(rollLeft(bool))); + + addShortcut(rollLeftShortcut); + + CSMPrefs::Shortcut* rollRightShortcut = new CSMPrefs::Shortcut("orbit-roll-right", widget); + rollRightShortcut->enable(false); + connect(rollRightShortcut, SIGNAL(activated(bool)), this, SLOT(rollRight(bool))); + + addShortcut(rollRightShortcut); + + CSMPrefs::Shortcut* speedModeShortcut = new CSMPrefs::Shortcut("orbit-speed-mode", widget); + speedModeShortcut->enable(false); + connect(speedModeShortcut, SIGNAL(activated()), this, SLOT(swapSpeedMode())); + + addShortcut(speedModeShortcut); + } + + osg::Vec3d OrbitCameraController::getCenter() const + { + return mCenter; + } + + double OrbitCameraController::getOrbitSpeed() const + { + return mOrbitSpeed; + } + + double OrbitCameraController::getOrbitSpeedMultiplier() const + { + return mOrbitSpeedMult; + } + + unsigned int OrbitCameraController::getPickingMask() const + { + return mPickingMask; + } + + void OrbitCameraController::setCenter(const osg::Vec3d& value) + { + osg::Vec3d eye, center, up; + getCamera()->getViewMatrixAsLookAt(eye, center, up); + + mCenter = value; + mDistance = (eye - mCenter).length(); + + getCamera()->setViewMatrixAsLookAt(eye, mCenter, up); + + mInitialized = true; + } + + void OrbitCameraController::setOrbitSpeed(double value) + { + mOrbitSpeed = value; + } + + void OrbitCameraController::setOrbitSpeedMultiplier(double value) + { + mOrbitSpeedMult = value; + } + + void OrbitCameraController::setPickingMask(unsigned int value) + { + mPickingMask = value; + } + + void OrbitCameraController::handleMouseMoveEvent(int x, int y) + { + if (!isActive()) + return; + + if (!mInitialized) + initialize(); + + if (mNaviPrimary) + { + double scalar = getCameraSensitivity() * (getInverted() ? -1.0 : 1.0); + rotateHorizontal(x * scalar); + rotateVertical(-y * scalar); + } + else if (mNaviSecondary) + { + osg::Vec3d movement; + movement += LocalLeft * x * getSecondaryMovementMultiplier(); + movement += LocalUp * -y * getSecondaryMovementMultiplier(); + + translate(movement); + } + } + + void OrbitCameraController::handleMouseScrollEvent(int x) + { + if (!isActive()) + return; + + zoom(-x * ((mFast ^ mFastAlternate) ? getWheelMovementMultiplier() : 1)); + } + + void OrbitCameraController::update(double dt) + { + if (!isActive()) + return; + + if (!mInitialized) + initialize(); + + double rotDist = mOrbitSpeed * dt; + + if (mFast ^ mFastAlternate) + rotDist *= mOrbitSpeedMult; + + if (mLeft) + rotateHorizontal(-rotDist); + if (mRight) + rotateHorizontal(rotDist); + if (mUp) + rotateVertical(rotDist); + if (mDown) + rotateVertical(-rotDist); + + if (mRollLeft) + roll(-rotDist); + if (mRollRight) + roll(rotDist); + + // Normalize the matrix to counter drift + getCamera()->getViewMatrix().orthoNormal(getCamera()->getViewMatrix()); + } + + void OrbitCameraController::onActivate() + { + mInitialized = false; + } + + void OrbitCameraController::initialize() + { + static const int DefaultStartDistance = 10000.f; + + // Try to intelligently pick focus object + osg::ref_ptr intersector (new osgUtil::LineSegmentIntersector( + osgUtil::Intersector::PROJECTION, osg::Vec3d(0, 0, 0), LocalForward)); + + intersector->setIntersectionLimit(osgUtil::LineSegmentIntersector::LIMIT_NEAREST); + osgUtil::IntersectionVisitor visitor(intersector); + + visitor.setTraversalMask(mPickingMask); + + getCamera()->accept(visitor); + + osg::Vec3d eye, center, up; + getCamera()->getViewMatrixAsLookAt(eye, center, up, DefaultStartDistance); + + if (intersector->getIntersections().begin() != intersector->getIntersections().end()) + { + mCenter = intersector->getIntersections().begin()->getWorldIntersectPoint(); + mDistance = (eye - mCenter).length(); + } + else + { + mCenter = center; + mDistance = DefaultStartDistance; + } + + mInitialized = true; + } + + void OrbitCameraController::rotateHorizontal(double value) + { + osg::Vec3d eye, center, up; + getCamera()->getViewMatrixAsLookAt(eye, center, up); + + osg::Quat rotation = osg::Quat(value, up); + osg::Vec3d oldOffset = eye - mCenter; + osg::Vec3d newOffset = rotation * oldOffset; + + getCamera()->setViewMatrixAsLookAt(mCenter + newOffset, mCenter, up); + } + + void OrbitCameraController::rotateVertical(double value) + { + osg::Vec3d eye, center, up; + getCamera()->getViewMatrixAsLookAt(eye, center, up); + + osg::Vec3d forward = center - eye; + osg::Quat rotation = osg::Quat(value, up ^ forward); + osg::Vec3d oldOffset = eye - mCenter; + osg::Vec3d newOffset = rotation * oldOffset; + + getCamera()->setViewMatrixAsLookAt(mCenter + newOffset, mCenter, up); + } + + void OrbitCameraController::roll(double value) + { + getCamera()->getViewMatrix() *= osg::Matrixd::rotate(value, LocalForward); + } + + void OrbitCameraController::translate(const osg::Vec3d& offset) + { + osg::Vec3d eye, center, up; + getCamera()->getViewMatrixAsLookAt(eye, center, up); + + osg::Vec3d newOffset = getCamera()->getViewMatrix().getRotate().inverse() * offset; + mCenter += newOffset; + eye += newOffset; + + getCamera()->setViewMatrixAsLookAt(eye, mCenter, up); + } + + void OrbitCameraController::zoom(double value) + { + mDistance = std::max(10., mDistance + value); + + osg::Vec3d eye, center, up; + getCamera()->getViewMatrixAsLookAt(eye, center, up, 1.f); + + osg::Vec3d offset = (eye - center) * mDistance; + + getCamera()->setViewMatrixAsLookAt(mCenter + offset, mCenter, up); + } + + void OrbitCameraController::naviPrimary(bool active) + { + mNaviPrimary = active; + } + + void OrbitCameraController::naviSecondary(bool active) + { + mNaviSecondary = active; + } + + void OrbitCameraController::up(bool active) + { + mUp = active; + } + + void OrbitCameraController::left(bool active) + { + mLeft = active; + } + + void OrbitCameraController::down(bool active) + { + mDown = active; + } + + void OrbitCameraController::right(bool active) + { + mRight = active; + } + + void OrbitCameraController::rollLeft(bool active) + { + if (isActive()) + mRollLeft = active; + } + + void OrbitCameraController::rollRight(bool active) + { + mRollRight = active; + } + + void OrbitCameraController::alternateFast(bool active) + { + mFastAlternate = active; + } + + void OrbitCameraController::swapSpeedMode() + { + mFast = !mFast; + } +} diff --git a/apps/opencs/view/render/cameracontroller.hpp b/apps/opencs/view/render/cameracontroller.hpp new file mode 100644 index 000000000..97af85790 --- /dev/null +++ b/apps/opencs/view/render/cameracontroller.hpp @@ -0,0 +1,200 @@ +#ifndef OPENCS_VIEW_CAMERACONTROLLER_H +#define OPENCS_VIEW_CAMERACONTROLLER_H + +#include +#include + +#include + +#include +#include + +namespace osg +{ + class Camera; + class Group; +} + +namespace CSMPrefs +{ + class Shortcut; +} + +namespace CSVRender +{ + class SceneWidget; + + class CameraController : public QObject + { + Q_OBJECT + + public: + + static const osg::Vec3d WorldUp; + + static const osg::Vec3d LocalUp; + static const osg::Vec3d LocalLeft; + static const osg::Vec3d LocalForward; + + CameraController(QObject* parent); + virtual ~CameraController(); + + bool isActive() const; + + osg::Camera* getCamera() const; + double getCameraSensitivity() const; + bool getInverted() const; + double getSecondaryMovementMultiplier() const; + double getWheelMovementMultiplier() const; + + void setCamera(osg::Camera*); + void setCameraSensitivity(double value); + void setInverted(bool value); + void setSecondaryMovementMultiplier(double value); + void setWheelMovementMultiplier(double value); + + // moves the camera to an intelligent position + void setup(osg::Group* root, unsigned int mask, const osg::Vec3d& up); + + virtual void handleMouseMoveEvent(int x, int y) = 0; + virtual void handleMouseScrollEvent(int x) = 0; + + virtual void update(double dt) = 0; + + protected: + + virtual void onActivate(){} + + void addShortcut(CSMPrefs::Shortcut* shortcut); + + private: + + bool mActive, mInverted; + double mCameraSensitivity; + double mSecondaryMoveMult; + double mWheelMoveMult; + + osg::Camera* mCamera; + + std::vector mShortcuts; + }; + + class FreeCameraController : public CameraController + { + Q_OBJECT + + public: + + FreeCameraController(QWidget* parent); + + double getLinearSpeed() const; + double getRotationalSpeed() const; + double getSpeedMultiplier() const; + + void setLinearSpeed(double value); + void setRotationalSpeed(double value); + void setSpeedMultiplier(double value); + + void fixUpAxis(const osg::Vec3d& up); + void unfixUpAxis(); + + void handleMouseMoveEvent(int x, int y); + void handleMouseScrollEvent(int x); + + void update(double dt); + + private: + + void yaw(double value); + void pitch(double value); + void roll(double value); + void translate(const osg::Vec3d& offset); + + void stabilize(); + + bool mLockUpright, mModified; + bool mNaviPrimary, mNaviSecondary; + bool mFast, mFastAlternate; + bool mLeft, mRight, mForward, mBackward, mRollLeft, mRollRight; + osg::Vec3d mUp; + + double mLinSpeed; + double mRotSpeed; + double mSpeedMult; + + private slots: + + void naviPrimary(bool active); + void naviSecondary(bool active); + void forward(bool active); + void left(bool active); + void backward(bool active); + void right(bool active); + void rollLeft(bool active); + void rollRight(bool active); + void alternateFast(bool active); + void swapSpeedMode(); + }; + + class OrbitCameraController : public CameraController + { + Q_OBJECT + + public: + + OrbitCameraController(QWidget* parent); + + osg::Vec3d getCenter() const; + double getOrbitSpeed() const; + double getOrbitSpeedMultiplier() const; + unsigned int getPickingMask() const; + + void setCenter(const osg::Vec3d& center); + void setOrbitSpeed(double value); + void setOrbitSpeedMultiplier(double value); + void setPickingMask(unsigned int value); + + void handleMouseMoveEvent(int x, int y); + void handleMouseScrollEvent(int x); + + void update(double dt); + + private: + + void onActivate(); + + void initialize(); + + void rotateHorizontal(double value); + void rotateVertical(double value); + void roll(double value); + void translate(const osg::Vec3d& offset); + void zoom(double value); + + bool mInitialized; + bool mNaviPrimary, mNaviSecondary; + bool mFast, mFastAlternate; + bool mLeft, mRight, mUp, mDown, mRollLeft, mRollRight; + unsigned int mPickingMask; + osg::Vec3d mCenter; + double mDistance; + + double mOrbitSpeed; + double mOrbitSpeedMult; + + private slots: + + void naviPrimary(bool active); + void naviSecondary(bool active); + void up(bool active); + void left(bool active); + void down(bool active); + void right(bool active); + void rollLeft(bool active); + void rollRight(bool active); + void alternateFast(bool active); + void swapSpeedMode(); + }; +} + +#endif diff --git a/apps/opencs/view/render/cell.cpp b/apps/opencs/view/render/cell.cpp index bd85c8a14..adc038836 100644 --- a/apps/opencs/view/render/cell.cpp +++ b/apps/opencs/view/render/cell.cpp @@ -1,9 +1,14 @@ #include "cell.hpp" +#include +#include +#include #include #include +#include #include +#include #include "../../model/world/idtable.hpp" #include "../../model/world/columns.hpp" @@ -11,7 +16,9 @@ #include "../../model/world/refcollection.hpp" #include "../../model/world/cellcoordinates.hpp" +#include "cellwater.hpp" #include "mask.hpp" +#include "pathgrid.hpp" #include "terrainstorage.hpp" bool CSVRender::Cell::removeObject (const std::string& id) @@ -50,7 +57,12 @@ bool CSVRender::Cell::addObjects (int start, int end) { std::string id = Misc::StringUtils::lowerCase (collection.getRecord (i).get().mId); - mObjects.insert (std::make_pair (id, new Object (mData, mCellNode, id, false))); + std::auto_ptr object (new Object (mData, mCellNode, id, false)); + + if (mSubModeElementMask & Mask_Reference) + object->setSubMode (mSubMode); + + mObjects.insert (std::make_pair (id, object.release())); modified = true; } } @@ -60,7 +72,8 @@ bool CSVRender::Cell::addObjects (int start, int end) CSVRender::Cell::Cell (CSMWorld::Data& data, osg::Group* rootNode, const std::string& id, bool deleted) -: mData (data), mId (Misc::StringUtils::lowerCase (id)), mDeleted (deleted) +: mData (data), mId (Misc::StringUtils::lowerCase (id)), mDeleted (deleted), mSubMode (0), + mSubModeElementMask (0) { std::pair result = CSMWorld::CellCoordinates::fromId (id); @@ -70,6 +83,8 @@ CSVRender::Cell::Cell (CSMWorld::Data& data, osg::Group* rootNode, const std::st mCellNode = new osg::Group; rootNode->addChild(mCellNode); + setCellMarker(); + if (!mDeleted) { CSMWorld::IdTable& references = dynamic_cast ( @@ -90,8 +105,14 @@ CSVRender::Cell::Cell (CSMWorld::Data& data, osg::Group* rootNode, const std::st mTerrain.reset(new Terrain::TerrainGrid(mCellNode, data.getResourceSystem().get(), NULL, new TerrainStorage(mData), Mask_Terrain)); mTerrain->loadCell(esmLand.mX, esmLand.mY); + + mCellBorder.reset(new CellBorder(mCellNode, mCoordinates)); + mCellBorder->buildShape(esmLand); } } + + mPathgrid.reset(new Pathgrid(mData, mCellNode, mId, mCoordinates)); + mCellWater.reset(new CellWater(mData, mCellNode, mId, mCoordinates)); } } @@ -104,6 +125,11 @@ CSVRender::Cell::~Cell() mCellNode->getParent(0)->removeChild(mCellNode); } +CSVRender::Pathgrid* CSVRender::Cell::getPathgrid() const +{ + return mPathgrid.get(); +} + bool CSVRender::Cell::referenceableDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) { @@ -193,12 +219,12 @@ bool CSVRender::Cell::referenceDataChanged (const QModelIndex& topLeft, } // add new objects - for (std::map::iterator iter (ids.begin()); iter!=ids.end(); ++iter) + for (std::map::iterator mapIter (ids.begin()); mapIter!=ids.end(); ++mapIter) { - if (!iter->second) + if (!mapIter->second) { mObjects.insert (std::make_pair ( - iter->first, new Object (mData, mCellNode, iter->first, false))); + mapIter->first, new Object (mData, mCellNode, mapIter->first, false))); modified = true; } @@ -242,6 +268,16 @@ bool CSVRender::Cell::referenceAdded (const QModelIndex& parent, int start, int return addObjects (start, end); } +void CSVRender::Cell::pathgridModified() +{ + mPathgrid->recreateGeometry(); +} + +void CSVRender::Cell::pathgridRemoved() +{ + mPathgrid->removeGeometry(); +} + void CSVRender::Cell::setSelection (int elementMask, Selection mode) { if (elementMask & Mask_Reference) @@ -261,6 +297,27 @@ void CSVRender::Cell::setSelection (int elementMask, Selection mode) iter->second->setSelected (selected); } } + if (elementMask & Mask_Pathgrid) + { + // Only one pathgrid may be selected, so some operations will only have an effect + // if the pathgrid is already focused + switch (mode) + { + case Selection_Clear: + mPathgrid->clearSelected(); + break; + + case Selection_All: + if (mPathgrid->isSelected()) + mPathgrid->selectAll(); + break; + + case Selection_Invert: + if (mPathgrid->isSelected()) + mPathgrid->invertSelected(); + break; + } + } } void CSVRender::Cell::selectAllWithSameParentId (int elementMask) @@ -303,6 +360,24 @@ void CSVRender::Cell::setCellArrows (int mask) } } +void CSVRender::Cell::setCellMarker() +{ + bool cellExists = false; + bool isInteriorCell = false; + + int cellIndex = mData.getCells().searchId(mId); + if (cellIndex > -1) + { + const CSMWorld::Record& cellRecord = mData.getCells().getRecord(cellIndex); + cellExists = !cellRecord.isDeleted(); + isInteriorCell = cellRecord.get().mData.mFlags & ESM::Cell::Interior; + } + + if (!isInteriorCell) { + mCellMarker.reset(new CellMarker(mCellNode, mCoordinates, cellExists)); + } +} + CSMWorld::CellCoordinates CSVRender::Cell::getCoordinates() const { return mCoordinates; @@ -322,6 +397,43 @@ std::vector > CSVRender::Cell::getSelection (un iter!=mObjects.end(); ++iter) if (iter->second->getSelected()) result.push_back (iter->second->getTag()); + if (elementMask & Mask_Pathgrid) + if (mPathgrid->isSelected()) + result.push_back(mPathgrid->getTag()); return result; } + +std::vector > CSVRender::Cell::getEdited (unsigned int elementMask) const +{ + std::vector > result; + + if (elementMask & Mask_Reference) + for (std::map::const_iterator iter (mObjects.begin()); + iter!=mObjects.end(); ++iter) + if (iter->second->isEdited()) + result.push_back (iter->second->getTag()); + + return result; +} + +void CSVRender::Cell::setSubMode (int subMode, unsigned int elementMask) +{ + mSubMode = subMode; + mSubModeElementMask = elementMask; + + if (elementMask & Mask_Reference) + for (std::map::const_iterator iter (mObjects.begin()); + iter!=mObjects.end(); ++iter) + iter->second->setSubMode (subMode); +} + +void CSVRender::Cell::reset (unsigned int elementMask) +{ + if (elementMask & Mask_Reference) + for (std::map::const_iterator iter (mObjects.begin()); + iter!=mObjects.end(); ++iter) + iter->second->reset(); + if (elementMask & Mask_Pathgrid) + mPathgrid->resetIndicators(); +} diff --git a/apps/opencs/view/render/cell.hpp b/apps/opencs/view/render/cell.hpp index 85b9bf21b..8f68e9f53 100644 --- a/apps/opencs/view/render/cell.hpp +++ b/apps/opencs/view/render/cell.hpp @@ -15,12 +15,16 @@ #include "object.hpp" #include "cellarrow.hpp" +#include "cellmarker.hpp" +#include "cellborder.hpp" class QModelIndex; namespace osg { class Group; + class Geometry; + class Geode; } namespace CSMWorld @@ -31,6 +35,8 @@ namespace CSMWorld namespace CSVRender { + class CellWater; + class Pathgrid; class TagBase; class Cell @@ -42,7 +48,13 @@ namespace CSVRender std::auto_ptr mTerrain; CSMWorld::CellCoordinates mCoordinates; std::auto_ptr mCellArrows[4]; + std::auto_ptr mCellMarker; + std::auto_ptr mCellBorder; + std::auto_ptr mCellWater; + std::auto_ptr mPathgrid; bool mDeleted; + int mSubMode; + unsigned int mSubModeElementMask; /// Ignored if cell does not have an object with the given ID. /// @@ -76,6 +88,9 @@ namespace CSVRender ~Cell(); + /// \note Returns the pathgrid representation which will exist as long as the cell exists + Pathgrid* getPathgrid() const; + /// \return Did this call result in a modification of the visual representation of /// this cell? bool referenceableDataChanged (const QModelIndex& topLeft, @@ -97,6 +112,10 @@ namespace CSVRender /// this cell? bool referenceAdded (const QModelIndex& parent, int start, int end); + void pathgridModified(); + + void pathgridRemoved(); + void setSelection (int elementMask, Selection mode); // Select everything that references the same ID as at least one of the elements @@ -105,12 +124,23 @@ namespace CSVRender void setCellArrows (int mask); + /// \brief Set marker for this cell. + void setCellMarker(); + /// Returns 0, 0 in case of an unpaged cell. CSMWorld::CellCoordinates getCoordinates() const; bool isDeleted() const; std::vector > getSelection (unsigned int elementMask) const; + + std::vector > getEdited (unsigned int elementMask) const; + + void setSubMode (int subMode, unsigned int elementMask); + + /// Erase all overrides and restore the visual representation of the cell to its + /// true state. + void reset (unsigned int elementMask); }; } diff --git a/apps/opencs/view/render/cellarrow.cpp b/apps/opencs/view/render/cellarrow.cpp index 6d8fa1c6c..b8c89c83d 100644 --- a/apps/opencs/view/render/cellarrow.cpp +++ b/apps/opencs/view/render/cellarrow.cpp @@ -7,6 +7,9 @@ #include #include +#include "../../model/prefs/state.hpp" +#include "../../model/prefs/shortcutmanager.hpp" + #include "mask.hpp" CSVRender::CellArrowTag::CellArrowTag (CellArrow *arrow) @@ -35,14 +38,19 @@ QString CSVRender::CellArrowTag::getToolTip (bool hideBasics) const text += "

" "Modify which cells are shown" - "

  • Primary-Edit: Add cell in given direction
  • " - "
  • Secondary-Edit: Add cell and remove old cell
  • " - "
  • Shift Primary-Edit: Add cells in given direction
  • " - "
  • Shift Secondary-Edit: Add cells and remove old cells
  • " + "
    • {scene-edit-primary}: Add cell in given direction
    • " + "
    • {scene-edit-secondary}: Add cell and remove old cell
    • " + "
    • {scene-select-primary}: Add cells in given direction
    • " + "
    • {scene-select-secondary}: Add cells and remove old cells
    • " + "
    • {scene-load-cam-cell}: Load cell where camera is located
    • " + "
    • {scene-load-cam-eastcell}: Load cell to east
    • " + "
    • {scene-load-cam-northcell}: Load cell to north
    • " + "
    • {scene-load-cam-westcell}: Load cell to west
    • " + "
    • {scene-load-cam-southcell}: Load cell to south
    • " "
    "; } - return text; + return CSMPrefs::State::get().getShortcutManager().processToolTip(text); } diff --git a/apps/opencs/view/render/cellborder.cpp b/apps/opencs/view/render/cellborder.cpp new file mode 100644 index 000000000..6073807ce --- /dev/null +++ b/apps/opencs/view/render/cellborder.cpp @@ -0,0 +1,96 @@ +#include "cellborder.hpp" + +#include +#include +#include +#include +#include + +#include + +#include "mask.hpp" + +#include "../../model/world/cellcoordinates.hpp" + +const int CSVRender::CellBorder::CellSize = ESM::Land::REAL_SIZE; +const int CSVRender::CellBorder::VertexCount = (ESM::Land::LAND_SIZE * 4) - 3; + + +CSVRender::CellBorder::CellBorder(osg::Group* cellNode, const CSMWorld::CellCoordinates& coords) + : mParentNode(cellNode) +{ + mBaseNode = new osg::PositionAttitudeTransform(); + mBaseNode->setNodeMask(Mask_CellBorder); + mBaseNode->setPosition(osg::Vec3f(coords.getX() * CellSize, coords.getY() * CellSize, 10)); + + mParentNode->addChild(mBaseNode); +} + +CSVRender::CellBorder::~CellBorder() +{ + mParentNode->removeChild(mBaseNode); +} + +void CSVRender::CellBorder::buildShape(const ESM::Land& esmLand) +{ + const ESM::Land::LandData* landData = esmLand.getLandData(ESM::Land::DATA_VHGT); + + if (!landData) + return; + + osg::ref_ptr geometry = new osg::Geometry(); + + // Vertices + osg::ref_ptr vertices = new osg::Vec3Array(); + + int x = 0, y = 0; + for (; x < ESM::Land::LAND_SIZE; ++x) + vertices->push_back(osg::Vec3f(scaleToWorld(x), scaleToWorld(y), landData->mHeights[landIndex(x, y)])); + + x = ESM::Land::LAND_SIZE - 1; + for (; y < ESM::Land::LAND_SIZE; ++y) + vertices->push_back(osg::Vec3f(scaleToWorld(x), scaleToWorld(y), landData->mHeights[landIndex(x, y)])); + + y = ESM::Land::LAND_SIZE - 1; + for (; x >= 0; --x) + vertices->push_back(osg::Vec3f(scaleToWorld(x), scaleToWorld(y), landData->mHeights[landIndex(x, y)])); + + x = 0; + for (; y >= 0; --y) + vertices->push_back(osg::Vec3f(scaleToWorld(x), scaleToWorld(y), landData->mHeights[landIndex(x, y)])); + + geometry->setVertexArray(vertices); + + // Color + osg::ref_ptr colors = new osg::Vec4Array(); + colors->push_back(osg::Vec4f(0.f, 0.5f, 0.f, 1.f)); + + geometry->setColorArray(colors, osg::Array::BIND_PER_PRIMITIVE_SET); + + // Primitive + osg::ref_ptr primitives = + new osg::DrawElementsUShort(osg::PrimitiveSet::LINE_STRIP, VertexCount+1); + + for (size_t i = 0; i < VertexCount; ++i) + primitives->setElement(i, i); + + primitives->setElement(VertexCount, 0); + + geometry->addPrimitiveSet(primitives); + geometry->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); + + + osg::ref_ptr geode = new osg::Geode(); + geode->addDrawable(geometry); + mBaseNode->addChild(geode); +} + +size_t CSVRender::CellBorder::landIndex(int x, int y) +{ + return y * ESM::Land::LAND_SIZE + x; +} + +float CSVRender::CellBorder::scaleToWorld(int value) +{ + return (CellSize + 128) * (float)value / ESM::Land::LAND_SIZE; +} diff --git a/apps/opencs/view/render/cellborder.hpp b/apps/opencs/view/render/cellborder.hpp new file mode 100644 index 000000000..c91aa46c6 --- /dev/null +++ b/apps/opencs/view/render/cellborder.hpp @@ -0,0 +1,54 @@ +#ifndef OPENCS_VIEW_CELLBORDER_H +#define OPENCS_VIEW_CELLBORDER_H + +#include + +#include + +namespace osg +{ + class Group; + class PositionAttitudeTransform; +} + +namespace ESM +{ + struct Land; +} + +namespace CSMWorld +{ + class CellCoordinates; +} + +namespace CSVRender +{ + + class CellBorder + { + public: + + CellBorder(osg::Group* cellNode, const CSMWorld::CellCoordinates& coords); + ~CellBorder(); + + void buildShape(const ESM::Land& esmLand); + + private: + + static const int CellSize; + static const int VertexCount; + + size_t landIndex(int x, int y); + float scaleToWorld(int val); + + // unimplemented + CellBorder(const CellBorder&); + CellBorder& operator=(const CellBorder&); + + osg::Group* mParentNode; + osg::ref_ptr mBaseNode; + + }; +} + +#endif diff --git a/apps/opencs/view/render/cellmarker.cpp b/apps/opencs/view/render/cellmarker.cpp new file mode 100644 index 000000000..abc337ce2 --- /dev/null +++ b/apps/opencs/view/render/cellmarker.cpp @@ -0,0 +1,92 @@ +#include "cellmarker.hpp" + +#include + +#include +#include +#include +#include + +#include "mask.hpp" + +CSVRender::CellMarkerTag::CellMarkerTag(CellMarker *marker) +: TagBase(Mask_CellMarker), mMarker(marker) +{} + +CSVRender::CellMarker *CSVRender::CellMarkerTag::getCellMarker() const +{ + return mMarker; +} + +void CSVRender::CellMarker::buildMarker() +{ + const int characterSize = 20; + + // Set up attributes of marker text. + osg::ref_ptr markerText (new osgText::Text); + markerText->setLayout(osgText::Text::LEFT_TO_RIGHT); + markerText->setCharacterSize(characterSize); + markerText->setAlignment(osgText::Text::CENTER_CENTER); + markerText->setDrawMode(osgText::Text::TEXT | osgText::Text::FILLEDBOUNDINGBOX); + + // If cell exists then show black bounding box otherwise show red. + if (mExists) + { + markerText->setBoundingBoxColor(osg::Vec4f(0.0f, 0.0f, 0.0f, 1.0f)); + } + else + { + markerText->setBoundingBoxColor(osg::Vec4f(1.0f, 0.0f, 0.0f, 1.0f)); + } + + // Add text containing cell's coordinates. + std::string coordinatesText = + boost::lexical_cast(mCoordinates.getX()) + "," + + boost::lexical_cast(mCoordinates.getY()); + markerText->setText(coordinatesText); + + // Add text to marker node. + osg::ref_ptr geode (new osg::Geode); + geode->addDrawable(markerText); + mMarkerNode->addChild(geode); +} + +void CSVRender::CellMarker::positionMarker() +{ + const int cellSize = 8192; + const int markerHeight = 0; + + // Move marker to center of cell. + int x = (mCoordinates.getX() * cellSize) + (cellSize / 2); + int y = (mCoordinates.getY() * cellSize) + (cellSize / 2); + mMarkerNode->setPosition(osg::Vec3f(x, y, markerHeight)); +} + +CSVRender::CellMarker::CellMarker( + osg::Group *cellNode, + const CSMWorld::CellCoordinates& coordinates, + const bool cellExists +) : mCellNode(cellNode), + mCoordinates(coordinates), + mExists(cellExists) +{ + // Set up node for cell marker. + mMarkerNode = new osg::AutoTransform(); + mMarkerNode->setAutoRotateMode(osg::AutoTransform::ROTATE_TO_SCREEN); + mMarkerNode->setAutoScaleToScreen(true); + mMarkerNode->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); + mMarkerNode->getOrCreateStateSet()->setRenderBinDetails(osg::StateSet::TRANSPARENT_BIN + 1, "RenderBin"); + + mMarkerNode->setUserData(new CellMarkerTag(this)); + mMarkerNode->setNodeMask(Mask_CellMarker); + + mCellNode->addChild(mMarkerNode); + + buildMarker(); + positionMarker(); +} + +CSVRender::CellMarker::~CellMarker() +{ + mCellNode->removeChild(mMarkerNode); +} diff --git a/apps/opencs/view/render/cellmarker.hpp b/apps/opencs/view/render/cellmarker.hpp new file mode 100644 index 000000000..4246b20b8 --- /dev/null +++ b/apps/opencs/view/render/cellmarker.hpp @@ -0,0 +1,68 @@ +#ifndef OPENCS_VIEW_CELLMARKER_H +#define OPENCS_VIEW_CELLMARKER_H + +#include "tagbase.hpp" + +#include + +#include "../../model/world/cellcoordinates.hpp" + +namespace osg +{ + class AutoTransform; + class Group; +} + +namespace CSVRender +{ + class CellMarker; + + class CellMarkerTag : public TagBase + { + private: + + CellMarker *mMarker; + + public: + + CellMarkerTag(CellMarker *marker); + + CellMarker *getCellMarker() const; + }; + + /// \brief Marker to display cell coordinates. + class CellMarker + { + private: + + osg::Group* mCellNode; + osg::ref_ptr mMarkerNode; + CSMWorld::CellCoordinates mCoordinates; + bool mExists; + + // Not implemented. + CellMarker(const CellMarker&); + CellMarker& operator=(const CellMarker&); + + /// \brief Build marker containing cell's coordinates. + void buildMarker(); + + /// \brief Position marker at center of cell. + void positionMarker(); + + public: + + /// \brief Constructor. + /// \param cellNode Cell to create marker for. + /// \param coordinates Coordinates of cell. + /// \param cellExists Whether or not cell exists. + CellMarker( + osg::Group *cellNode, + const CSMWorld::CellCoordinates& coordinates, + const bool cellExists); + + ~CellMarker(); + }; +} + +#endif diff --git a/apps/opencs/view/render/cellwater.cpp b/apps/opencs/view/render/cellwater.cpp new file mode 100644 index 000000000..15ea15cc4 --- /dev/null +++ b/apps/opencs/view/render/cellwater.cpp @@ -0,0 +1,177 @@ +#include "cellwater.hpp" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "../../model/world/cell.hpp" +#include "../../model/world/cellcoordinates.hpp" +#include "../../model/world/data.hpp" + +#include "mask.hpp" + +namespace CSVRender +{ + const int CellWater::CellSize = ESM::Land::REAL_SIZE; + + CellWater::CellWater(CSMWorld::Data& data, osg::Group* cellNode, const std::string& id, + const CSMWorld::CellCoordinates& cellCoords) + : mData(data) + , mId(id) + , mParentNode(cellNode) + , mWaterTransform(0) + , mWaterNode(0) + , mWaterGeometry(0) + , mDeleted(false) + , mExterior(false) + , mHasWater(false) + { + mWaterTransform = new osg::PositionAttitudeTransform(); + mWaterTransform->setPosition(osg::Vec3f(cellCoords.getX() * CellSize + CellSize / 2.f, + cellCoords.getY() * CellSize + CellSize / 2.f, 0)); + + mWaterTransform->setNodeMask(Mask_Water); + mParentNode->addChild(mWaterTransform); + + mWaterNode = new osg::Geode(); + mWaterTransform->addChild(mWaterNode); + + int cellIndex = mData.getCells().searchId(mId); + if (cellIndex > -1) + { + updateCellData(mData.getCells().getRecord(cellIndex)); + } + + // Keep water existence/height up to date + QAbstractItemModel* cells = mData.getTableModel(CSMWorld::UniversalId::Type_Cells); + connect(cells, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)), + this, SLOT(cellDataChanged(const QModelIndex&, const QModelIndex&))); + } + + CellWater::~CellWater() + { + mParentNode->removeChild(mWaterTransform); + } + + void CellWater::updateCellData(const CSMWorld::Record& cellRecord) + { + mDeleted = cellRecord.isDeleted(); + if (!mDeleted) + { + const CSMWorld::Cell& cell = cellRecord.get(); + + if (mExterior != cell.isExterior() || mHasWater != cell.hasWater()) + { + mExterior = cellRecord.get().isExterior(); + mHasWater = cellRecord.get().hasWater(); + + recreate(); + } + + float waterHeight = -1; + if (!mExterior) + { + waterHeight = cellRecord.get().mWater; + } + + osg::Vec3d pos = mWaterTransform->getPosition(); + pos.z() = waterHeight; + mWaterTransform->setPosition(pos); + } + else + { + recreate(); + } + } + + void CellWater::cellDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) + { + const CSMWorld::Collection& cells = mData.getCells(); + + int rowStart = -1; + int rowEnd = -1; + + if (topLeft.parent().isValid()) + { + rowStart = topLeft.parent().row(); + rowEnd = bottomRight.parent().row(); + } + else + { + rowStart = topLeft.row(); + rowEnd = bottomRight.row(); + } + + for (int row = rowStart; row <= rowEnd; ++row) + { + const CSMWorld::Record& cellRecord = cells.getRecord(row); + + if (Misc::StringUtils::lowerCase(cellRecord.get().mId) == mId) + updateCellData(cellRecord); + } + } + + void CellWater::recreate() + { + const int InteriorScalar = 20; + const int SegmentsPerCell = 1; + const int TextureRepeatsPerCell = 6; + + const float Alpha = 0.5f; + + const int RenderBin = osg::StateSet::TRANSPARENT_BIN - 1; + + if (mWaterGeometry) + { + mWaterNode->removeDrawable(mWaterGeometry); + mWaterGeometry = 0; + } + + if (mDeleted || !mHasWater) + return; + + float size; + int segments; + float textureRepeats; + + if (mExterior) + { + size = CellSize; + segments = SegmentsPerCell; + textureRepeats = TextureRepeatsPerCell; + } + else + { + size = CellSize * InteriorScalar; + segments = SegmentsPerCell * InteriorScalar; + textureRepeats = TextureRepeatsPerCell * InteriorScalar; + } + + mWaterGeometry = SceneUtil::createWaterGeometry(size, segments, textureRepeats); + mWaterGeometry->setStateSet(SceneUtil::createSimpleWaterStateSet(Alpha, RenderBin)); + + // Add water texture + std::string textureName = mData.getFallbackMap()->getFallbackString("Water_SurfaceTexture"); + textureName = "textures/water/" + textureName + "00.dds"; + + Resource::ImageManager* imageManager = mData.getResourceSystem()->getImageManager(); + + osg::ref_ptr waterTexture = new osg::Texture2D(); + waterTexture->setImage(imageManager->getImage(textureName)); + waterTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); + waterTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); + + mWaterGeometry->getStateSet()->setTextureAttributeAndModes(0, waterTexture, osg::StateAttribute::ON); + + + mWaterNode->addDrawable(mWaterGeometry); + } +} diff --git a/apps/opencs/view/render/cellwater.hpp b/apps/opencs/view/render/cellwater.hpp new file mode 100644 index 000000000..d2ed9b458 --- /dev/null +++ b/apps/opencs/view/render/cellwater.hpp @@ -0,0 +1,70 @@ +#ifndef CSV_RENDER_CELLWATER_H +#define CSV_RENDER_CELLWATER_H + +#include + +#include + +#include +#include + +#include "../../model/world/record.hpp" + +namespace osg +{ + class Geode; + class Geometry; + class Group; + class PositionAttitudeTransform; +} + +namespace CSMWorld +{ + struct Cell; + class CellCoordinates; + class Data; +} + +namespace CSVRender +{ + /// For exterior cells, this adds a patch of water to fit the size of the cell. For interior cells with water, this + /// adds a large patch of water much larger than the typical size of a cell. + class CellWater : public QObject + { + Q_OBJECT + + public: + + CellWater(CSMWorld::Data& data, osg::Group* cellNode, const std::string& id, + const CSMWorld::CellCoordinates& cellCoords); + + ~CellWater(); + + void updateCellData(const CSMWorld::Record& cellRecord); + + private slots: + + void cellDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight); + + private: + + void recreate(); + + static const int CellSize; + + CSMWorld::Data& mData; + std::string mId; + + osg::Group* mParentNode; + + osg::ref_ptr mWaterTransform; + osg::ref_ptr mWaterNode; + osg::ref_ptr mWaterGeometry; + + bool mDeleted; + bool mExterior; + bool mHasWater; + }; +} + +#endif diff --git a/apps/opencs/view/render/editmode.cpp b/apps/opencs/view/render/editmode.cpp index b325e31fb..5f0852c90 100644 --- a/apps/opencs/view/render/editmode.cpp +++ b/apps/opencs/view/render/editmode.cpp @@ -29,37 +29,37 @@ void CSVRender::EditMode::setEditLock (bool locked) } -void CSVRender::EditMode::primaryEditPressed (osg::ref_ptr tag) {} +void CSVRender::EditMode::primaryEditPressed (const WorldspaceHitResult& hit) {} -void CSVRender::EditMode::secondaryEditPressed (osg::ref_ptr tag) {} +void CSVRender::EditMode::secondaryEditPressed (const WorldspaceHitResult& hit) {} -void CSVRender::EditMode::primarySelectPressed (osg::ref_ptr tag) {} +void CSVRender::EditMode::primarySelectPressed (const WorldspaceHitResult& hit) {} -void CSVRender::EditMode::secondarySelectPressed (osg::ref_ptr tag) {} +void CSVRender::EditMode::secondarySelectPressed (const WorldspaceHitResult& hit) {} -bool CSVRender::EditMode::primaryEditStartDrag (osg::ref_ptr tag) +bool CSVRender::EditMode::primaryEditStartDrag (const QPoint& pos) { return false; } -bool CSVRender::EditMode::secondaryEditStartDrag (osg::ref_ptr tag) +bool CSVRender::EditMode::secondaryEditStartDrag (const QPoint& pos) { return false; } -bool CSVRender::EditMode::primarySelectStartDrag (osg::ref_ptr tag) +bool CSVRender::EditMode::primarySelectStartDrag (const QPoint& pos) { return false; } -bool CSVRender::EditMode::secondarySelectStartDrag (osg::ref_ptr tag) +bool CSVRender::EditMode::secondarySelectStartDrag (const QPoint& pos) { return false; } -void CSVRender::EditMode::drag (int diffX, int diffY, double speedFactor) {} +void CSVRender::EditMode::drag (const QPoint& pos, int diffX, int diffY, double speedFactor) {} -void CSVRender::EditMode::dragCompleted() {} +void CSVRender::EditMode::dragCompleted(const QPoint& pos) {} void CSVRender::EditMode::dragAborted() {} @@ -67,6 +67,11 @@ void CSVRender::EditMode::dragWheel (int diff, double speedFactor) {} void CSVRender::EditMode::dragEnterEvent (QDragEnterEvent *event) {} -void CSVRender::EditMode::dropEvent (QDropEvent* event) {} +void CSVRender::EditMode::dropEvent (QDropEvent *event) {} void CSVRender::EditMode::dragMoveEvent (QDragMoveEvent *event) {} + +int CSVRender::EditMode::getSubMode() const +{ + return -1; +} diff --git a/apps/opencs/view/render/editmode.hpp b/apps/opencs/view/render/editmode.hpp index 3ba97cf00..f9b7027f9 100644 --- a/apps/opencs/view/render/editmode.hpp +++ b/apps/opencs/view/render/editmode.hpp @@ -8,10 +8,12 @@ class QDragEnterEvent; class QDropEvent; class QDragMoveEvent; +class QPoint; namespace CSVRender { class WorldspaceWidget; + struct WorldspaceHitResult; class TagBase; class EditMode : public CSVWidget::ModeButton @@ -38,42 +40,42 @@ namespace CSVRender virtual void setEditLock (bool locked); /// Default-implementation: Ignored. - virtual void primaryEditPressed (osg::ref_ptr tag); + virtual void primaryEditPressed (const WorldspaceHitResult& hit); /// Default-implementation: Ignored. - virtual void secondaryEditPressed (osg::ref_ptr tag); + virtual void secondaryEditPressed (const WorldspaceHitResult& hit); /// Default-implementation: Ignored. - virtual void primarySelectPressed (osg::ref_ptr tag); + virtual void primarySelectPressed (const WorldspaceHitResult& hit); /// Default-implementation: Ignored. - virtual void secondarySelectPressed (osg::ref_ptr tag); + virtual void secondarySelectPressed (const WorldspaceHitResult& hit); /// Default-implementation: ignore and return false /// /// \return Drag accepted? - virtual bool primaryEditStartDrag (osg::ref_ptr tag); + virtual bool primaryEditStartDrag (const QPoint& pos); /// Default-implementation: ignore and return false /// /// \return Drag accepted? - virtual bool secondaryEditStartDrag (osg::ref_ptr tag); + virtual bool secondaryEditStartDrag (const QPoint& pos); /// Default-implementation: ignore and return false /// /// \return Drag accepted? - virtual bool primarySelectStartDrag (osg::ref_ptr tag); + virtual bool primarySelectStartDrag (const QPoint& pos); /// Default-implementation: ignore and return false /// /// \return Drag accepted? - virtual bool secondarySelectStartDrag (osg::ref_ptr tag); + virtual bool secondarySelectStartDrag (const QPoint& pos); /// Default-implementation: ignored - virtual void drag (int diffX, int diffY, double speedFactor); + virtual void drag (const QPoint& pos, int diffX, int diffY, double speedFactor); /// Default-implementation: ignored - virtual void dragCompleted(); + virtual void dragCompleted(const QPoint& pos); /// Default-implementation: ignored /// @@ -88,10 +90,13 @@ namespace CSVRender virtual void dragEnterEvent (QDragEnterEvent *event); /// Default-implementation: ignored - virtual void dropEvent (QDropEvent* event); + virtual void dropEvent (QDropEvent *event); /// Default-implementation: ignored virtual void dragMoveEvent (QDragMoveEvent *event); + + /// Default: return -1 + virtual int getSubMode() const; }; } diff --git a/apps/opencs/view/render/instancemode.cpp b/apps/opencs/view/render/instancemode.cpp index 4b6b2e41f..3373daa4a 100644 --- a/apps/opencs/view/render/instancemode.cpp +++ b/apps/opencs/view/render/instancemode.cpp @@ -2,12 +2,14 @@ #include "instancemode.hpp" #include +#include #include "../../model/prefs/state.hpp" #include "../../model/world/idtable.hpp" #include "../../model/world/idtree.hpp" #include "../../model/world/commands.hpp" +#include "../../model/world/commandmacro.hpp" #include "../widget/scenetoolbar.hpp" #include "../widget/scenetoolmode.hpp" @@ -18,10 +20,81 @@ #include "worldspacewidget.hpp" #include "pagedworldspacewidget.hpp" #include "instanceselectionmode.hpp" +#include "instancemovemode.hpp" + +int CSVRender::InstanceMode::getSubModeFromId (const std::string& id) const +{ + return id=="move" ? 0 : (id=="rotate" ? 1 : 2); +} + +osg::Vec3f CSVRender::InstanceMode::quatToEuler(const osg::Quat& rot) const +{ + const float Pi = 3.14159265f; + + float x, y, z; + float test = 2 * (rot.w() * rot.y() + rot.x() * rot.z()); + + if (std::abs(test) >= 1.f) + { + x = atan2(rot.x(), rot.w()); + y = (test > 0) ? (Pi / 2) : (-Pi / 2); + z = 0; + } + else + { + x = std::atan2(2 * (rot.w() * rot.x() - rot.y() * rot.z()), 1 - 2 * (rot.x() * rot.x() + rot.y() * rot.y())); + y = std::asin(test); + z = std::atan2(2 * (rot.w() * rot.z() - rot.x() * rot.y()), 1 - 2 * (rot.y() * rot.y() + rot.z() * rot.z())); + } + + return osg::Vec3f(-x, -y, -z); +} + +osg::Quat CSVRender::InstanceMode::eulerToQuat(const osg::Vec3f& euler) const +{ + osg::Quat xr = osg::Quat(-euler[0], osg::Vec3f(1,0,0)); + osg::Quat yr = osg::Quat(-euler[1], osg::Vec3f(0,1,0)); + osg::Quat zr = osg::Quat(-euler[2], osg::Vec3f(0,0,1)); + + return zr * yr * xr; +} + +osg::Vec3f CSVRender::InstanceMode::getSelectionCenter(const std::vector >& selection) const +{ + osg::Vec3f center = osg::Vec3f(0, 0, 0); + int objectCount = 0; + + for (std::vector >::const_iterator iter (selection.begin()); iter!=selection.end(); ++iter) + { + if (CSVRender::ObjectTag *objectTag = dynamic_cast (iter->get())) + { + const ESM::Position& position = objectTag->mObject->getPosition(); + center += osg::Vec3f(position.pos[0], position.pos[1], position.pos[2]); + + ++objectCount; + } + } + + if (objectCount > 0) + center /= objectCount; + + return center; +} + +osg::Vec3f CSVRender::InstanceMode::getScreenCoords(const osg::Vec3f& pos) +{ + osg::Matrix viewMatrix = getWorldspaceWidget().getCamera()->getViewMatrix(); + osg::Matrix projMatrix = getWorldspaceWidget().getCamera()->getProjectionMatrix(); + osg::Matrix windowMatrix = getWorldspaceWidget().getCamera()->getViewport()->computeWindowMatrix(); + osg::Matrix combined = viewMatrix * projMatrix * windowMatrix; + + return pos * combined; +} CSVRender::InstanceMode::InstanceMode (WorldspaceWidget *worldspaceWidget, QWidget *parent) -: EditMode (worldspaceWidget, QIcon (":placeholder"), Mask_Reference, "Instance editing", - parent), mSubMode (0), mSelectionMode (0) +: EditMode (worldspaceWidget, QIcon (":placeholder"), Mask_Reference | Mask_Terrain, "Instance editing", + parent), mSubMode (0), mSubModeId ("move"), mSelectionMode (0), mDragMode (DragMode_None), + mDragAxis (-1), mLocked (false), mUnitScaleDist(1) { } @@ -30,37 +103,48 @@ void CSVRender::InstanceMode::activate (CSVWidget::SceneToolbar *toolbar) if (!mSubMode) { mSubMode = new CSVWidget::SceneToolMode (toolbar, "Edit Sub-Mode"); - mSubMode->addButton (":placeholder", "move", - "Move selected instances" - "
    • Use primary edit to move instances around freely
    • " - "
    • Use secondary edit to move instances around within the grid
    • " - "
    " - "Not implemented yet"); + mSubMode->addButton (new InstanceMoveMode (this), "move"); mSubMode->addButton (":placeholder", "rotate", "Rotate selected instances" - "
    • Use primary edit to rotate instances freely
    • " - "
    • Use secondary edit to rotate instances within the grid
    • " + "
      • Use {scene-edit-primary} to rotate instances freely
      • " + "
      • Use {scene-edit-secondary} to rotate instances within the grid
      • " + "
      • The center of the view acts as the axis of rotation
      • " "
      " - "Not implemented yet"); + "Grid rotate not implemented yet"); mSubMode->addButton (":placeholder", "scale", "Scale selected instances" - "
      • Use primary edit to scale instances freely
      • " - "
      • Use secondary edit to scale instances along the grid
      • " + "
        • Use {scene-edit-primary} to scale instances freely
        • " + "
        • Use {scene-edit-secondary} to scale instances along the grid
        • " + "
        • The scaling rate is based on how close the start of a drag is to the center of the screen
        • " "
        " - "Not implemented yet"); + "Grid scale not implemented yet"); + + mSubMode->setButton (mSubModeId); + + connect (mSubMode, SIGNAL (modeChanged (const std::string&)), + this, SLOT (subModeChanged (const std::string&))); } if (!mSelectionMode) mSelectionMode = new InstanceSelectionMode (toolbar, getWorldspaceWidget()); + mDragMode = DragMode_None; + EditMode::activate (toolbar); toolbar->addTool (mSubMode); toolbar->addTool (mSelectionMode); + + std::string subMode = mSubMode->getCurrentId(); + + getWorldspaceWidget().setSubMode (getSubModeFromId (subMode), Mask_Reference); } void CSVRender::InstanceMode::deactivate (CSVWidget::SceneToolbar *toolbar) { + mDragMode = DragMode_None; + getWorldspaceWidget().reset (Mask_Reference); + if (mSelectionMode) { toolbar->removeTool (mSelectionMode); @@ -78,25 +162,33 @@ void CSVRender::InstanceMode::deactivate (CSVWidget::SceneToolbar *toolbar) EditMode::deactivate (toolbar); } -void CSVRender::InstanceMode::primaryEditPressed (osg::ref_ptr tag) +void CSVRender::InstanceMode::setEditLock (bool locked) +{ + mLocked = locked; + + if (mLocked) + getWorldspaceWidget().abortDrag(); +} + +void CSVRender::InstanceMode::primaryEditPressed (const WorldspaceHitResult& hit) { if (CSMPrefs::get()["3D Scene Input"]["context-select"].isTrue()) - primarySelectPressed (tag); + primarySelectPressed (hit); } -void CSVRender::InstanceMode::secondaryEditPressed (osg::ref_ptr tag) +void CSVRender::InstanceMode::secondaryEditPressed (const WorldspaceHitResult& hit) { if (CSMPrefs::get()["3D Scene Input"]["context-select"].isTrue()) - secondarySelectPressed (tag); + secondarySelectPressed (hit); } -void CSVRender::InstanceMode::primarySelectPressed (osg::ref_ptr tag) +void CSVRender::InstanceMode::primarySelectPressed (const WorldspaceHitResult& hit) { getWorldspaceWidget().clearSelection (Mask_Reference); - if (tag) + if (hit.tag) { - if (CSVRender::ObjectTag *objectTag = dynamic_cast (tag.get())) + if (CSVRender::ObjectTag *objectTag = dynamic_cast (hit.tag.get())) { // hit an Object, select it CSVRender::Object* object = objectTag->mObject; @@ -106,11 +198,11 @@ void CSVRender::InstanceMode::primarySelectPressed (osg::ref_ptr tag) } } -void CSVRender::InstanceMode::secondarySelectPressed (osg::ref_ptr tag) +void CSVRender::InstanceMode::secondarySelectPressed (const WorldspaceHitResult& hit) { - if (tag) + if (hit.tag) { - if (CSVRender::ObjectTag *objectTag = dynamic_cast (tag.get())) + if (CSVRender::ObjectTag *objectTag = dynamic_cast (hit.tag.get())) { // hit an Object, toggle its selection state CSVRender::Object* object = objectTag->mObject; @@ -120,6 +212,313 @@ void CSVRender::InstanceMode::secondarySelectPressed (osg::ref_ptr tag) } } +bool CSVRender::InstanceMode::primaryEditStartDrag (const QPoint& pos) +{ + if (mDragMode!=DragMode_None || mLocked) + return false; + + WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); + + std::vector > selection = getWorldspaceWidget().getSelection (Mask_Reference); + if (selection.empty()) + { + // Only change selection at the start of drag if no object is already selected + if (hit.tag && CSMPrefs::get()["3D Scene Input"]["context-select"].isTrue()) + { + getWorldspaceWidget().clearSelection (Mask_Reference); + if (CSVRender::ObjectTag *objectTag = dynamic_cast (hit.tag.get())) + { + CSVRender::Object* object = objectTag->mObject; + object->setSelected (true); + } + } + + selection = getWorldspaceWidget().getSelection (Mask_Reference); + if (selection.empty()) + return false; + } + + for (std::vector >::iterator iter (selection.begin()); + iter!=selection.end(); ++iter) + { + if (CSVRender::ObjectTag *objectTag = dynamic_cast (iter->get())) + { + if (mSubModeId == "move") + { + objectTag->mObject->setEdited (Object::Override_Position); + mDragMode = DragMode_Move; + } + else if (mSubModeId == "rotate") + { + objectTag->mObject->setEdited (Object::Override_Rotation); + mDragMode = DragMode_Rotate; + } + else if (mSubModeId == "scale") + { + objectTag->mObject->setEdited (Object::Override_Scale); + mDragMode = DragMode_Scale; + + // Calculate scale factor + std::vector > editedSelection = getWorldspaceWidget().getEdited (Mask_Reference); + osg::Vec3f center = getScreenCoords(getSelectionCenter(editedSelection)); + + int widgetHeight = getWorldspaceWidget().height(); + + float dx = pos.x() - center.x(); + float dy = (widgetHeight - pos.y()) - center.y(); + + mUnitScaleDist = std::sqrt(dx * dx + dy * dy); + } + } + } + + if (CSVRender::ObjectMarkerTag *objectTag = dynamic_cast (hit.tag.get())) + { + mDragAxis = objectTag->mAxis; + } + else + mDragAxis = -1; + + return true; +} + +bool CSVRender::InstanceMode::secondaryEditStartDrag (const QPoint& pos) +{ + if (mLocked) + return false; + + return false; +} + +void CSVRender::InstanceMode::drag (const QPoint& pos, int diffX, int diffY, double speedFactor) +{ + osg::Vec3f offset; + osg::Quat rotation; + + std::vector > selection = getWorldspaceWidget().getEdited (Mask_Reference); + + if (mDragMode == DragMode_Move) + { + osg::Vec3f eye, centre, up; + getWorldspaceWidget().getCamera()->getViewMatrix().getLookAt (eye, centre, up); + + if (diffY) + { + offset += up * diffY * speedFactor; + } + if (diffX) + { + offset += ((centre-eye) ^ up) * diffX * speedFactor; + } + + if (mDragAxis!=-1) + { + for (int i=0; i<3; ++i) + { + if (i!=mDragAxis) + offset[i] = 0; + } + } + } + else if (mDragMode == DragMode_Rotate) + { + osg::Vec3f eye, centre, up; + getWorldspaceWidget().getCamera()->getViewMatrix().getLookAt (eye, centre, up); + + float angle; + osg::Vec3f axis; + + if (mDragAxis == -1) + { + // Free rotate + float rotationFactor = CSMPrefs::get()["3D Scene Input"]["rotate-factor"].toDouble() * speedFactor; + + osg::Quat cameraRotation = getWorldspaceWidget().getCamera()->getInverseViewMatrix().getRotate(); + + osg::Vec3f camForward = centre - eye; + osg::Vec3f screenDir = cameraRotation * osg::Vec3f(diffX, diffY, 0); + screenDir.normalize(); + + angle = std::sqrt(diffX*diffX + diffY*diffY) * rotationFactor; + axis = screenDir ^ camForward; + } + else + { + // Global axis rotation + osg::Vec3f camBack = eye - centre; + + for (int i = 0; i < 3; ++i) + { + if (i == mDragAxis) + axis[i] = 1; + else + axis[i] = 0; + } + + // Flip axis if facing opposite side + if (camBack * axis < 0) + axis *= -1; + + // Convert coordinate system + osg::Vec3f screenCenter = getScreenCoords(getSelectionCenter(selection)); + + int widgetHeight = getWorldspaceWidget().height(); + + float newX = pos.x() - screenCenter.x(); + float newY = (widgetHeight - pos.y()) - screenCenter.y(); + + float oldX = newX - diffX; + float oldY = newY - diffY; // diffY appears to already be flipped + + osg::Vec3f oldVec = osg::Vec3f(oldX, oldY, 0); + oldVec.normalize(); + + osg::Vec3f newVec = osg::Vec3f(newX, newY, 0); + newVec.normalize(); + + // Find angle and axis of rotation + angle = std::acos(oldVec * newVec) * speedFactor; + if (((oldVec ^ newVec) * camBack < 0) ^ (camBack.z() < 0)) + angle *= -1; + } + + rotation = osg::Quat(angle, axis); + } + else if (mDragMode == DragMode_Scale) + { + osg::Vec3f center = getScreenCoords(getSelectionCenter(selection)); + + // Calculate scaling distance/rate + int widgetHeight = getWorldspaceWidget().height(); + + float dx = pos.x() - center.x(); + float dy = (widgetHeight - pos.y()) - center.y(); + + float dist = std::sqrt(dx * dx + dy * dy); + float scale = dist / mUnitScaleDist; + + // Only uniform scaling is currently supported + offset = osg::Vec3f(scale, scale, scale); + } + + // Apply + for (std::vector >::iterator iter (selection.begin()); iter!=selection.end(); ++iter) + { + if (CSVRender::ObjectTag *objectTag = dynamic_cast (iter->get())) + { + if (mDragMode == DragMode_Move) + { + ESM::Position position = objectTag->mObject->getPosition(); + for (int i=0; i<3; ++i) + { + position.pos[i] += offset[i]; + } + + objectTag->mObject->setPosition(position.pos); + } + else if (mDragMode == DragMode_Rotate) + { + ESM::Position position = objectTag->mObject->getPosition(); + + osg::Quat currentRot = eulerToQuat(osg::Vec3f(position.rot[0], position.rot[1], position.rot[2])); + osg::Quat combined = currentRot * rotation; + + osg::Vec3f euler = quatToEuler(combined); + // There appears to be a very rare rounding error that can cause asin to return NaN + if (!euler.isNaN()) + { + position.rot[0] = euler.x(); + position.rot[1] = euler.y(); + position.rot[2] = euler.z(); + } + + objectTag->mObject->setRotation(position.rot); + } + else if (mDragMode == DragMode_Scale) + { + // Reset scale + objectTag->mObject->setEdited(0); + objectTag->mObject->setEdited(Object::Override_Scale); + + float scale = objectTag->mObject->getScale(); + scale *= offset.x(); + + objectTag->mObject->setScale (scale); + } + } + } +} + +void CSVRender::InstanceMode::dragCompleted(const QPoint& pos) +{ + std::vector > selection = + getWorldspaceWidget().getEdited (Mask_Reference); + + QUndoStack& undoStack = getWorldspaceWidget().getDocument().getUndoStack(); + + QString description; + + switch (mDragMode) + { + case DragMode_Move: description = "Move Instances"; break; + case DragMode_Rotate: description = "Rotate Instances"; break; + case DragMode_Scale: description = "Scale Instances"; break; + + case DragMode_None: break; + } + + + CSMWorld::CommandMacro macro (undoStack, description); + + for (std::vector >::iterator iter (selection.begin()); + iter!=selection.end(); ++iter) + { + if (CSVRender::ObjectTag *objectTag = dynamic_cast (iter->get())) + { + objectTag->mObject->apply (macro); + } + } + + mDragMode = DragMode_None; +} + +void CSVRender::InstanceMode::dragAborted() +{ + getWorldspaceWidget().reset (Mask_Reference); + mDragMode = DragMode_None; +} + +void CSVRender::InstanceMode::dragWheel (int diff, double speedFactor) +{ + if (mDragMode==DragMode_Move) + { + osg::Vec3f eye; + osg::Vec3f centre; + osg::Vec3f up; + + getWorldspaceWidget().getCamera()->getViewMatrix().getLookAt (eye, centre, up); + + osg::Vec3f offset = centre - eye; + offset.normalize(); + offset *= diff * speedFactor; + + std::vector > selection = + getWorldspaceWidget().getEdited (Mask_Reference); + + for (std::vector >::iterator iter (selection.begin()); + iter!=selection.end(); ++iter) + { + if (CSVRender::ObjectTag *objectTag = dynamic_cast (iter->get())) + { + ESM::Position position = objectTag->mObject->getPosition(); + for (int i=0; i<3; ++i) + position.pos[i] += offset[i]; + objectTag->mObject->setPosition (position.pos); + } + } + } +} + void CSVRender::InstanceMode::dragEnterEvent (QDragEnterEvent *event) { if (const CSMWorld::TableMimeData* mime = dynamic_cast (event->mimeData())) @@ -141,9 +540,9 @@ void CSVRender::InstanceMode::dropEvent (QDropEvent* event) if (!mime->fromDocument (document)) return; - osg::Vec3f insertPoint = getWorldspaceWidget().getIntersectionPoint (event->pos()); + WorldspaceHitResult hit = getWorldspaceWidget().mousePick (event->pos(), getWorldspaceWidget().getInteractionMask()); - std::string cellId = getWorldspaceWidget().getCellId (insertPoint); + std::string cellId = getWorldspaceWidget().getCellId (hit.worldPos); CSMWorld::IdTree& cellTable = dynamic_cast ( *document.getData().getTableModel (CSMWorld::UniversalId::Type_Cells)); @@ -176,8 +575,6 @@ void CSVRender::InstanceMode::dropEvent (QDropEvent* event) selection.add (CSMWorld::CellCoordinates::fromId (cellId).first); paged->setCellSelection (selection); } - - noCell = false; } } else if (CSVRender::PagedWorldspaceWidget *paged = @@ -220,34 +617,16 @@ void CSVRender::InstanceMode::dropEvent (QDropEvent* event) createCommand->addValue (referencesTable.findColumnIndex ( CSMWorld::Columns::ColumnId_Cell), QString::fromUtf8 (cellId.c_str())); createCommand->addValue (referencesTable.findColumnIndex ( - CSMWorld::Columns::ColumnId_PositionXPos), insertPoint.x()); + CSMWorld::Columns::ColumnId_PositionXPos), hit.worldPos.x()); createCommand->addValue (referencesTable.findColumnIndex ( - CSMWorld::Columns::ColumnId_PositionYPos), insertPoint.y()); + CSMWorld::Columns::ColumnId_PositionYPos), hit.worldPos.y()); createCommand->addValue (referencesTable.findColumnIndex ( - CSMWorld::Columns::ColumnId_PositionZPos), insertPoint.z()); + CSMWorld::Columns::ColumnId_PositionZPos), hit.worldPos.z()); createCommand->addValue (referencesTable.findColumnIndex ( CSMWorld::Columns::ColumnId_ReferenceableId), QString::fromUtf8 (iter->getId().c_str())); - std::auto_ptr incrementCommand; - - if (!noCell) - { - // increase reference count in cell - QModelIndex countIndex = cellTable.getModelIndex (cellId, - cellTable.findColumnIndex (CSMWorld::Columns::ColumnId_RefNumCounter)); - - int count = cellTable.data (countIndex).toInt(); - - incrementCommand.reset ( - new CSMWorld::ModifyCommand (cellTable, countIndex, count+1)); - } - - document.getUndoStack().beginMacro (createCommand->text()); document.getUndoStack().push (createCommand.release()); - if (incrementCommand.get()) - document.getUndoStack().push (incrementCommand.release()); - document.getUndoStack().endMacro(); dropped = true; } @@ -256,3 +635,15 @@ void CSVRender::InstanceMode::dropEvent (QDropEvent* event) event->accept(); } } + +int CSVRender::InstanceMode::getSubMode() const +{ + return mSubMode ? getSubModeFromId (mSubMode->getCurrentId()) : 0; +} + +void CSVRender::InstanceMode::subModeChanged (const std::string& id) +{ + mSubModeId = id; + getWorldspaceWidget().abortDrag(); + getWorldspaceWidget().setSubMode (getSubModeFromId (id), Mask_Reference); +} diff --git a/apps/opencs/view/render/instancemode.hpp b/apps/opencs/view/render/instancemode.hpp index 78836878a..933cae529 100644 --- a/apps/opencs/view/render/instancemode.hpp +++ b/apps/opencs/view/render/instancemode.hpp @@ -1,6 +1,10 @@ #ifndef CSV_RENDER_INSTANCEMODE_H #define CSV_RENDER_INSTANCEMODE_H +#include +#include +#include + #include "editmode.hpp" namespace CSVWidget @@ -10,13 +14,36 @@ namespace CSVWidget namespace CSVRender { + class TagBase; class InstanceSelectionMode; class InstanceMode : public EditMode { Q_OBJECT + + enum DragMode + { + DragMode_None, + DragMode_Move, + DragMode_Rotate, + DragMode_Scale + }; + CSVWidget::SceneToolMode *mSubMode; + std::string mSubModeId; InstanceSelectionMode *mSelectionMode; + DragMode mDragMode; + int mDragAxis; + bool mLocked; + float mUnitScaleDist; + + int getSubModeFromId (const std::string& id) const; + + osg::Vec3f quatToEuler(const osg::Quat& quat) const; + osg::Quat eulerToQuat(const osg::Vec3f& euler) const; + + osg::Vec3f getSelectionCenter(const std::vector >& selection) const; + osg::Vec3f getScreenCoords(const osg::Vec3f& pos); public: @@ -26,17 +53,39 @@ namespace CSVRender virtual void deactivate (CSVWidget::SceneToolbar *toolbar); - virtual void primaryEditPressed (osg::ref_ptr tag); + virtual void setEditLock (bool locked); + + virtual void primaryEditPressed (const WorldspaceHitResult& hit); + + virtual void secondaryEditPressed (const WorldspaceHitResult& hit); - virtual void secondaryEditPressed (osg::ref_ptr tag); + virtual void primarySelectPressed (const WorldspaceHitResult& hit); - virtual void primarySelectPressed (osg::ref_ptr tag); + virtual void secondarySelectPressed (const WorldspaceHitResult& hit); - virtual void secondarySelectPressed (osg::ref_ptr tag); + virtual bool primaryEditStartDrag (const QPoint& pos); + + virtual bool secondaryEditStartDrag (const QPoint& pos); + + virtual void drag (const QPoint& pos, int diffX, int diffY, double speedFactor); + + virtual void dragCompleted(const QPoint& pos); + + /// \note dragAborted will not be called, if the drag is aborted via changing + /// editing mode + virtual void dragAborted(); + + virtual void dragWheel (int diff, double speedFactor); virtual void dragEnterEvent (QDragEnterEvent *event); - virtual void dropEvent (QDropEvent* event); + virtual void dropEvent (QDropEvent *event); + + virtual int getSubMode() const; + + private slots: + + void subModeChanged (const std::string& id); }; } diff --git a/apps/opencs/view/render/instancemovemode.cpp b/apps/opencs/view/render/instancemovemode.cpp new file mode 100644 index 000000000..fe58b581b --- /dev/null +++ b/apps/opencs/view/render/instancemovemode.cpp @@ -0,0 +1,12 @@ + +#include "instancemovemode.hpp" + +CSVRender::InstanceMoveMode::InstanceMoveMode (QWidget *parent) +: ModeButton (QIcon (QPixmap (":placeholder")), + "Move selected instances" + "
        • Use {scene-edit-primary} to move instances around freely
        • " + "
        • Use {scene-edit-secondary} to move instances around within the grid
        • " + "
        " + "Grid move not implemented yet", + parent) +{} diff --git a/apps/opencs/view/render/instancemovemode.hpp b/apps/opencs/view/render/instancemovemode.hpp new file mode 100644 index 000000000..bd0e28dac --- /dev/null +++ b/apps/opencs/view/render/instancemovemode.hpp @@ -0,0 +1,18 @@ +#ifndef CSV_RENDER_INSTANCEMOVEMODE_H +#define CSV_RENDER_INSTANCEMOVEMODE_H + +#include "../widget/modebutton.hpp" + +namespace CSVRender +{ + class InstanceMoveMode : public CSVWidget::ModeButton + { + Q_OBJECT + + public: + + InstanceMoveMode (QWidget *parent = 0); + }; +} + +#endif diff --git a/apps/opencs/view/render/instanceselectionmode.cpp b/apps/opencs/view/render/instanceselectionmode.cpp index 5c3aaa8d1..bf8ede0eb 100644 --- a/apps/opencs/view/render/instanceselectionmode.cpp +++ b/apps/opencs/view/render/instanceselectionmode.cpp @@ -1,4 +1,3 @@ - #include "instanceselectionmode.hpp" #include @@ -10,84 +9,49 @@ #include "worldspacewidget.hpp" #include "object.hpp" -bool CSVRender::InstanceSelectionMode::createContextMenu (QMenu *menu) +namespace CSVRender { - if (menu) + InstanceSelectionMode::InstanceSelectionMode(CSVWidget::SceneToolbar* parent, WorldspaceWidget& worldspaceWidget) + : SelectionMode(parent, worldspaceWidget, Mask_Reference) { - menu->addAction (mSelectAll); - menu->addAction (mDeselectAll); - menu->addAction (mSelectSame); - menu->addAction (mDeleteSelection); - } + mSelectSame = new QAction("Extend selection to instances with same object ID", this); + mDeleteSelection = new QAction("Delete selected instances", this); - return true; -} + connect(mSelectSame, SIGNAL(triggered()), this, SLOT(selectSame())); + connect(mDeleteSelection, SIGNAL(triggered()), this, SLOT(deleteSelection())); + } -CSVRender::InstanceSelectionMode::InstanceSelectionMode (CSVWidget::SceneToolbar *parent, - WorldspaceWidget& worldspaceWidget) -: CSVWidget::SceneToolMode (parent, "Selection Mode"), mWorldspaceWidget (worldspaceWidget) -{ - addButton (":placeholder", "cube-centre", - "Centred cube" - "
        • Drag with primary (make instances the selection) or secondary (invert selection state) select button from the centre of the selection cube outwards
        • " - "
        • The selection cube is aligned to the word space axis
        • " - "
        • If context selection mode is enabled, a drag with primary/secondary edit not starting on an instance will have the same effect
        • " - "
        " - "Not implemented yet"); - addButton (":placeholder", "cube-corner", - "Cube corner to corner" - "
        • Drag with primary (make instances the selection) or secondary (invert selection state) select button from one corner of the selection cube to the opposite corner
        • " - "
        • The selection cube is aligned to the word space axis
        • " - "
        • If context selection mode is enabled, a drag with primary/secondary edit not starting on an instance will have the same effect
        • " - "
        " - "Not implemented yet"); - addButton (":placeholder", "sphere", - "Centred sphere" - "
        • Drag with primary (make instances the selection) or secondary (invert selection state) select button from the centre of the selection sphere outwards
        • " - "
        • If context selection mode is enabled, a drag with primary/secondary edit not starting on an instance will have the same effect
        • " - "
        " - "Not implemented yet"); + bool InstanceSelectionMode::createContextMenu(QMenu* menu) + { + if (menu) + { + SelectionMode::createContextMenu(menu); - mSelectAll = new QAction ("Select all instances", this); - mDeselectAll = new QAction ("Clear selection", this); - mDeleteSelection = new QAction ("Delete selected instances", this); - mSelectSame = new QAction ("Extend selection to instances with same object ID", this); - connect (mSelectAll, SIGNAL (triggered ()), this, SLOT (selectAll())); - connect (mDeselectAll, SIGNAL (triggered ()), this, SLOT (clearSelection())); - connect (mDeleteSelection, SIGNAL (triggered ()), this, SLOT (deleteSelection())); - connect (mSelectSame, SIGNAL (triggered ()), this, SLOT (selectSame())); -} + menu->addAction(mSelectSame); + menu->addAction(mDeleteSelection); + } -void CSVRender::InstanceSelectionMode::selectAll() -{ - mWorldspaceWidget.selectAll (Mask_Reference); -} + return true; + } -void CSVRender::InstanceSelectionMode::clearSelection() -{ - mWorldspaceWidget.clearSelection (Mask_Reference); -} + void InstanceSelectionMode::selectSame() + { + getWorldspaceWidget().selectAllWithSameParentId(Mask_Reference); + } -void CSVRender::InstanceSelectionMode::deleteSelection() -{ - std::vector > selection = - mWorldspaceWidget.getSelection (Mask_Reference); + void InstanceSelectionMode::deleteSelection() + { + std::vector > selection = getWorldspaceWidget().getSelection(Mask_Reference); - CSMWorld::IdTable& referencesTable = - dynamic_cast (*mWorldspaceWidget.getDocument().getData(). - getTableModel (CSMWorld::UniversalId::Type_References)); + CSMWorld::IdTable& referencesTable = dynamic_cast( + *getWorldspaceWidget().getDocument().getData().getTableModel(CSMWorld::UniversalId::Type_References)); - for (std::vector >::iterator iter (selection.begin()); - iter!=selection.end(); ++iter) - { - CSMWorld::DeleteCommand *command = new CSMWorld::DeleteCommand (referencesTable, - static_cast (iter->get())->mObject->getReferenceId()); + for (std::vector >::iterator iter = selection.begin(); iter != selection.end(); ++iter) + { + CSMWorld::DeleteCommand* command = new CSMWorld::DeleteCommand(referencesTable, + static_cast(iter->get())->mObject->getReferenceId()); - mWorldspaceWidget.getDocument().getUndoStack().push (command); + getWorldspaceWidget().getDocument().getUndoStack().push(command); + } } } - -void CSVRender::InstanceSelectionMode::selectSame() -{ - mWorldspaceWidget.selectAllWithSameParentId (Mask_Reference); -} diff --git a/apps/opencs/view/render/instanceselectionmode.hpp b/apps/opencs/view/render/instanceselectionmode.hpp index cac2ca8c2..a590240d9 100644 --- a/apps/opencs/view/render/instanceselectionmode.hpp +++ b/apps/opencs/view/render/instanceselectionmode.hpp @@ -1,23 +1,19 @@ #ifndef CSV_RENDER_INSTANCE_SELECTION_MODE_H #define CSV_RENDER_INSTANCE_SELECTION_MODE_H -#include "../widget/scenetoolmode.hpp" - -class QAction; +#include "selectionmode.hpp" namespace CSVRender { - class WorldspaceWidget; - - class InstanceSelectionMode : public CSVWidget::SceneToolMode + class InstanceSelectionMode : public SelectionMode { Q_OBJECT - WorldspaceWidget& mWorldspaceWidget; - QAction *mSelectAll; - QAction *mDeselectAll; - QAction *mDeleteSelection; - QAction *mSelectSame; + public: + + InstanceSelectionMode(CSVWidget::SceneToolbar* parent, WorldspaceWidget& worldspaceWidget); + + protected: /// Add context menu items to \a menu. /// @@ -25,20 +21,16 @@ namespace CSVRender /// /// \return Have there been any menu items to be added (if menu is 0 and there /// items to be added, the function must return true anyway. - virtual bool createContextMenu (QMenu *menu); + bool createContextMenu(QMenu* menu); - public: + private: - InstanceSelectionMode (CSVWidget::SceneToolbar *parent, WorldspaceWidget& worldspaceWidget); + QAction* mDeleteSelection; + QAction* mSelectSame; private slots: - void selectAll(); - - void clearSelection(); - void deleteSelection(); - void selectSame(); }; } diff --git a/apps/opencs/view/render/object.cpp b/apps/opencs/view/render/object.cpp index 33939625d..eb90e9db3 100644 --- a/apps/opencs/view/render/object.cpp +++ b/apps/opencs/view/render/object.cpp @@ -9,12 +9,18 @@ #include #include #include +#include +#include #include #include "../../model/world/data.hpp" #include "../../model/world/ref.hpp" #include "../../model/world/refidcollection.hpp" +#include "../../model/world/commands.hpp" +#include "../../model/world/universalid.hpp" +#include "../../model/world/commandmacro.hpp" +#include "../../model/world/cellcoordinates.hpp" #include #include @@ -23,6 +29,13 @@ #include "mask.hpp" + +const float CSVRender::Object::MarkerShaftWidth = 30; +const float CSVRender::Object::MarkerShaftBaseLength = 70; +const float CSVRender::Object::MarkerHeadWidth = 50; +const float CSVRender::Object::MarkerHeadLength = 50; + + namespace { @@ -50,6 +63,11 @@ QString CSVRender::ObjectTag::getToolTip (bool hideBasics) const } +CSVRender::ObjectMarkerTag::ObjectMarkerTag (Object* object, int axis) +: ObjectTag (object), mAxis (axis) +{} + + void CSVRender::Object::clear() { } @@ -130,18 +148,20 @@ void CSVRender::Object::adjustTransform() if (mReferenceId.empty()) return; - const CSMWorld::CellRef& reference = getReference(); + ESM::Position position = getPosition(); // position - mBaseNode->setPosition(mForceBaseToZero ? osg::Vec3() : osg::Vec3f(reference.mPos.pos[0], reference.mPos.pos[1], reference.mPos.pos[2])); + mRootNode->setPosition(mForceBaseToZero ? osg::Vec3() : osg::Vec3f(position.pos[0], position.pos[1], position.pos[2])); // orientation - osg::Quat xr (-reference.mPos.rot[0], osg::Vec3f(1,0,0)); - osg::Quat yr (-reference.mPos.rot[1], osg::Vec3f(0,1,0)); - osg::Quat zr (-reference.mPos.rot[2], osg::Vec3f(0,0,1)); + osg::Quat xr (-position.rot[0], osg::Vec3f(1,0,0)); + osg::Quat yr (-position.rot[1], osg::Vec3f(0,1,0)); + osg::Quat zr (-position.rot[2], osg::Vec3f(0,0,1)); mBaseNode->setAttitude(zr*yr*xr); - mBaseNode->setScale(osg::Vec3(reference.mScale, reference.mScale, reference.mScale)); + float scale = getScale(); + + mBaseNode->setScale(osg::Vec3(scale, scale, scale)); } const CSMWorld::CellRef& CSVRender::Object::getReference() const @@ -152,21 +172,249 @@ const CSMWorld::CellRef& CSVRender::Object::getReference() const return mData.getReferences().getRecord (mReferenceId).get(); } +void CSVRender::Object::updateMarker() +{ + for (int i=0; i<3; ++i) + { + if (mMarker[i]) + { + mRootNode->removeChild (mMarker[i]); + mMarker[i] = osg::ref_ptr(); + } + + if (mSelected) + { + if (mSubMode==0) + { + mMarker[i] = makeMoveOrScaleMarker (i); + mMarker[i]->setUserData(new ObjectMarkerTag (this, i)); + + mRootNode->addChild (mMarker[i]); + } + else if (mSubMode==1) + { + mMarker[i] = makeRotateMarker (i); + mMarker[i]->setUserData(new ObjectMarkerTag (this, i)); + + mRootNode->addChild (mMarker[i]); + } + else if (mSubMode==2) + { + mMarker[i] = makeMoveOrScaleMarker (i); + mMarker[i]->setUserData(new ObjectMarkerTag (this, i)); + + mRootNode->addChild (mMarker[i]); + } + } + } +} + +osg::ref_ptr CSVRender::Object::makeMoveOrScaleMarker (int axis) +{ + osg::ref_ptr geometry (new osg::Geometry); + + float shaftLength = MarkerShaftBaseLength + mBaseNode->getBound().radius(); + + // shaft + osg::Vec3Array *vertices = new osg::Vec3Array; + + for (int i=0; i<2; ++i) + { + float length = i ? shaftLength : 0; + + vertices->push_back (getMarkerPosition (-MarkerShaftWidth/2, -MarkerShaftWidth/2, length, axis)); + vertices->push_back (getMarkerPosition (-MarkerShaftWidth/2, MarkerShaftWidth/2, length, axis)); + vertices->push_back (getMarkerPosition (MarkerShaftWidth/2, MarkerShaftWidth/2, length, axis)); + vertices->push_back (getMarkerPosition (MarkerShaftWidth/2, -MarkerShaftWidth/2, length, axis)); + } + + // head backside + vertices->push_back (getMarkerPosition (-MarkerHeadWidth/2, -MarkerHeadWidth/2, shaftLength, axis)); + vertices->push_back (getMarkerPosition (-MarkerHeadWidth/2, MarkerHeadWidth/2, shaftLength, axis)); + vertices->push_back (getMarkerPosition (MarkerHeadWidth/2, MarkerHeadWidth/2, shaftLength, axis)); + vertices->push_back (getMarkerPosition (MarkerHeadWidth/2, -MarkerHeadWidth/2, shaftLength, axis)); + + // head + vertices->push_back (getMarkerPosition (0, 0, shaftLength+MarkerHeadLength, axis)); + + geometry->setVertexArray (vertices); + + osg::DrawElementsUShort *primitives = new osg::DrawElementsUShort (osg::PrimitiveSet::TRIANGLES, 0); + + // shaft + for (int i=0; i<4; ++i) + { + int i2 = i==3 ? 0 : i+1; + primitives->push_back (i); + primitives->push_back (4+i); + primitives->push_back (i2); + + primitives->push_back (4+i); + primitives->push_back (4+i2); + primitives->push_back (i2); + } + + // cap + primitives->push_back (0); + primitives->push_back (1); + primitives->push_back (2); + + primitives->push_back (2); + primitives->push_back (3); + primitives->push_back (0); + + // head, backside + primitives->push_back (0+8); + primitives->push_back (1+8); + primitives->push_back (2+8); + + primitives->push_back (2+8); + primitives->push_back (3+8); + primitives->push_back (0+8); + + for (int i=0; i<4; ++i) + { + primitives->push_back (12); + primitives->push_back (8+(i==3 ? 0 : i+1)); + primitives->push_back (8+i); + } + + geometry->addPrimitiveSet (primitives); + + osg::Vec4Array *colours = new osg::Vec4Array; + + for (int i=0; i<8; ++i) + colours->push_back (osg::Vec4f (axis==0 ? 1.0f : 0.2f, axis==1 ? 1.0f : 0.2f, + axis==2 ? 1.0f : 0.2f, 1.0f)); + + for (int i=8; i<8+4+1; ++i) + colours->push_back (osg::Vec4f (axis==0 ? 1.0f : 0.0f, axis==1 ? 1.0f : 0.0f, + axis==2 ? 1.0f : 0.0f, 1.0f)); + + geometry->setColorArray (colours, osg::Array::BIND_PER_VERTEX); + + geometry->getOrCreateStateSet()->setMode (GL_LIGHTING, osg::StateAttribute::OFF); + + osg::ref_ptr geode (new osg::Geode); + geode->addDrawable (geometry); + + return geode; +} + +osg::ref_ptr CSVRender::Object::makeRotateMarker (int axis) +{ + const float Pi = 3.14159265f; + + const float InnerRadius = mBaseNode->getBound().radius(); + const float OuterRadius = InnerRadius + MarkerShaftWidth; + + const float SegmentDistance = 100.f; + const size_t SegmentCount = std::min(64, std::max(8, (int)(OuterRadius * 2 * Pi / SegmentDistance))); + const size_t VerticesPerSegment = 4; + const size_t IndicesPerSegment = 24; + + const size_t VertexCount = SegmentCount * VerticesPerSegment; + const size_t IndexCount = SegmentCount * IndicesPerSegment; + + const float Angle = 2 * Pi / SegmentCount; + + const unsigned short IndexPattern[IndicesPerSegment] = + { + 0, 4, 5, 0, 5, 1, + 2, 6, 4, 2, 4, 0, + 3, 7, 6, 3, 6, 2, + 1, 5, 7, 1, 7, 3 + }; + + + osg::ref_ptr geometry = new osg::Geometry(); + + osg::ref_ptr vertices = new osg::Vec3Array(VertexCount); + osg::ref_ptr colors = new osg::Vec4Array(1); + osg::ref_ptr primitives = new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLES, + IndexCount); + + for (size_t i = 0; i < SegmentCount; ++i) + { + size_t index = i * VerticesPerSegment; + + float innerX = InnerRadius * std::cos(i * Angle); + float innerY = InnerRadius * std::sin(i * Angle); + + float outerX = OuterRadius * std::cos(i * Angle); + float outerY = OuterRadius * std::sin(i * Angle); + + vertices->at(index++) = getMarkerPosition(innerX, innerY, MarkerShaftWidth / 2, axis); + vertices->at(index++) = getMarkerPosition(innerX, innerY, -MarkerShaftWidth / 2, axis); + vertices->at(index++) = getMarkerPosition(outerX, outerY, MarkerShaftWidth / 2, axis); + vertices->at(index++) = getMarkerPosition(outerX, outerY, -MarkerShaftWidth / 2, axis); + } + + colors->at(0) = osg::Vec4f (axis==0 ? 1.0f : 0.2f, axis==1 ? 1.0f : 0.2f, axis==2 ? 1.0f : 0.2f, 1.0f); + + for (size_t i = 0; i < SegmentCount; ++i) + { + size_t indices[IndicesPerSegment]; + for (size_t j = 0; j < IndicesPerSegment; ++j) + { + indices[j] = i * VerticesPerSegment + j; + + if (indices[j] >= VertexCount) + indices[j] -= VertexCount; + } + + size_t offset = i * IndicesPerSegment; + for (size_t j = 0; j < IndicesPerSegment; ++j) + { + primitives->setElement(offset++, indices[IndexPattern[j]]); + } + } + + geometry->setVertexArray(vertices); + geometry->setColorArray(colors, osg::Array::BIND_OVERALL); + geometry->addPrimitiveSet(primitives); + + geometry->getOrCreateStateSet()->setMode (GL_LIGHTING, osg::StateAttribute::OFF); + + osg::ref_ptr geode = new osg::Geode(); + geode->addDrawable (geometry); + + return geode; +} + +osg::Vec3f CSVRender::Object::getMarkerPosition (float x, float y, float z, int axis) +{ + switch (axis) + { + case 2: return osg::Vec3f (x, y, z); + case 0: return osg::Vec3f (z, x, y); + case 1: return osg::Vec3f (y, z, x); + + default: + + throw std::logic_error ("invalid axis for marker geometry"); + } +} + CSVRender::Object::Object (CSMWorld::Data& data, osg::Group* parentNode, const std::string& id, bool referenceable, bool forceBaseToZero) -: mData (data), mBaseNode(0), mSelected(false), mParentNode(parentNode), mResourceSystem(data.getResourceSystem().get()), mForceBaseToZero (forceBaseToZero) +: mData (data), mBaseNode(0), mSelected(false), mParentNode(parentNode), mResourceSystem(data.getResourceSystem().get()), mForceBaseToZero (forceBaseToZero), + mScaleOverride (1), mOverrideFlags (0), mSubMode (-1) { + mRootNode = new osg::PositionAttitudeTransform; + mBaseNode = new osg::PositionAttitudeTransform; mBaseNode->addCullCallback(new SceneUtil::LightListCallback); mOutline = new osgFX::Scribe; - mOutline->addChild(mBaseNode); mBaseNode->setUserData(new ObjectTag(this)); - parentNode->addChild(mBaseNode); + mRootNode->addChild (mBaseNode); + + parentNode->addChild (mRootNode); - mBaseNode->setNodeMask(Mask_Reference); + mRootNode->setNodeMask(Mask_Reference); if (referenceable) { @@ -180,26 +428,32 @@ CSVRender::Object::Object (CSMWorld::Data& data, osg::Group* parentNode, adjustTransform(); update(); + updateMarker(); } CSVRender::Object::~Object() { clear(); - mParentNode->removeChild(mBaseNode); - mParentNode->removeChild(mOutline); + mParentNode->removeChild (mRootNode); } void CSVRender::Object::setSelected(bool selected) { mSelected = selected; - mParentNode->removeChild(mOutline); - mParentNode->removeChild(mBaseNode); + mOutline->removeChild(mBaseNode); + mRootNode->removeChild(mOutline); + mRootNode->removeChild(mBaseNode); if (selected) - mParentNode->addChild(mOutline); + { + mOutline->addChild(mBaseNode); + mRootNode->addChild(mOutline); + } else - mParentNode->addChild(mBaseNode); + mRootNode->addChild(mBaseNode); + + updateMarker(); } bool CSVRender::Object::getSelected() const @@ -218,6 +472,7 @@ bool CSVRender::Object::referenceableDataChanged (const QModelIndex& topLeft, { adjustTransform(); update(); + updateMarker(); return true; } @@ -268,6 +523,7 @@ bool CSVRender::Object::referenceDataChanged (const QModelIndex& topLeft, references.getData (index, columnIndex).toString().toUtf8().constData(); update(); + updateMarker(); } return true; @@ -290,3 +546,151 @@ osg::ref_ptr CSVRender::Object::getTag() const { return static_cast (mBaseNode->getUserData()); } + +bool CSVRender::Object::isEdited() const +{ + return mOverrideFlags; +} + +void CSVRender::Object::setEdited (int flags) +{ + bool discard = mOverrideFlags & ~flags; + int added = flags & ~mOverrideFlags; + + mOverrideFlags = flags; + + if (added & Override_Position) + for (int i=0; i<3; ++i) + mPositionOverride.pos[i] = getReference().mPos.pos[i]; + + if (added & Override_Rotation) + for (int i=0; i<3; ++i) + mPositionOverride.rot[i] = getReference().mPos.rot[i]; + + if (added & Override_Scale) + mScaleOverride = getReference().mScale; + + if (discard) + adjustTransform(); +} + +ESM::Position CSVRender::Object::getPosition() const +{ + ESM::Position position = getReference().mPos; + + if (mOverrideFlags & Override_Position) + for (int i=0; i<3; ++i) + position.pos[i] = mPositionOverride.pos[i]; + + if (mOverrideFlags & Override_Rotation) + for (int i=0; i<3; ++i) + position.rot[i] = mPositionOverride.rot[i]; + + return position; +} + +float CSVRender::Object::getScale() const +{ + return (mOverrideFlags & Override_Scale) ? mScaleOverride : getReference().mScale; +} + +void CSVRender::Object::setPosition (const float position[3]) +{ + mOverrideFlags |= Override_Position; + + for (int i=0; i<3; ++i) + mPositionOverride.pos[i] = position[i]; + + adjustTransform(); +} + +void CSVRender::Object::setRotation (const float rotation[3]) +{ + mOverrideFlags |= Override_Rotation; + + for (int i=0; i<3; ++i) + mPositionOverride.rot[i] = rotation[i]; + + adjustTransform(); +} + +void CSVRender::Object::setScale (float scale) +{ + mOverrideFlags |= Override_Scale; + + mScaleOverride = scale; + + adjustTransform(); +} + +void CSVRender::Object::apply (CSMWorld::CommandMacro& commands) +{ + const CSMWorld::RefCollection& collection = mData.getReferences(); + QAbstractItemModel *model = mData.getTableModel (CSMWorld::UniversalId::Type_References); + + int recordIndex = collection.getIndex (mReferenceId); + + if (mOverrideFlags & Override_Position) + { + for (int i=0; i<3; ++i) + { + int column = collection.findColumnIndex (static_cast ( + CSMWorld::Columns::ColumnId_PositionXPos+i)); + + commands.push (new CSMWorld::ModifyCommand (*model, + model->index (recordIndex, column), mPositionOverride.pos[i])); + } + + int column = collection.findColumnIndex (static_cast ( + CSMWorld::Columns::ColumnId_Cell)); + + if (CSMWorld::CellCoordinates::isExteriorCell(collection.getRecord (recordIndex).get().mCell)) + { + std::pair cellIndex = collection.getRecord (recordIndex).get().getCellIndex(); + + /// \todo figure out worldspace (not important until multiple worldspaces are supported) + std::string cellId = CSMWorld::CellCoordinates (cellIndex).getId (""); + + commands.push (new CSMWorld::ModifyCommand (*model, + model->index (recordIndex, column), QString::fromUtf8 (cellId.c_str()))); + } + } + + if (mOverrideFlags & Override_Rotation) + { + for (int i=0; i<3; ++i) + { + int column = collection.findColumnIndex (static_cast ( + CSMWorld::Columns::ColumnId_PositionXRot+i)); + + commands.push (new CSMWorld::ModifyCommand (*model, + model->index (recordIndex, column), mPositionOverride.rot[i])); + } + } + + if (mOverrideFlags & Override_Scale) + { + int column = collection.findColumnIndex (CSMWorld::Columns::ColumnId_Scale); + + commands.push (new CSMWorld::ModifyCommand (*model, + model->index (recordIndex, column), mScaleOverride)); + } + + mOverrideFlags = 0; +} + +void CSVRender::Object::setSubMode (int subMode) +{ + if (subMode!=mSubMode) + { + mSubMode = subMode; + updateMarker(); + } +} + +void CSVRender::Object::reset() +{ + mOverrideFlags = 0; + adjustTransform(); + updateMarker(); +} diff --git a/apps/opencs/view/render/object.hpp b/apps/opencs/view/render/object.hpp index 4a89fe201..d3cba6c55 100644 --- a/apps/opencs/view/render/object.hpp +++ b/apps/opencs/view/render/object.hpp @@ -8,14 +8,19 @@ #include #include +#include + #include "tagbase.hpp" class QModelIndex; +class QUndoStack; namespace osg { class PositionAttitudeTransform; class Group; + class Node; + class Geode; } namespace osgFX @@ -32,6 +37,7 @@ namespace CSMWorld { class Data; struct CellRef; + class CommandMacro; } namespace CSVRender @@ -50,18 +56,48 @@ namespace CSVRender virtual QString getToolTip (bool hideBasics) const; }; + class ObjectMarkerTag : public ObjectTag + { + public: + + ObjectMarkerTag (Object* object, int axis); + + int mAxis; + }; class Object { - const CSMWorld::Data& mData; + public: + + enum OverrideFlags + { + Override_Position = 1, + Override_Rotation = 2, + Override_Scale = 4 + }; + + private: + + static const float MarkerShaftWidth; + static const float MarkerShaftBaseLength; + static const float MarkerHeadWidth; + static const float MarkerHeadLength; + + CSMWorld::Data& mData; std::string mReferenceId; std::string mReferenceableId; + osg::ref_ptr mRootNode; osg::ref_ptr mBaseNode; osg::ref_ptr mOutline; bool mSelected; osg::Group* mParentNode; Resource::ResourceSystem* mResourceSystem; bool mForceBaseToZero; + ESM::Position mPositionOverride; + float mScaleOverride; + int mOverrideFlags; + osg::ref_ptr mMarker[3]; + int mSubMode; /// Not implemented Object (const Object&); @@ -82,6 +118,13 @@ namespace CSVRender /// Throws an exception if *this was constructed with referenceable const CSMWorld::CellRef& getReference() const; + void updateMarker(); + + osg::ref_ptr makeMoveOrScaleMarker (int axis); + osg::ref_ptr makeRotateMarker (int axis); + + osg::Vec3f getMarkerPosition (float x, float y, float z, int axis); + public: Object (CSMWorld::Data& data, osg::Group *cellNode, @@ -116,6 +159,33 @@ namespace CSVRender std::string getReferenceableId() const; osg::ref_ptr getTag() const; + + /// Is there currently an editing operation running on this object? + bool isEdited() const; + + void setEdited (int flags); + + ESM::Position getPosition() const; + + float getScale() const; + + /// Set override position. + void setPosition (const float position[3]); + + /// Set override rotation + void setRotation (const float rotation[3]); + + /// Set override scale + void setScale (float scale); + + /// Apply override changes via command and end edit mode + void apply (CSMWorld::CommandMacro& commands); + + void setSubMode (int subMode); + + /// Erase all overrides and restore the visual representation of the object to its + /// true state. + void reset(); }; } diff --git a/apps/opencs/view/render/orbitcameramode.cpp b/apps/opencs/view/render/orbitcameramode.cpp new file mode 100644 index 000000000..ba25beaba --- /dev/null +++ b/apps/opencs/view/render/orbitcameramode.cpp @@ -0,0 +1,56 @@ +#include "orbitcameramode.hpp" + +#include + +#include "../../model/prefs/shortcut.hpp" +#include "../../model/prefs/shortcuteventhandler.hpp" + +#include "worldspacewidget.hpp" + +namespace CSVRender +{ + OrbitCameraMode::OrbitCameraMode(WorldspaceWidget* worldspaceWidget, const QIcon& icon, const QString& tooltip, + QWidget* parent) + : ModeButton(icon, tooltip, parent) + , mWorldspaceWidget(worldspaceWidget) + , mCenterOnSelection(0) + { + mCenterShortcut = new CSMPrefs::Shortcut("orbit-center-selection", worldspaceWidget); + mCenterShortcut->enable(false); + connect(mCenterShortcut, SIGNAL(activated()), this, SLOT(centerSelection())); + } + + OrbitCameraMode::~OrbitCameraMode() + { + } + + void OrbitCameraMode::activate(CSVWidget::SceneToolbar* toolbar) + { + mCenterOnSelection = new QAction("Center on selected object", this); + mCenterShortcut->associateAction(mCenterOnSelection); + connect(mCenterOnSelection, SIGNAL(triggered()), this, SLOT(centerSelection())); + + mCenterShortcut->enable(true); + } + + void OrbitCameraMode::deactivate(CSVWidget::SceneToolbar* toolbar) + { + mCenterShortcut->associateAction(0); + mCenterShortcut->enable(false); + } + + bool OrbitCameraMode::createContextMenu(QMenu* menu) + { + if (menu) + { + menu->addAction(mCenterOnSelection); + } + + return true; + } + + void OrbitCameraMode::centerSelection() + { + mWorldspaceWidget->centerOrbitCameraOnSelection(); + } +} diff --git a/apps/opencs/view/render/orbitcameramode.hpp b/apps/opencs/view/render/orbitcameramode.hpp new file mode 100644 index 000000000..312cd1756 --- /dev/null +++ b/apps/opencs/view/render/orbitcameramode.hpp @@ -0,0 +1,43 @@ +#ifndef CSV_RENDER_ORBITCAMERAPICKMODE_H +#define CSV_RENDER_ORBITCAMERAPICKMODE_H + +#include + +#include "../widget/modebutton.hpp" + +namespace CSMPrefs +{ + class Shortcut; +} + +namespace CSVRender +{ + class WorldspaceWidget; + + class OrbitCameraMode : public CSVWidget::ModeButton + { + Q_OBJECT + + public: + + OrbitCameraMode(WorldspaceWidget* worldspaceWidget, const QIcon& icon, const QString& tooltip = "", + QWidget* parent = 0); + ~OrbitCameraMode(); + + virtual void activate(CSVWidget::SceneToolbar* toolbar); + virtual void deactivate(CSVWidget::SceneToolbar* toolbar); + virtual bool createContextMenu(QMenu* menu); + + private: + + WorldspaceWidget* mWorldspaceWidget; + QAction* mCenterOnSelection; + CSMPrefs::Shortcut* mCenterShortcut; + + private slots: + + void centerSelection(); + }; +} + +#endif diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index d71446d0b..901dadc85 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -6,24 +6,24 @@ #include #include -#include - #include +#include "../../model/prefs/shortcut.hpp" + #include "../../model/world/tablemimedata.hpp" #include "../../model/world/idtable.hpp" -#include "../widget/scenetooltoggle.hpp" +#include "../widget/scenetooltoggle2.hpp" #include "../widget/scenetoolmode.hpp" #include "../widget/scenetooltoggle2.hpp" #include "editmode.hpp" #include "mask.hpp" +#include "cameracontroller.hpp" bool CSVRender::PagedWorldspaceWidget::adjustCells() { bool modified = false; - bool wasEmpty = mCells.empty(); const CSMWorld::IdCollection& cells = mDocument.getData().getCells(); @@ -114,11 +114,6 @@ bool CSVRender::PagedWorldspaceWidget::adjustCells() } } - /// \todo do not overwrite manipulator object - /// \todo move code to useViewHint function - if (modified && wasEmpty) - mView->setCameraManipulator(new osgGA::TrackballManipulator); - return modified; } @@ -150,74 +145,71 @@ void CSVRender::PagedWorldspaceWidget::addEditModeSelectorButtons ( "terrain-move"); } -void CSVRender::PagedWorldspaceWidget::handleMouseClick (osg::ref_ptr tag, const std::string& button, bool shift) +void CSVRender::PagedWorldspaceWidget::handleInteractionPress (const WorldspaceHitResult& hit, InteractionType type) { - if (tag && tag->getMask()==Mask_CellArrow) + if (hit.tag && hit.tag->getMask()==Mask_CellArrow) { - if (button=="p-edit" || button=="s-edit") + if (CellArrowTag *cellArrowTag = dynamic_cast (hit.tag.get())) { - if (CellArrowTag *cellArrowTag = - dynamic_cast (tag.get())) - { - CellArrow *arrow = cellArrowTag->getCellArrow(); + CellArrow *arrow = cellArrowTag->getCellArrow(); - CSMWorld::CellCoordinates coordinates = arrow->getCoordinates(); + CSMWorld::CellCoordinates coordinates = arrow->getCoordinates(); - CellArrow::Direction direction = arrow->getDirection(); + CellArrow::Direction direction = arrow->getDirection(); - int x = 0; - int y = 0; + int x = 0; + int y = 0; - switch (direction) - { - case CellArrow::Direction_North: y = 1; break; - case CellArrow::Direction_West: x = -1; break; - case CellArrow::Direction_South: y = -1; break; - case CellArrow::Direction_East: x = 1; break; - } + switch (direction) + { + case CellArrow::Direction_North: y = 1; break; + case CellArrow::Direction_West: x = -1; break; + case CellArrow::Direction_South: y = -1; break; + case CellArrow::Direction_East: x = 1; break; + } - bool modified = false; + bool modified = false; - if (shift) - { - if (button=="p-edit") - addCellSelection (x, y); - else - moveCellSelection (x, y); + if (type == InteractionType_PrimarySelect) + { + addCellSelection (x, y); + modified = true; + } + else if (type == InteractionType_SecondarySelect) + { + moveCellSelection (x, y); + modified = true; + } + else // Primary/SecondaryEdit + { + CSMWorld::CellCoordinates newCoordinates = coordinates.move (x, y); + if (mCells.find (newCoordinates)==mCells.end()) + { + addCellToScene (newCoordinates); + mSelection.add (newCoordinates); modified = true; } - else - { - CSMWorld::CellCoordinates newCoordinates = coordinates.move (x, y); - if (mCells.find (newCoordinates)==mCells.end()) + if (type == InteractionType_SecondaryEdit) + { + if (mCells.find (coordinates)!=mCells.end()) { - addCellToScene (newCoordinates); - mSelection.add (newCoordinates); + removeCellFromScene (coordinates); + mSelection.remove (coordinates); modified = true; } - - if (button=="s-edit") - { - if (mCells.find (coordinates)!=mCells.end()) - { - removeCellFromScene (coordinates); - mSelection.remove (coordinates); - modified = true; - } - } } + } - if (modified) - adjustCells(); + if (modified) + adjustCells(); - return; - } + return; } } - WorldspaceWidget::handleMouseClick (tag, button, shift); + WorldspaceWidget::handleInteractionPress (hit, type); } void CSVRender::PagedWorldspaceWidget::referenceableDataChanged (const QModelIndex& topLeft, @@ -283,6 +275,82 @@ void CSVRender::PagedWorldspaceWidget::referenceAdded (const QModelIndex& parent flagAsModified(); } +void CSVRender::PagedWorldspaceWidget::pathgridDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) +{ + const CSMWorld::SubCellCollection& pathgrids = mDocument.getData().getPathgrids(); + + int rowStart = -1; + int rowEnd = -1; + + if (topLeft.parent().isValid()) + { + rowStart = topLeft.parent().row(); + rowEnd = bottomRight.parent().row(); + } + else + { + rowStart = topLeft.row(); + rowEnd = bottomRight.row(); + } + + for (int row = rowStart; row <= rowEnd; ++row) + { + const CSMWorld::Pathgrid& pathgrid = pathgrids.getRecord(row).get(); + CSMWorld::CellCoordinates coords = CSMWorld::CellCoordinates(pathgrid.mData.mX, pathgrid.mData.mY); + + std::map::iterator searchResult = mCells.find(coords); + if (searchResult != mCells.end()) + { + searchResult->second->pathgridModified(); + flagAsModified(); + } + } +} + +void CSVRender::PagedWorldspaceWidget::pathgridAboutToBeRemoved (const QModelIndex& parent, int start, int end) +{ + const CSMWorld::SubCellCollection& pathgrids = mDocument.getData().getPathgrids(); + + if (!parent.isValid()) + { + // Pathgrid going to be deleted + for (int row = start; row <= end; ++row) + { + const CSMWorld::Pathgrid& pathgrid = pathgrids.getRecord(row).get(); + CSMWorld::CellCoordinates coords = CSMWorld::CellCoordinates(pathgrid.mData.mX, pathgrid.mData.mY); + + std::map::iterator searchResult = mCells.find(coords); + if (searchResult != mCells.end()) + { + searchResult->second->pathgridRemoved(); + flagAsModified(); + } + } + } +} + +void CSVRender::PagedWorldspaceWidget::pathgridAdded(const QModelIndex& parent, int start, int end) +{ + const CSMWorld::SubCellCollection& pathgrids = mDocument.getData().getPathgrids(); + + if (!parent.isValid()) + { + for (int row = start; row <= end; ++row) + { + const CSMWorld::Pathgrid& pathgrid = pathgrids.getRecord(row).get(); + CSMWorld::CellCoordinates coords = CSMWorld::CellCoordinates(pathgrid.mData.mX, pathgrid.mData.mY); + + std::map::iterator searchResult = mCells.find(coords); + if (searchResult != mCells.end()) + { + searchResult->second->pathgridModified(); + flagAsModified(); + } + } + } +} + + std::string CSVRender::PagedWorldspaceWidget::getStartupInstruction() { osg::Vec3d eye, center, up; @@ -309,10 +377,13 @@ void CSVRender::PagedWorldspaceWidget::addCellToScene ( bool deleted = index==-1 || cells.getRecord (index).mState==CSMWorld::RecordBase::State_Deleted; - Cell *cell = new Cell (mDocument.getData(), mRootNode, coordinates.getId (mWorldspace), - deleted); + std::auto_ptr cell ( + new Cell (mDocument.getData(), mRootNode, coordinates.getId (mWorldspace), + deleted)); + EditMode *editMode = getEditMode(); + cell->setSubMode (editMode->getSubMode(), editMode->getInteractionMask()); - mCells.insert (std::make_pair (coordinates, cell)); + mCells.insert (std::make_pair (coordinates, cell.release())); } void CSVRender::PagedWorldspaceWidget::removeCellFromScene ( @@ -365,6 +436,27 @@ void CSVRender::PagedWorldspaceWidget::moveCellSelection (int x, int y) mSelection = newSelection; } +void CSVRender::PagedWorldspaceWidget::addCellToSceneFromCamera (int offsetX, int offsetY) +{ + const int CellSize = 8192; + + osg::Vec3f eye, center, up; + getCamera()->getViewMatrixAsLookAt(eye, center, up); + + int cellX = (int)std::floor(center.x() / CellSize) + offsetX; + int cellY = (int)std::floor(center.y() / CellSize) + offsetY; + + CSMWorld::CellCoordinates cellCoordinates(cellX, cellY); + + if (!mSelection.has(cellCoordinates)) + { + addCellToScene(cellCoordinates); + mSelection.add(cellCoordinates); + + adjustCells(); + } +} + CSVRender::PagedWorldspaceWidget::PagedWorldspaceWidget (QWidget* parent, CSMDoc::Document& document) : WorldspaceWidget (document, parent), mDocument (document), mWorldspace ("std::default"), mControlElements(NULL), mDisplayCellCoord(true) @@ -378,6 +470,22 @@ CSVRender::PagedWorldspaceWidget::PagedWorldspaceWidget (QWidget* parent, CSMDoc this, SLOT (cellRemoved (const QModelIndex&, int, int))); connect (cells, SIGNAL (rowsInserted (const QModelIndex&, int, int)), this, SLOT (cellAdded (const QModelIndex&, int, int))); + + // Shortcuts + CSMPrefs::Shortcut* loadCameraCellShortcut = new CSMPrefs::Shortcut("scene-load-cam-cell", this); + connect(loadCameraCellShortcut, SIGNAL(activated()), this, SLOT(loadCameraCell())); + + CSMPrefs::Shortcut* loadCameraEastCellShortcut = new CSMPrefs::Shortcut("scene-load-cam-eastcell", this); + connect(loadCameraEastCellShortcut, SIGNAL(activated()), this, SLOT(loadEastCell())); + + CSMPrefs::Shortcut* loadCameraNorthCellShortcut = new CSMPrefs::Shortcut("scene-load-cam-northcell", this); + connect(loadCameraNorthCellShortcut, SIGNAL(activated()), this, SLOT(loadNorthCell())); + + CSMPrefs::Shortcut* loadCameraWestCellShortcut = new CSMPrefs::Shortcut("scene-load-cam-westcell", this); + connect(loadCameraWestCellShortcut, SIGNAL(activated()), this, SLOT(loadWestCell())); + + CSMPrefs::Shortcut* loadCameraSouthCellShortcut = new CSMPrefs::Shortcut("scene-load-cam-southcell", this); + connect(loadCameraSouthCellShortcut, SIGNAL(activated()), this, SLOT(loadSouthCell())); } CSVRender::PagedWorldspaceWidget::~PagedWorldspaceWidget() @@ -405,12 +513,15 @@ void CSVRender::PagedWorldspaceWidget::useViewHint (const std::string& hint) { char ignore1; // : or ; char ignore2; // # + // Current coordinate int x, y; + // Loop through all the coordinates to add them to selection while (stream >> ignore1 >> ignore2 >> x >> y) selection.add (CSMWorld::CellCoordinates (x, y)); - - /// \todo adjust camera position + + // Mark that camera needs setup + mCamPositionSet=false; } } else if (hint[0]=='r') @@ -447,18 +558,18 @@ std::pair< int, int > CSVRender::PagedWorldspaceWidget::getCoordinatesFromId (co } bool CSVRender::PagedWorldspaceWidget::handleDrop ( - const std::vector< CSMWorld::UniversalId >& data, DropType type) + const std::vector< CSMWorld::UniversalId >& universalIdData, DropType type) { - if (WorldspaceWidget::handleDrop (data, type)) + if (WorldspaceWidget::handleDrop (universalIdData, type)) return true; if (type!=Type_CellsExterior) return false; bool selectionChanged = false; - for (unsigned i = 0; i < data.size(); ++i) + for (unsigned i = 0; i < universalIdData.size(); ++i) { - std::pair coordinates(getCoordinatesFromId(data[i].getId())); + std::pair coordinates(getCoordinatesFromId(universalIdData[i].getId())); if (mSelection.add(CSMWorld::CellCoordinates(coordinates.first, coordinates.second))) { selectionChanged = true; @@ -509,6 +620,15 @@ void CSVRender::PagedWorldspaceWidget::clearSelection (int elementMask) flagAsModified(); } +void CSVRender::PagedWorldspaceWidget::invertSelection (int elementMask) +{ + for (std::map::iterator iter = mCells.begin(); + iter!=mCells.end(); ++iter) + iter->second->setSelection (elementMask, Cell::Selection_Invert); + + flagAsModified(); +} + void CSVRender::PagedWorldspaceWidget::selectAll (int elementMask) { for (std::map::iterator iter = mCells.begin(); @@ -538,6 +658,21 @@ std::string CSVRender::PagedWorldspaceWidget::getCellId (const osg::Vec3f& point return cellCoordinates.getId (mWorldspace); } +CSVRender::Cell* CSVRender::PagedWorldspaceWidget::getCell(const osg::Vec3d& point) const +{ + const int cellSize = 8192; + + CSMWorld::CellCoordinates coords( + static_cast (std::floor (point.x()/cellSize)), + static_cast (std::floor (point.y()/cellSize))); + + std::map::const_iterator searchResult = mCells.find(coords); + if (searchResult != mCells.end()) + return searchResult->second; + else + return 0; +} + std::vector > CSVRender::PagedWorldspaceWidget::getSelection ( unsigned int elementMask) const { @@ -555,16 +690,46 @@ std::vector > CSVRender::PagedWorldspaceWidget: return result; } -CSVWidget::SceneToolToggle *CSVRender::PagedWorldspaceWidget::makeControlVisibilitySelector ( +std::vector > CSVRender::PagedWorldspaceWidget::getEdited ( + unsigned int elementMask) const +{ + std::vector > result; + + for (std::map::const_iterator iter = mCells.begin(); + iter!=mCells.end(); ++iter) + { + std::vector > cellResult = + iter->second->getEdited (elementMask); + + result.insert (result.end(), cellResult.begin(), cellResult.end()); + } + + return result; +} + +void CSVRender::PagedWorldspaceWidget::setSubMode (int subMode, unsigned int elementMask) +{ + for (std::map::const_iterator iter = mCells.begin(); + iter!=mCells.end(); ++iter) + iter->second->setSubMode (subMode, elementMask); +} + +void CSVRender::PagedWorldspaceWidget::reset (unsigned int elementMask) +{ + for (std::map::const_iterator iter = mCells.begin(); + iter!=mCells.end(); ++iter) + iter->second->reset (elementMask); +} + +CSVWidget::SceneToolToggle2 *CSVRender::PagedWorldspaceWidget::makeControlVisibilitySelector ( CSVWidget::SceneToolbar *parent) { - mControlElements = new CSVWidget::SceneToolToggle (parent, - "Controls & Guides Visibility", ":placeholder"); + mControlElements = new CSVWidget::SceneToolToggle2 (parent, + "Controls & Guides Visibility", ":scenetoolbar/scene-view-marker-c", ":scenetoolbar/scene-view-marker-"); - mControlElements->addButton (":placeholder", Mask_CellMarker, ":placeholder", - "Cell marker"); - mControlElements->addButton (":placeholder", Mask_CellArrow, ":placeholder", "Cell arrows"); - mControlElements->addButton (":placeholder", Mask_CellBorder, ":placeholder", "Cell border"); + mControlElements->addButton (1, Mask_CellMarker, "Cell Marker"); + mControlElements->addButton (2, Mask_CellArrow, "Cell Arrows"); + mControlElements->addButton (4, Mask_CellBorder, "Cell Border"); mControlElements->setSelectionMask (0xffffffff); @@ -596,3 +761,28 @@ void CSVRender::PagedWorldspaceWidget::cellAdded (const QModelIndex& index, int if (adjustCells()) flagAsModified(); } + +void CSVRender::PagedWorldspaceWidget::loadCameraCell() +{ + addCellToSceneFromCamera(0, 0); +} + +void CSVRender::PagedWorldspaceWidget::loadEastCell() +{ + addCellToSceneFromCamera(1, 0); +} + +void CSVRender::PagedWorldspaceWidget::loadNorthCell() +{ + addCellToSceneFromCamera(0, 1); +} + +void CSVRender::PagedWorldspaceWidget::loadWestCell() +{ + addCellToSceneFromCamera(-1, 0); +} + +void CSVRender::PagedWorldspaceWidget::loadSouthCell() +{ + addCellToSceneFromCamera(0, -1); +} diff --git a/apps/opencs/view/render/pagedworldspacewidget.hpp b/apps/opencs/view/render/pagedworldspacewidget.hpp index 419a2a46a..0663d3424 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.hpp +++ b/apps/opencs/view/render/pagedworldspacewidget.hpp @@ -11,6 +11,7 @@ namespace CSVWidget { class SceneToolToggle; + class SceneToolToggle2; } namespace CSVRender @@ -26,7 +27,7 @@ namespace CSVRender CSMWorld::CellSelection mSelection; std::map mCells; std::string mWorldspace; - CSVWidget::SceneToolToggle *mControlElements; + CSVWidget::SceneToolToggle2 *mControlElements; bool mDisplayCellCoord; private: @@ -51,6 +52,12 @@ namespace CSVRender virtual void referenceAdded (const QModelIndex& index, int start, int end); + virtual void pathgridDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); + + virtual void pathgridAboutToBeRemoved (const QModelIndex& parent, int start, int end); + + virtual void pathgridAdded (const QModelIndex& parent, int start, int end); + virtual std::string getStartupInstruction(); /// \note Does not update the view or any cell marker @@ -67,6 +74,8 @@ namespace CSVRender /// \note Does not update the view or any cell marker void moveCellSelection (int x, int y); + void addCellToSceneFromCamera (int offsetX, int offsetY); + public: PagedWorldspaceWidget (QWidget *parent, CSMDoc::Document& document); @@ -75,7 +84,8 @@ namespace CSVRender /// hint system. virtual ~PagedWorldspaceWidget(); - + + /// Decodes the the hint string to set of cell that are rendered. void useViewHint (const std::string& hint); void setCellSelection(const CSMWorld::CellSelection& selection); @@ -90,7 +100,7 @@ namespace CSVRender /// \attention The created tool is not added to the toolbar (via addTool). Doing /// that is the responsibility of the calling function. - virtual CSVWidget::SceneToolToggle *makeControlVisibilitySelector ( + virtual CSVWidget::SceneToolToggle2 *makeControlVisibilitySelector ( CSVWidget::SceneToolbar *parent); virtual unsigned int getVisibilityMask() const; @@ -98,6 +108,9 @@ namespace CSVRender /// \param elementMask Elements to be affected by the clear operation virtual void clearSelection (int elementMask); + /// \param elementMask Elements to be affected by the select operation + virtual void invertSelection (int elementMask); + /// \param elementMask Elements to be affected by the select operation virtual void selectAll (int elementMask); @@ -109,16 +122,26 @@ namespace CSVRender virtual std::string getCellId (const osg::Vec3f& point) const; + virtual Cell* getCell(const osg::Vec3d& point) const; + virtual std::vector > getSelection (unsigned int elementMask) const; + virtual std::vector > getEdited (unsigned int elementMask) + const; + + virtual void setSubMode (int subMode, unsigned int elementMask); + + /// Erase all overrides and restore the visual representation to its true state. + virtual void reset (unsigned int elementMask); + protected: virtual void addVisibilitySelectorButtons (CSVWidget::SceneToolToggle2 *tool); virtual void addEditModeSelectorButtons (CSVWidget::SceneToolMode *tool); - virtual void handleMouseClick (osg::ref_ptr tag, const std::string& button, bool shift); + virtual void handleInteractionPress (const WorldspaceHitResult& hit, InteractionType type); signals: @@ -132,6 +155,16 @@ namespace CSVRender virtual void cellAdded (const QModelIndex& index, int start, int end); + void loadCameraCell(); + + void loadEastCell(); + + void loadNorthCell(); + + void loadWestCell(); + + void loadSouthCell(); + }; } diff --git a/apps/opencs/view/render/pathgrid.cpp b/apps/opencs/view/render/pathgrid.cpp new file mode 100644 index 000000000..9eb2765d3 --- /dev/null +++ b/apps/opencs/view/render/pathgrid.cpp @@ -0,0 +1,680 @@ +#include "pathgrid.hpp" + +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include "../../model/world/cell.hpp" +#include "../../model/world/commands.hpp" +#include "../../model/world/commandmacro.hpp" +#include "../../model/world/data.hpp" +#include "../../model/world/idtree.hpp" + +namespace CSVRender +{ + class PathgridNodeCallback : public osg::NodeCallback + { + public: + + virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) + { + PathgridTag* tag = static_cast(node->getUserData()); + tag->getPathgrid()->update(); + } + }; + + PathgridTag::PathgridTag(Pathgrid* pathgrid) + : TagBase(Mask_Pathgrid), mPathgrid(pathgrid) + { + } + + Pathgrid* PathgridTag::getPathgrid() const + { + return mPathgrid; + } + + QString PathgridTag::getToolTip(bool hideBasics) const + { + QString text("Pathgrid: "); + text += mPathgrid->getId().c_str(); + + return text; + } + + Pathgrid::Pathgrid(CSMWorld::Data& data, osg::Group* parent, const std::string& pathgridId, + const CSMWorld::CellCoordinates& coordinates) + : mData(data) + , mPathgridCollection(mData.getPathgrids()) + , mId(pathgridId) + , mCoords(coordinates) + , mInterior(false) + , mDragOrigin(0) + , mChangeGeometry(true) + , mRemoveGeometry(false) + , mUseOffset(true) + , mParent(parent) + , mPathgridGeometry(0) + , mDragGeometry(0) + , mTag(new PathgridTag(this)) + { + const float CoordScalar = ESM::Land::REAL_SIZE; + + mBaseNode = new osg::PositionAttitudeTransform (); + mBaseNode->setPosition(osg::Vec3f(mCoords.getX() * CoordScalar, mCoords.getY() * CoordScalar, 0.f)); + mBaseNode->setUserData(mTag); + mBaseNode->setUpdateCallback(new PathgridNodeCallback()); + mBaseNode->setNodeMask(Mask_Pathgrid); + mParent->addChild(mBaseNode); + + mPathgridGeode = new osg::Geode(); + mBaseNode->addChild(mPathgridGeode); + + recreateGeometry(); + + int index = mData.getCells().searchId(mId); + if (index != -1) + { + const CSMWorld::Cell& cell = mData.getCells().getRecord(index).get(); + mInterior = cell.mData.mFlags & ESM::Cell::Interior; + } + } + + Pathgrid::~Pathgrid() + { + mParent->removeChild(mBaseNode); + } + + const CSMWorld::CellCoordinates& Pathgrid::getCoordinates() const + { + return mCoords; + } + + const std::string& Pathgrid::getId() const + { + return mId; + } + + bool Pathgrid::isSelected() const + { + return !mSelected.empty(); + } + + const Pathgrid::NodeList& Pathgrid::getSelected() const + { + return mSelected; + } + + void Pathgrid::selectAll() + { + mSelected.clear(); + + const CSMWorld::Pathgrid* source = getPathgridSource(); + if (source) + { + for (unsigned short i = 0; i < static_cast(source->mPoints.size()); ++i) + mSelected.push_back(i); + + createSelectedGeometry(*source); + } + else + { + removeSelectedGeometry(); + } + } + + void Pathgrid::toggleSelected(unsigned short node) + { + NodeList::iterator searchResult = std::find(mSelected.begin(), mSelected.end(), node); + if (searchResult != mSelected.end()) + { + mSelected.erase(searchResult); + } + else + { + mSelected.push_back(node); + } + + createSelectedGeometry(); + } + + void Pathgrid::invertSelected() + { + NodeList temp = NodeList(mSelected); + mSelected.clear(); + + const CSMWorld::Pathgrid* source = getPathgridSource(); + if (source) + { + for (unsigned short i = 0; i < static_cast(source->mPoints.size()); ++i) + { + if (std::find(temp.begin(), temp.end(), i) == temp.end()) + mSelected.push_back(i); + } + + createSelectedGeometry(*source); + } + else + { + removeSelectedGeometry(); + } + } + + void Pathgrid::clearSelected() + { + mSelected.clear(); + removeSelectedGeometry(); + } + + void Pathgrid::moveSelected(const osg::Vec3d& offset) + { + mUseOffset = true; + mMoveOffset += offset; + + recreateGeometry(); + } + + void Pathgrid::setDragOrigin(unsigned short node) + { + mDragOrigin = node; + } + + void Pathgrid::setDragEndpoint(unsigned short node) + { + const CSMWorld::Pathgrid* source = getPathgridSource(); + if (source) + { + const CSMWorld::Pathgrid::Point& pointA = source->mPoints[mDragOrigin]; + const CSMWorld::Pathgrid::Point& pointB = source->mPoints[node]; + + osg::Vec3f start = osg::Vec3f(pointA.mX, pointA.mY, pointA.mZ + SceneUtil::DiamondHalfHeight); + osg::Vec3f end = osg::Vec3f(pointB.mX, pointB.mY, pointB.mZ + SceneUtil::DiamondHalfHeight); + + createDragGeometry(start, end, true); + } + } + + void Pathgrid::setDragEndpoint(const osg::Vec3d& pos) + { + const CSMWorld::Pathgrid* source = getPathgridSource(); + if (source) + { + const CSMWorld::Pathgrid::Point& point = source->mPoints[mDragOrigin]; + + osg::Vec3f start = osg::Vec3f(point.mX, point.mY, point.mZ + SceneUtil::DiamondHalfHeight); + osg::Vec3f end = pos - mBaseNode->getPosition(); + createDragGeometry(start, end, false); + } + } + + void Pathgrid::resetIndicators() + { + mUseOffset = false; + mMoveOffset.set(0, 0, 0); + + mPathgridGeode->removeDrawable(mDragGeometry); + mDragGeometry = 0; + } + + void Pathgrid::applyPoint(CSMWorld::CommandMacro& commands, const osg::Vec3d& worldPos) + { + CSMWorld::IdTree* model = dynamic_cast(mData.getTableModel( + CSMWorld::UniversalId::Type_Pathgrids)); + + const CSMWorld::Pathgrid* source = getPathgridSource(); + if (source) + { + osg::Vec3d localCoords = worldPos - mBaseNode->getPosition(); + + int posX = clampToCell(static_cast(localCoords.x())); + int posY = clampToCell(static_cast(localCoords.y())); + int posZ = clampToCell(static_cast(localCoords.z())); + + int recordIndex = mPathgridCollection.getIndex (mId); + int parentColumn = mPathgridCollection.findColumnIndex(CSMWorld::Columns::ColumnId_PathgridPoints); + + int posXColumn = mPathgridCollection.searchNestedColumnIndex(parentColumn, + CSMWorld::Columns::ColumnId_PathgridPosX); + + int posYColumn = mPathgridCollection.searchNestedColumnIndex(parentColumn, + CSMWorld::Columns::ColumnId_PathgridPosY); + + int posZColumn = mPathgridCollection.searchNestedColumnIndex(parentColumn, + CSMWorld::Columns::ColumnId_PathgridPosZ); + + QModelIndex parent = model->index(recordIndex, parentColumn); + int row = static_cast(source->mPoints.size()); + + // Add node to end of list + commands.push(new CSMWorld::AddNestedCommand(*model, mId, row, parentColumn)); + commands.push(new CSMWorld::ModifyCommand(*model, model->index(row, posXColumn, parent), posX)); + commands.push(new CSMWorld::ModifyCommand(*model, model->index(row, posYColumn, parent), posY)); + commands.push(new CSMWorld::ModifyCommand(*model, model->index(row, posZColumn, parent), posZ)); + } + else + { + int index = mPathgridCollection.searchId(mId); + if (index == -1) + { + // Does not exist + commands.push(new CSMWorld::CreatePathgridCommand(*model, mId)); + } + else + { + source = &mPathgridCollection.getRecord(index).get(); + + // Deleted, so revert and remove all data + commands.push(new CSMWorld::RevertCommand(*model, mId)); + + int parentColumn = mPathgridCollection.findColumnIndex(CSMWorld::Columns::ColumnId_PathgridPoints); + for (int row = source->mPoints.size() - 1; row >= 0; --row) + { + commands.push(new CSMWorld::DeleteNestedCommand(*model, mId, row, parentColumn)); + } + + parentColumn = mPathgridCollection.findColumnIndex(CSMWorld::Columns::ColumnId_PathgridEdges); + for (int row = source->mEdges.size() - 1; row >= 0; --row) + { + commands.push(new CSMWorld::DeleteNestedCommand(*model, mId, row, parentColumn)); + } + } + } + } + + void Pathgrid::applyPosition(CSMWorld::CommandMacro& commands) + { + const CSMWorld::Pathgrid* source = getPathgridSource(); + if (source) + { + osg::Vec3d localCoords = mMoveOffset; + + int offsetX = static_cast(localCoords.x()); + int offsetY = static_cast(localCoords.y()); + int offsetZ = static_cast(localCoords.z()); + + QAbstractItemModel* model = mData.getTableModel(CSMWorld::UniversalId::Type_Pathgrids); + + int recordIndex = mPathgridCollection.getIndex(mId); + int parentColumn = mPathgridCollection.findColumnIndex(CSMWorld::Columns::ColumnId_PathgridPoints); + + int posXColumn = mPathgridCollection.searchNestedColumnIndex(parentColumn, + CSMWorld::Columns::ColumnId_PathgridPosX); + + int posYColumn = mPathgridCollection.searchNestedColumnIndex(parentColumn, + CSMWorld::Columns::ColumnId_PathgridPosY); + + int posZColumn = mPathgridCollection.searchNestedColumnIndex(parentColumn, + CSMWorld::Columns::ColumnId_PathgridPosZ); + + QModelIndex parent = model->index(recordIndex, parentColumn); + + for (size_t i = 0; i < mSelected.size(); ++i) + { + const CSMWorld::Pathgrid::Point& point = source->mPoints[mSelected[i]]; + int row = static_cast(mSelected[i]); + + commands.push(new CSMWorld::ModifyCommand(*model, model->index(row, posXColumn, parent), + clampToCell(point.mX + offsetX))); + + commands.push(new CSMWorld::ModifyCommand(*model, model->index(row, posYColumn, parent), + clampToCell(point.mY + offsetY))); + + commands.push(new CSMWorld::ModifyCommand(*model, model->index(row, posZColumn, parent), + clampToCell(point.mZ + offsetZ))); + } + } + } + + void Pathgrid::applyEdge(CSMWorld::CommandMacro& commands, unsigned short node1, unsigned short node2) + { + const CSMWorld::Pathgrid* source = getPathgridSource(); + if (source) + { + addEdge(commands, *source, node1, node2); + } + } + + void Pathgrid::applyEdges(CSMWorld::CommandMacro& commands, unsigned short node) + { + const CSMWorld::Pathgrid* source = getPathgridSource(); + if (source) + { + for (size_t i = 0; i < mSelected.size(); ++i) + { + addEdge(commands, *source, node, mSelected[i]); + } + } + } + + void Pathgrid::applyRemoveNodes(CSMWorld::CommandMacro& commands) + { + const CSMWorld::Pathgrid* source = getPathgridSource(); + if (source) + { + CSMWorld::IdTree* model = dynamic_cast(mData.getTableModel( + CSMWorld::UniversalId::Type_Pathgrids)); + + // Want to remove nodes from end of list first + std::sort(mSelected.begin(), mSelected.end(), std::greater()); + + int recordIndex = mPathgridCollection.getIndex(mId); + int parentColumn = mPathgridCollection.findColumnIndex(CSMWorld::Columns::ColumnId_PathgridPoints); + + for (std::vector::iterator row = mSelected.begin(); row != mSelected.end(); ++row) + { + commands.push(new CSMWorld::DeleteNestedCommand(*model, mId, static_cast(*row), parentColumn)); + } + + // Fix/remove edges + std::set > edgeRowsToRemove; + + parentColumn = mPathgridCollection.findColumnIndex(CSMWorld::Columns::ColumnId_PathgridEdges); + + int edge0Column = mPathgridCollection.searchNestedColumnIndex(parentColumn, + CSMWorld::Columns::ColumnId_PathgridEdge0); + + int edge1Column = mPathgridCollection.searchNestedColumnIndex(parentColumn, + CSMWorld::Columns::ColumnId_PathgridEdge1); + + QModelIndex parent = model->index(recordIndex, parentColumn); + + for (size_t edge = 0; edge < source->mEdges.size(); ++edge) + { + int adjustment0 = 0; + int adjustment1 = 0; + + // Determine necessary adjustment + for (std::vector::iterator point = mSelected.begin(); point != mSelected.end(); ++point) + { + if (source->mEdges[edge].mV0 == *point || source->mEdges[edge].mV1 == *point) + { + edgeRowsToRemove.insert(static_cast(edge)); + + adjustment0 = 0; // No need to adjust, its getting removed + adjustment1 = 0; + break; + } + + if (source->mEdges[edge].mV0 > *point) + --adjustment0; + + if (source->mEdges[edge].mV1 > *point) + --adjustment1; + } + + if (adjustment0 != 0) + { + int adjustedEdge = source->mEdges[edge].mV0 + adjustment0; + commands.push(new CSMWorld::ModifyCommand(*model, model->index(edge, edge0Column, parent), + adjustedEdge)); + } + + if (adjustment1 != 0) + { + int adjustedEdge = source->mEdges[edge].mV1 + adjustment1; + commands.push(new CSMWorld::ModifyCommand(*model, model->index(edge, edge1Column, parent), + adjustedEdge)); + } + } + + std::set >::iterator row; + for (row = edgeRowsToRemove.begin(); row != edgeRowsToRemove.end(); ++row) + { + commands.push(new CSMWorld::DeleteNestedCommand(*model, mId, *row, parentColumn)); + } + } + + clearSelected(); + } + + void Pathgrid::applyRemoveEdges(CSMWorld::CommandMacro& commands) + { + const CSMWorld::Pathgrid* source = getPathgridSource(); + if (source) + { + // Want to remove from end of row first + std::set > rowsToRemove; + for (size_t i = 0; i <= mSelected.size(); ++i) + { + for (size_t j = i + 1; j < mSelected.size(); ++j) + { + int row = edgeExists(*source, mSelected[i], mSelected[j]); + if (row != -1) + { + rowsToRemove.insert(row); + } + + row = edgeExists(*source, mSelected[j], mSelected[i]); + if (row != -1) + { + rowsToRemove.insert(row); + } + } + } + + CSMWorld::IdTree* model = dynamic_cast(mData.getTableModel( + CSMWorld::UniversalId::Type_Pathgrids)); + + int parentColumn = mPathgridCollection.findColumnIndex(CSMWorld::Columns::ColumnId_PathgridEdges); + + std::set >::iterator row; + for (row = rowsToRemove.begin(); row != rowsToRemove.end(); ++row) + { + commands.push(new CSMWorld::DeleteNestedCommand(*model, mId, *row, parentColumn)); + } + } + } + + osg::ref_ptr Pathgrid::getTag() const + { + return mTag; + } + + void Pathgrid::recreateGeometry() + { + mChangeGeometry = true; + } + + void Pathgrid::removeGeometry() + { + mRemoveGeometry = true; + } + + void Pathgrid::update() + { + if (mRemoveGeometry) + { + removePathgridGeometry(); + removeSelectedGeometry(); + } + else if (mChangeGeometry) + { + createGeometry(); + } + + mChangeGeometry = false; + mRemoveGeometry = false; + } + + void Pathgrid::createGeometry() + { + const CSMWorld::Pathgrid* source = getPathgridSource(); + if (source) + { + CSMWorld::Pathgrid temp; + if (mUseOffset) + { + temp = *source; + + for (NodeList::iterator it = mSelected.begin(); it != mSelected.end(); ++it) + { + temp.mPoints[*it].mX += mMoveOffset.x(); + temp.mPoints[*it].mY += mMoveOffset.y(); + temp.mPoints[*it].mZ += mMoveOffset.z(); + } + + source = &temp; + } + + removePathgridGeometry(); + mPathgridGeometry = SceneUtil::createPathgridGeometry(*source); + mPathgridGeode->addDrawable(mPathgridGeometry); + + createSelectedGeometry(*source); + } + else + { + removePathgridGeometry(); + removeSelectedGeometry(); + } + } + + void Pathgrid::createSelectedGeometry() + { + const CSMWorld::Pathgrid* source = getPathgridSource(); + if (source) + { + createSelectedGeometry(*source); + } + else + { + removeSelectedGeometry(); + } + } + + void Pathgrid::createSelectedGeometry(const CSMWorld::Pathgrid& source) + { + removeSelectedGeometry(); + + mSelectedGeometry = SceneUtil::createPathgridSelectedWireframe(source, mSelected); + mPathgridGeode->addDrawable(mSelectedGeometry); + } + + void Pathgrid::removePathgridGeometry() + { + if (mPathgridGeometry) + { + mPathgridGeode->removeDrawable(mPathgridGeometry); + mPathgridGeometry = 0; + } + } + + void Pathgrid::removeSelectedGeometry() + { + if (mSelectedGeometry) + { + mPathgridGeode->removeDrawable(mSelectedGeometry); + mSelectedGeometry = 0; + } + } + + void Pathgrid::createDragGeometry(const osg::Vec3f& start, const osg::Vec3f& end, bool valid) + { + if (mDragGeometry) + mPathgridGeode->removeDrawable(mDragGeometry); + + mDragGeometry = new osg::Geometry(); + + osg::ref_ptr vertices = new osg::Vec3Array(2); + osg::ref_ptr colors = new osg::Vec4Array(1); + osg::ref_ptr indices = new osg::DrawElementsUShort(osg::PrimitiveSet::LINES, 2); + + (*vertices)[0] = start; + (*vertices)[1] = end; + + if (valid) + { + (*colors)[0] = osg::Vec4f(0.91f, 0.66f, 1.f, 1.f); + } + else + { + (*colors)[0] = osg::Vec4f(1.f, 0.f, 0.f, 1.f); + } + + indices->setElement(0, 0); + indices->setElement(1, 1); + + mDragGeometry->setVertexArray(vertices); + mDragGeometry->setColorArray(colors, osg::Array::BIND_OVERALL); + mDragGeometry->addPrimitiveSet(indices); + mDragGeometry->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); + + mPathgridGeode->addDrawable(mDragGeometry); + } + + const CSMWorld::Pathgrid* Pathgrid::getPathgridSource() + { + int index = mPathgridCollection.searchId(mId); + if (index != -1 && !mPathgridCollection.getRecord(index).isDeleted()) + { + return &mPathgridCollection.getRecord(index).get(); + } + + return 0; + } + + int Pathgrid::edgeExists(const CSMWorld::Pathgrid& source, unsigned short node1, unsigned short node2) + { + for (size_t i = 0; i < source.mEdges.size(); ++i) + { + if (source.mEdges[i].mV0 == node1 && source.mEdges[i].mV1 == node2) + return static_cast(i); + } + + return -1; + } + + void Pathgrid::addEdge(CSMWorld::CommandMacro& commands, const CSMWorld::Pathgrid& source, unsigned short node1, + unsigned short node2) + { + CSMWorld::IdTree* model = dynamic_cast(mData.getTableModel( + CSMWorld::UniversalId::Type_Pathgrids)); + + int recordIndex = mPathgridCollection.getIndex(mId); + int parentColumn = mPathgridCollection.findColumnIndex(CSMWorld::Columns::ColumnId_PathgridEdges); + + int edge0Column = mPathgridCollection.searchNestedColumnIndex(parentColumn, + CSMWorld::Columns::ColumnId_PathgridEdge0); + + int edge1Column = mPathgridCollection.searchNestedColumnIndex(parentColumn, + CSMWorld::Columns::ColumnId_PathgridEdge1); + + QModelIndex parent = model->index(recordIndex, parentColumn); + int row = static_cast(source.mEdges.size()); + + if (edgeExists(source, node1, node2) == -1) + { + commands.push(new CSMWorld::AddNestedCommand(*model, mId, row, parentColumn)); + commands.push(new CSMWorld::ModifyCommand(*model, model->index(row, edge0Column, parent), node1)); + commands.push(new CSMWorld::ModifyCommand(*model, model->index(row, edge1Column, parent), node2)); + ++row; + } + + if (edgeExists(source, node2, node1) == -1) + { + commands.push(new CSMWorld::AddNestedCommand(*model, mId, row, parentColumn)); + commands.push(new CSMWorld::ModifyCommand(*model, model->index(row, edge0Column, parent), node2)); + commands.push(new CSMWorld::ModifyCommand(*model, model->index(row, edge1Column, parent), node1)); + } + } + + int Pathgrid::clampToCell(int v) + { + const int CellExtent = ESM::Land::REAL_SIZE; + + if (mInterior) + return v; + else if (v > CellExtent) + return CellExtent; + else if (v < 0) + return 0; + else + return v; + } +} diff --git a/apps/opencs/view/render/pathgrid.hpp b/apps/opencs/view/render/pathgrid.hpp new file mode 100644 index 000000000..181a62b44 --- /dev/null +++ b/apps/opencs/view/render/pathgrid.hpp @@ -0,0 +1,137 @@ +#ifndef CSV_RENDER_PATHGRID_H +#define CSV_RENDER_PATHGRID_H + +#include + +#include +#include +#include + +#include "../../model/world/cellcoordinates.hpp" +#include "../../model/world/idcollection.hpp" +#include "../../model/world/subcellcollection.hpp" + +#include "tagbase.hpp" + +namespace osg +{ + class Geode; + class Geometry; + class Group; + class PositionAttitudeTransform; +} + +namespace CSMWorld +{ + class CommandMacro; + class Data; + struct Pathgrid; +} + +namespace CSVRender +{ + class Pathgrid; + + class PathgridTag : public TagBase + { + public: + + PathgridTag (Pathgrid* pathgrid); + + Pathgrid* getPathgrid () const; + + virtual QString getToolTip (bool hideBasics) const; + + private: + + Pathgrid* mPathgrid; + }; + + class Pathgrid + { + public: + + typedef std::vector NodeList; + + Pathgrid(CSMWorld::Data& data, osg::Group* parent, const std::string& pathgridId, + const CSMWorld::CellCoordinates& coordinates); + + ~Pathgrid(); + + const CSMWorld::CellCoordinates& getCoordinates() const; + const std::string& getId() const; + + bool isSelected() const; + const NodeList& getSelected() const; + void selectAll(); + void toggleSelected(unsigned short node); // Adds to end of vector + void invertSelected(); + void clearSelected(); + + void moveSelected(const osg::Vec3d& offset); + void setDragOrigin(unsigned short node); + void setDragEndpoint(unsigned short node); + void setDragEndpoint(const osg::Vec3d& pos); + + void resetIndicators(); + + void applyPoint(CSMWorld::CommandMacro& commands, const osg::Vec3d& worldPos); + void applyPosition(CSMWorld::CommandMacro& commands); + void applyEdge(CSMWorld::CommandMacro& commands, unsigned short node1, unsigned short node2); + void applyEdges(CSMWorld::CommandMacro& commands, unsigned short node); + void applyRemoveNodes(CSMWorld::CommandMacro& commands); + void applyRemoveEdges(CSMWorld::CommandMacro& commands); + + osg::ref_ptr getTag() const; + + void recreateGeometry(); + void removeGeometry(); + + void update(); + + private: + + CSMWorld::Data& mData; + CSMWorld::SubCellCollection& mPathgridCollection; + std::string mId; + CSMWorld::CellCoordinates mCoords; + bool mInterior; + + NodeList mSelected; + osg::Vec3d mMoveOffset; + unsigned short mDragOrigin; + + bool mChangeGeometry; + bool mRemoveGeometry; + bool mUseOffset; + + osg::Group* mParent; + osg::ref_ptr mBaseNode; + osg::ref_ptr mPathgridGeode; + osg::ref_ptr mPathgridGeometry; + osg::ref_ptr mSelectedGeometry; + osg::ref_ptr mDragGeometry; + + osg::ref_ptr mTag; + + void createGeometry(); + void createSelectedGeometry(); + void createSelectedGeometry(const CSMWorld::Pathgrid& source); + void removePathgridGeometry(); + void removeSelectedGeometry(); + + void createDragGeometry(const osg::Vec3f& start, const osg::Vec3f& end, bool valid); + + const CSMWorld::Pathgrid* getPathgridSource(); + + int edgeExists(const CSMWorld::Pathgrid& source, unsigned short node1, unsigned short node2); + void addEdge(CSMWorld::CommandMacro& commands, const CSMWorld::Pathgrid& source, unsigned short node1, + unsigned short node2); + void removeEdge(CSMWorld::CommandMacro& commands, const CSMWorld::Pathgrid& source, unsigned short node1, + unsigned short node2); + + int clampToCell(int v); + }; +} + +#endif diff --git a/apps/opencs/view/render/pathgridmode.cpp b/apps/opencs/view/render/pathgridmode.cpp new file mode 100644 index 000000000..228b2b5e7 --- /dev/null +++ b/apps/opencs/view/render/pathgridmode.cpp @@ -0,0 +1,275 @@ +#include "pathgridmode.hpp" + +#include +#include + +#include + +#include "../../model/prefs/state.hpp" + +#include "../../model/world/commands.hpp" +#include "../../model/world/commandmacro.hpp" +#include "../../model/world/idtable.hpp" +#include "../../model/world/idtree.hpp" + +#include "../widget/scenetoolbar.hpp" + +#include "cell.hpp" +#include "mask.hpp" +#include "pathgrid.hpp" +#include "pathgridselectionmode.hpp" +#include "worldspacewidget.hpp" + +namespace CSVRender +{ + PathgridMode::PathgridMode(WorldspaceWidget* worldspaceWidget, QWidget* parent) + : EditMode(worldspaceWidget, QIcon(":placeholder"), Mask_Pathgrid | Mask_Terrain | Mask_Reference, + getTooltip(), parent) + , mDragMode(DragMode_None) + , mFromNode(0) + , mSelectionMode(0) + { + } + + QString PathgridMode::getTooltip() + { + return QString( + "Pathgrid editing" + "
        • Press {scene-edit-primary} to add a node to the cursor location
        • " + "
        • Press {scene-edit-secondary} to connect the selected nodes to the node beneath the cursor
        • " + "
        • Press {scene-edit-primary} and drag to move selected nodes
        • " + "
        • Press {scene-edit-secondary} and drag to connect one node to another
        • " + "

        Note: Only a single cell's pathgrid may be edited at a time"); + } + + void PathgridMode::activate(CSVWidget::SceneToolbar* toolbar) + { + if (!mSelectionMode) + { + mSelectionMode = new PathgridSelectionMode(toolbar, getWorldspaceWidget()); + } + + EditMode::activate(toolbar); + toolbar->addTool(mSelectionMode); + } + + void PathgridMode::deactivate(CSVWidget::SceneToolbar* toolbar) + { + if (mSelectionMode) + { + toolbar->removeTool (mSelectionMode); + delete mSelectionMode; + mSelectionMode = 0; + } + } + + void PathgridMode::primaryEditPressed(const WorldspaceHitResult& hitResult) + { + if (CSMPrefs::get()["3D Scene Input"]["context-select"].isTrue() && + dynamic_cast(hitResult.tag.get())) + { + primarySelectPressed(hitResult); + } + else if (Cell* cell = getWorldspaceWidget().getCell (hitResult.worldPos)) + { + // Add node + QUndoStack& undoStack = getWorldspaceWidget().getDocument().getUndoStack(); + QString description = "Add node"; + + CSMWorld::CommandMacro macro(undoStack, description); + cell->getPathgrid()->applyPoint(macro, hitResult.worldPos); + } + } + + void PathgridMode::secondaryEditPressed(const WorldspaceHitResult& hit) + { + if (hit.tag) + { + if (PathgridTag* tag = dynamic_cast(hit.tag.get())) + { + if (tag->getPathgrid()->isSelected()) + { + unsigned short node = SceneUtil::getPathgridNode(static_cast(hit.index0)); + + QUndoStack& undoStack = getWorldspaceWidget().getDocument().getUndoStack(); + QString description = "Connect node to selected nodes"; + + CSMWorld::CommandMacro macro(undoStack, description); + tag->getPathgrid()->applyEdges(macro, node); + } + } + } + } + + void PathgridMode::primarySelectPressed(const WorldspaceHitResult& hit) + { + getWorldspaceWidget().clearSelection(Mask_Pathgrid); + + if (hit.tag) + { + if (PathgridTag* tag = dynamic_cast(hit.tag.get())) + { + mLastId = tag->getPathgrid()->getId(); + unsigned short node = SceneUtil::getPathgridNode(static_cast(hit.index0)); + tag->getPathgrid()->toggleSelected(node); + } + } + } + + void PathgridMode::secondarySelectPressed(const WorldspaceHitResult& hit) + { + if (hit.tag) + { + if (PathgridTag* tag = dynamic_cast(hit.tag.get())) + { + if (tag->getPathgrid()->getId() != mLastId) + { + getWorldspaceWidget().clearSelection(Mask_Pathgrid); + mLastId = tag->getPathgrid()->getId(); + } + + unsigned short node = SceneUtil::getPathgridNode(static_cast(hit.index0)); + tag->getPathgrid()->toggleSelected(node); + + return; + } + } + + getWorldspaceWidget().clearSelection(Mask_Pathgrid); + } + + bool PathgridMode::primaryEditStartDrag(const QPoint& pos) + { + std::vector > selection = getWorldspaceWidget().getSelection (Mask_Pathgrid); + + if (CSMPrefs::get()["3D Scene Input"]["context-select"].isTrue()) + { + WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); + + if (dynamic_cast(hit.tag.get())) + { + primarySelectPressed(hit); + selection = getWorldspaceWidget().getSelection (Mask_Pathgrid); + } + } + + if (!selection.empty()) + { + mDragMode = DragMode_Move; + return true; + } + + return false; + } + + bool PathgridMode::secondaryEditStartDrag(const QPoint& pos) + { + WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); + if (hit.tag) + { + if (PathgridTag* tag = dynamic_cast(hit.tag.get())) + { + mDragMode = DragMode_Edge; + mEdgeId = tag->getPathgrid()->getId(); + mFromNode = SceneUtil::getPathgridNode(static_cast(hit.index0)); + + tag->getPathgrid()->setDragOrigin(mFromNode); + return true; + } + } + + return false; + } + + void PathgridMode::drag(const QPoint& pos, int diffX, int diffY, double speedFactor) + { + if (mDragMode == DragMode_Move) + { + std::vector > selection = getWorldspaceWidget().getSelection(Mask_Pathgrid); + + for (std::vector >::iterator it = selection.begin(); it != selection.end(); ++it) + { + if (PathgridTag* tag = dynamic_cast(it->get())) + { + osg::Vec3d eye, center, up, offset; + getWorldspaceWidget().getCamera()->getViewMatrix().getLookAt (eye, center, up); + + offset = (up * diffY * speedFactor) + (((center - eye) ^ up) * diffX * speedFactor); + + tag->getPathgrid()->moveSelected(offset); + } + } + } + else if (mDragMode == DragMode_Edge) + { + WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); + + Cell* cell = getWorldspaceWidget().getCell(hit.worldPos); + if (cell) + { + PathgridTag* tag = 0; + if (hit.tag && (tag = dynamic_cast(hit.tag.get())) && tag->getPathgrid()->getId() == mEdgeId) + { + unsigned short node = SceneUtil::getPathgridNode(static_cast(hit.index0)); + cell->getPathgrid()->setDragEndpoint(node); + } + else + { + cell->getPathgrid()->setDragEndpoint(hit.worldPos); + } + + } + } + } + + void PathgridMode::dragCompleted(const QPoint& pos) + { + if (mDragMode == DragMode_Move) + { + std::vector > selection = getWorldspaceWidget().getSelection (Mask_Pathgrid); + for (std::vector >::iterator it = selection.begin(); it != selection.end(); ++it) + { + if (PathgridTag* tag = dynamic_cast(it->get())) + { + QUndoStack& undoStack = getWorldspaceWidget().getDocument().getUndoStack(); + QString description = "Move pathgrid node(s)"; + + CSMWorld::CommandMacro macro(undoStack, description); + tag->getPathgrid()->applyPosition(macro); + } + } + } + else if (mDragMode == DragMode_Edge) + { + WorldspaceHitResult hit = getWorldspaceWidget().mousePick(pos, getWorldspaceWidget().getInteractionMask()); + + if (hit.tag) + { + if (PathgridTag* tag = dynamic_cast(hit.tag.get())) + { + if (tag->getPathgrid()->getId() == mEdgeId) + { + unsigned short toNode = SceneUtil::getPathgridNode(static_cast(hit.index0)); + + QUndoStack& undoStack = getWorldspaceWidget().getDocument().getUndoStack(); + QString description = "Add edge between nodes"; + + CSMWorld::CommandMacro macro(undoStack, description); + tag->getPathgrid()->applyEdge(macro, mFromNode, toNode); + } + } + } + + mEdgeId.clear(); + mFromNode = 0; + } + + mDragMode = DragMode_None; + getWorldspaceWidget().reset(Mask_Pathgrid); + } + + void PathgridMode::dragAborted() + { + getWorldspaceWidget().reset(Mask_Pathgrid); + } +} diff --git a/apps/opencs/view/render/pathgridmode.hpp b/apps/opencs/view/render/pathgridmode.hpp new file mode 100644 index 000000000..e34208f8c --- /dev/null +++ b/apps/opencs/view/render/pathgridmode.hpp @@ -0,0 +1,63 @@ +#ifndef CSV_RENDER_PATHGRIDMODE_H +#define CSV_RENDER_PATHGRIDMODE_H + +#include + +#include "editmode.hpp" + +namespace CSVRender +{ + class PathgridSelectionMode; + + class PathgridMode : public EditMode + { + Q_OBJECT + + public: + + PathgridMode(WorldspaceWidget* worldspace, QWidget* parent=0); + + virtual void activate(CSVWidget::SceneToolbar* toolbar); + + virtual void deactivate(CSVWidget::SceneToolbar* toolbar); + + virtual void primaryEditPressed(const WorldspaceHitResult& hit); + + virtual void secondaryEditPressed(const WorldspaceHitResult& hit); + + virtual void primarySelectPressed(const WorldspaceHitResult& hit); + + virtual void secondarySelectPressed(const WorldspaceHitResult& hit); + + virtual bool primaryEditStartDrag (const QPoint& pos); + + virtual bool secondaryEditStartDrag (const QPoint& pos); + + virtual void drag (const QPoint& pos, int diffX, int diffY, double speedFactor); + + virtual void dragCompleted(const QPoint& pos); + + /// \note dragAborted will not be called, if the drag is aborted via changing + /// editing mode + virtual void dragAborted(); + + private: + + enum DragMode + { + DragMode_None, + DragMode_Move, + DragMode_Edge + }; + + DragMode mDragMode; + std::string mLastId, mEdgeId; + unsigned short mFromNode; + + PathgridSelectionMode* mSelectionMode; + + QString getTooltip(); + }; +} + +#endif diff --git a/apps/opencs/view/render/pathgridselectionmode.cpp b/apps/opencs/view/render/pathgridselectionmode.cpp new file mode 100644 index 000000000..db41faf50 --- /dev/null +++ b/apps/opencs/view/render/pathgridselectionmode.cpp @@ -0,0 +1,71 @@ +#include "pathgridselectionmode.hpp" + +#include +#include + +#include "../../model/world/idtable.hpp" +#include "../../model/world/commands.hpp" +#include "../../model/world/commandmacro.hpp" + +#include "worldspacewidget.hpp" +#include "pathgrid.hpp" + +namespace CSVRender +{ + PathgridSelectionMode::PathgridSelectionMode(CSVWidget::SceneToolbar* parent, WorldspaceWidget& worldspaceWidget) + : SelectionMode(parent, worldspaceWidget, Mask_Pathgrid) + { + mRemoveSelectedNodes = new QAction("Remove selected nodes", this); + mRemoveSelectedEdges = new QAction("Remove edges between selected nodes", this); + + connect(mRemoveSelectedNodes, SIGNAL(triggered()), this, SLOT(removeSelectedNodes())); + connect(mRemoveSelectedEdges, SIGNAL(triggered()), this, SLOT(removeSelectedEdges())); + } + + bool PathgridSelectionMode::createContextMenu(QMenu* menu) + { + if (menu) + { + SelectionMode::createContextMenu(menu); + + menu->addAction(mRemoveSelectedNodes); + menu->addAction(mRemoveSelectedEdges); + } + + return true; + } + + void PathgridSelectionMode::removeSelectedNodes() + { + std::vector > selection = getWorldspaceWidget().getSelection (Mask_Pathgrid); + + for (std::vector >::iterator it = selection.begin(); it != selection.end(); ++it) + { + if (PathgridTag* tag = dynamic_cast(it->get())) + { + QUndoStack& undoStack = getWorldspaceWidget().getDocument().getUndoStack(); + QString description = "Remove selected nodes"; + + CSMWorld::CommandMacro macro(undoStack, description); + tag->getPathgrid()->applyRemoveNodes(macro); + } + } + } + + void PathgridSelectionMode::removeSelectedEdges() + { + std::vector > selection = getWorldspaceWidget().getSelection (Mask_Pathgrid); + + for (std::vector >::iterator it = selection.begin(); it != selection.end(); ++it) + { + if (PathgridTag* tag = dynamic_cast(it->get())) + { + QUndoStack& undoStack = getWorldspaceWidget().getDocument().getUndoStack(); + QString description = "Remove edges between selected nodes"; + + CSMWorld::CommandMacro macro(undoStack, description); + tag->getPathgrid()->applyRemoveEdges(macro); + } + } + } +} diff --git a/apps/opencs/view/render/pathgridselectionmode.hpp b/apps/opencs/view/render/pathgridselectionmode.hpp new file mode 100644 index 000000000..e4cb1e044 --- /dev/null +++ b/apps/opencs/view/render/pathgridselectionmode.hpp @@ -0,0 +1,38 @@ +#ifndef CSV_RENDER_PATHGRID_SELECTION_MODE_H +#define CSV_RENDER_PATHGRID_SELECTION_MODE_H + +#include "selectionmode.hpp" + +namespace CSVRender +{ + class PathgridSelectionMode : public SelectionMode + { + Q_OBJECT + + public: + + PathgridSelectionMode(CSVWidget::SceneToolbar* parent, WorldspaceWidget& worldspaceWidget); + + protected: + + /// Add context menu items to \a menu. + /// + /// \attention menu can be a 0-pointer + /// + /// \return Have there been any menu items to be added (if menu is 0 and there + /// items to be added, the function must return true anyway. + bool createContextMenu(QMenu* menu); + + private: + + QAction* mRemoveSelectedNodes; + QAction* mRemoveSelectedEdges; + + private slots: + + void removeSelectedNodes(); + void removeSelectedEdges(); + }; +} + +#endif diff --git a/apps/opencs/view/render/previewwidget.cpp b/apps/opencs/view/render/previewwidget.cpp index a03b277d3..2f3510317 100644 --- a/apps/opencs/view/render/previewwidget.cpp +++ b/apps/opencs/view/render/previewwidget.cpp @@ -1,7 +1,5 @@ #include "previewwidget.hpp" -#include - #include "../../model/world/data.hpp" #include "../../model/world/idtable.hpp" @@ -9,7 +7,7 @@ CSVRender::PreviewWidget::PreviewWidget (CSMWorld::Data& data, const std::string& id, bool referenceable, QWidget *parent) : SceneWidget (data.getResourceSystem(), parent), mData (data), mObject(data, mRootNode, id, referenceable) { - mView->setCameraManipulator(new osgGA::TrackballManipulator); + selectNavigationMode("orbit"); QAbstractItemModel *referenceables = mData.getTableModel (CSMWorld::UniversalId::Type_Referenceables); diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp index e5b9171e0..a072cd0aa 100644 --- a/apps/opencs/view/render/scenewidget.cpp +++ b/apps/opencs/view/render/scenewidget.cpp @@ -3,14 +3,15 @@ #include #include #include -#include #include -#include +#include #include #include #include #include +#include +#include #include #include @@ -18,8 +19,13 @@ #include "../widget/scenetoolmode.hpp" +#include "../../model/prefs/state.hpp" +#include "../../model/prefs/shortcut.hpp" +#include "../../model/prefs/shortcuteventhandler.hpp" + #include "lighting.hpp" #include "mask.hpp" +#include "cameracontroller.hpp" namespace CSVRender { @@ -57,9 +63,6 @@ RenderWidget::RenderWidget(QWidget *parent, Qt::WindowFlags f) layout->addWidget(window->getGLWidget()); setLayout(layout); - // Pass events through this widget first - window->getGLWidget()->installEventFilter(this); - mView->getCamera()->setGraphicsContext(window); mView->getCamera()->setClearColor( osg::Vec4(0.2, 0.2, 0.6, 1.0) ); mView->getCamera()->setViewport( new osg::Viewport(0, 0, traits->width, traits->height) ); @@ -72,10 +75,16 @@ RenderWidget::RenderWidget(QWidget *parent, Qt::WindowFlags f) mView->getCamera()->getOrCreateStateSet()->setMode(GL_NORMALIZE, osg::StateAttribute::ON); mView->getCamera()->getOrCreateStateSet()->setMode(GL_CULL_FACE, osg::StateAttribute::ON); + osg::ref_ptr defaultMat (new osg::Material); + defaultMat->setColorMode(osg::Material::OFF); + defaultMat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); + defaultMat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); + defaultMat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 0.f)); + mView->getCamera()->getOrCreateStateSet()->setAttribute(defaultMat); mView->setSceneData(mRootNode); - // Press S to reveal profiling stats + // Add ability to signal osg to show its statistics for debugging purposes mView->addEventHandler(new osgViewer::StatsHandler); mView->getCamera()->setCullMask(~(Mask_UpdateVisitor)); @@ -100,26 +109,21 @@ void RenderWidget::setVisibilityMask(int mask) mView->getCamera()->setCullMask(mask | Mask_ParticleSystem | Mask_Lighting); } -bool RenderWidget::eventFilter(QObject* obj, QEvent* event) +osg::Camera *RenderWidget::getCamera() { - // handle event in this widget, is there a better way to do this? - if (event->type() == QEvent::MouseButtonPress) - mousePressEvent(static_cast(event)); - if (event->type() == QEvent::MouseButtonRelease) - mouseReleaseEvent(static_cast(event)); - if (event->type() == QEvent::MouseMove) - mouseMoveEvent(static_cast(event)); - if (event->type() == QEvent::KeyPress) - keyPressEvent(static_cast(event)); - if (event->type() == QEvent::KeyRelease) - keyReleaseEvent(static_cast(event)); - if (event->type() == QEvent::Wheel) - wheelEvent(static_cast(event)); - - // Always pass the event on to GLWidget, i.e. to OSG event queue - return QObject::eventFilter(obj, event); + return mView->getCamera(); } +void RenderWidget::toggleRenderStats() +{ + osgViewer::GraphicsWindow* window = + static_cast(mView->getCamera()->getGraphicsContext()); + + window->getEventQueue()->keyPress(osgGA::GUIEventAdapter::KEY_S); + window->getEventQueue()->keyRelease(osgGA::GUIEventAdapter::KEY_S); +} + + // -------------------------------------------------- CompositeViewer::CompositeViewer() @@ -134,6 +138,10 @@ CompositeViewer::CompositeViewer() setThreadingModel(threadingModel); +#if OSG_VERSION_GREATER_OR_EQUAL(3,5,5) + setUseConfigureAffinity(false); +#endif + // disable the default setting of viewer.done() by pressing Escape. setKeyEventSetsDone(0); @@ -153,19 +161,33 @@ CompositeViewer &CompositeViewer::get() void CompositeViewer::update() { - mSimulationTime += mFrameTimer.time_s(); + double dt = mFrameTimer.time_s(); mFrameTimer.setStartTick(); + + emit simulationUpdated(dt); + + mSimulationTime += dt; frame(mSimulationTime); } // --------------------------------------------------- -SceneWidget::SceneWidget(boost::shared_ptr resourceSystem, QWidget *parent, Qt::WindowFlags f) +SceneWidget::SceneWidget(boost::shared_ptr resourceSystem, QWidget *parent, Qt::WindowFlags f, + bool retrieveInput) : RenderWidget(parent, f) , mResourceSystem(resourceSystem) , mLighting(NULL) , mHasDefaultAmbient(false) + , mPrevMouseX(0) + , mPrevMouseY(0) + , mCamPositionSet(false) { + mFreeCamControl = new FreeCameraController(this); + mOrbitCamControl = new OrbitCameraController(this); + mCurrentCamControl = mFreeCamControl; + + mOrbitCamControl->setPickingMask(Mask_Reference | Mask_Terrain); + // we handle lighting manually mView->setLightingMode(osgViewer::View::NO_LIGHT); @@ -173,14 +195,33 @@ SceneWidget::SceneWidget(boost::shared_ptr resourceSys mResourceSystem->getSceneManager()->setParticleSystemMask(Mask_ParticleSystem); - /// \todo make shortcut configurable - QShortcut *focusToolbar = new QShortcut (Qt::Key_T, this, 0, 0, Qt::WidgetWithChildrenShortcut); - connect (focusToolbar, SIGNAL (activated()), this, SIGNAL (focusToolbarRequest())); + // Recieve mouse move event even if mouse button is not pressed + setMouseTracking(true); + setFocusPolicy(Qt::ClickFocus); + + connect (&CSMPrefs::State::get(), SIGNAL (settingChanged (const CSMPrefs::Setting *)), + this, SLOT (settingChanged (const CSMPrefs::Setting *))); + + // TODO update this outside of the constructor where virtual methods can be used + if (retrieveInput) + { + CSMPrefs::get()["3D Scene Input"].update(); + CSMPrefs::get()["Tooltips"].update(); + } + + connect (&CompositeViewer::get(), SIGNAL (simulationUpdated(double)), this, SLOT (update(double))); + + // Shortcuts + CSMPrefs::Shortcut* focusToolbarShortcut = new CSMPrefs::Shortcut("scene-focus-toolbar", this); + connect(focusToolbarShortcut, SIGNAL(activated()), this, SIGNAL(focusToolbarRequest())); + + CSMPrefs::Shortcut* renderStatsShortcut = new CSMPrefs::Shortcut("scene-render-stats", this); + connect(renderStatsShortcut, SIGNAL(activated()), this, SLOT(toggleRenderStats())); } SceneWidget::~SceneWidget() { - // Since we're holding on to the scene templates past the existance of this graphics context, we'll need to manually release the created objects + // Since we're holding on to the scene templates past the existence of this graphics context, we'll need to manually release the created objects mResourceSystem->getSceneManager()->releaseGLObjects(mView->getCamera()->getGraphicsContext()->getState()); } @@ -255,4 +296,104 @@ void SceneWidget::setDefaultAmbient (const osg::Vec4f& colour) setAmbient(mLighting->getAmbientColour(&mDefaultAmbient)); } +void SceneWidget::mouseMoveEvent (QMouseEvent *event) +{ + mCurrentCamControl->handleMouseMoveEvent(event->x() - mPrevMouseX, event->y() - mPrevMouseY); + + mPrevMouseX = event->x(); + mPrevMouseY = event->y(); +} + +void SceneWidget::wheelEvent(QWheelEvent *event) +{ + mCurrentCamControl->handleMouseScrollEvent(event->delta()); +} + +void SceneWidget::update(double dt) +{ + if (mCamPositionSet) + { + mCurrentCamControl->update(dt); + } + else + { + mCurrentCamControl->setup(mRootNode, Mask_Reference | Mask_Terrain, CameraController::WorldUp); + mCamPositionSet = true; + } +} + +void SceneWidget::settingChanged (const CSMPrefs::Setting *setting) +{ + if (*setting=="3D Scene Input/p-navi-free-sensitivity") + { + mFreeCamControl->setCameraSensitivity(setting->toDouble()); + } + else if (*setting=="3D Scene Input/p-navi-orbit-sensitivity") + { + mOrbitCamControl->setCameraSensitivity(setting->toDouble()); + } + else if (*setting=="3D Scene Input/p-navi-free-invert") + { + mFreeCamControl->setInverted(setting->isTrue()); + } + else if (*setting=="3D Scene Input/p-navi-orbit-invert") + { + mOrbitCamControl->setInverted(setting->isTrue()); + } + else if (*setting=="3D Scene Input/s-navi-sensitivity") + { + mFreeCamControl->setSecondaryMovementMultiplier(setting->toDouble()); + mOrbitCamControl->setSecondaryMovementMultiplier(setting->toDouble()); + } + else if (*setting=="3D Scene Input/navi-wheel-factor") + { + mFreeCamControl->setWheelMovementMultiplier(setting->toDouble()); + mOrbitCamControl->setWheelMovementMultiplier(setting->toDouble()); + } + else if (*setting=="3D Scene Input/navi-free-lin-speed") + { + mFreeCamControl->setLinearSpeed(setting->toDouble()); + } + else if (*setting=="3D Scene Input/navi-free-rot-speed") + { + mFreeCamControl->setRotationalSpeed(setting->toDouble()); + } + else if (*setting=="3D Scene Input/navi-free-speed-mult") + { + mFreeCamControl->setSpeedMultiplier(setting->toDouble()); + } + else if (*setting=="3D Scene Input/navi-orbit-rot-speed") + { + mOrbitCamControl->setOrbitSpeed(setting->toDouble()); + } + else if (*setting=="3D Scene Input/navi-orbit-speed-mult") + { + mOrbitCamControl->setOrbitSpeedMultiplier(setting->toDouble()); + } +} + +void SceneWidget::selectNavigationMode (const std::string& mode) +{ + if (mode=="1st") + { + mCurrentCamControl->setCamera(NULL); + mCurrentCamControl = mFreeCamControl; + mFreeCamControl->setCamera(getCamera()); + mFreeCamControl->fixUpAxis(CameraController::WorldUp); + } + else if (mode=="free") + { + mCurrentCamControl->setCamera(NULL); + mCurrentCamControl = mFreeCamControl; + mFreeCamControl->setCamera(getCamera()); + mFreeCamControl->unfixUpAxis(); + } + else if (mode=="orbit") + { + mCurrentCamControl->setCamera(NULL); + mCurrentCamControl = mOrbitCamControl; + mOrbitCamControl->setCamera(getCamera()); + } +} + } diff --git a/apps/opencs/view/render/scenewidget.hpp b/apps/opencs/view/render/scenewidget.hpp index 63da01a45..c5d9cdfce 100644 --- a/apps/opencs/view/render/scenewidget.hpp +++ b/apps/opencs/view/render/scenewidget.hpp @@ -1,17 +1,21 @@ #ifndef OPENCS_VIEW_SCENEWIDGET_H #define OPENCS_VIEW_SCENEWIDGET_H +#include +#include + #include #include +#include +#include + #include #include "lightingday.hpp" #include "lightingnight.hpp" #include "lightingbright.hpp" -#include -#include namespace Resource { @@ -21,6 +25,7 @@ namespace Resource namespace osg { class Group; + class Camera; } namespace CSVWidget @@ -29,91 +34,128 @@ namespace CSVWidget class SceneToolbar; } +namespace CSMPrefs +{ + class Setting; +} + namespace CSVRender { + class CameraController; + class FreeCameraController; + class OrbitCameraController; class Lighting; class RenderWidget : public QWidget { - Q_OBJECT + Q_OBJECT + + public: + RenderWidget(QWidget* parent = 0, Qt::WindowFlags f = 0); + virtual ~RenderWidget(); - public: - RenderWidget(QWidget* parent = 0, Qt::WindowFlags f = 0); - virtual ~RenderWidget(); + /// Initiates a request to redraw the view + void flagAsModified(); - void flagAsModified(); + void setVisibilityMask(int mask); - void setVisibilityMask(int mask); + osg::Camera *getCamera(); - bool eventFilter(QObject *, QEvent *); + protected: - protected: + osg::ref_ptr mView; + osg::ref_ptr mRootNode; - osg::ref_ptr mView; + QTimer mTimer; - osg::Group* mRootNode; + protected slots: - QTimer mTimer; + void toggleRenderStats(); }; - // Extension of RenderWidget to support lighting mode selection & toolbar + /// Extension of RenderWidget to support lighting mode selection & toolbar class SceneWidget : public RenderWidget { - Q_OBJECT - public: - SceneWidget(boost::shared_ptr resourceSystem, QWidget* parent = 0, Qt::WindowFlags f = 0); - virtual ~SceneWidget(); + Q_OBJECT + public: + SceneWidget(boost::shared_ptr resourceSystem, QWidget* parent = 0, + Qt::WindowFlags f = 0, bool retrieveInput = true); + virtual ~SceneWidget(); - CSVWidget::SceneToolMode *makeLightingSelector (CSVWidget::SceneToolbar *parent); - ///< \attention The created tool is not added to the toolbar (via addTool). Doing that - /// is the responsibility of the calling function. + CSVWidget::SceneToolMode *makeLightingSelector (CSVWidget::SceneToolbar *parent); + ///< \attention The created tool is not added to the toolbar (via addTool). Doing that + /// is the responsibility of the calling function. - void setDefaultAmbient (const osg::Vec4f& colour); - ///< \note The actual ambient colour may differ based on lighting settings. + void setDefaultAmbient (const osg::Vec4f& colour); + ///< \note The actual ambient colour may differ based on lighting settings. - protected: - void setLighting (Lighting *lighting); - ///< \attention The ownership of \a lighting is not transferred to *this. + protected: + void setLighting (Lighting *lighting); + ///< \attention The ownership of \a lighting is not transferred to *this. - void setAmbient(const osg::Vec4f& ambient); + void setAmbient(const osg::Vec4f& ambient); - boost::shared_ptr mResourceSystem; + virtual void mouseMoveEvent (QMouseEvent *event); + virtual void wheelEvent (QWheelEvent *event); - Lighting* mLighting; + boost::shared_ptr mResourceSystem; - osg::Vec4f mDefaultAmbient; - bool mHasDefaultAmbient; - LightingDay mLightingDay; - LightingNight mLightingNight; - LightingBright mLightingBright; + Lighting* mLighting; - private slots: + osg::Vec4f mDefaultAmbient; + bool mHasDefaultAmbient; + LightingDay mLightingDay; + LightingNight mLightingNight; + LightingBright mLightingBright; - void selectLightingMode (const std::string& mode); + int mPrevMouseX, mPrevMouseY; + + /// Tells update that camera isn't set + bool mCamPositionSet; + + FreeCameraController* mFreeCamControl; + OrbitCameraController* mOrbitCamControl; + CameraController* mCurrentCamControl; + + public slots: + void update(double dt); + + protected slots: + + virtual void settingChanged (const CSMPrefs::Setting *setting); + + void selectNavigationMode (const std::string& mode); + + private slots: + + void selectLightingMode (const std::string& mode); signals: - void focusToolbarRequest(); + void focusToolbarRequest(); }; // There are rendering glitches when using multiple Viewer instances, work around using CompositeViewer with multiple views class CompositeViewer : public QObject, public osgViewer::CompositeViewer { - Q_OBJECT - public: - CompositeViewer(); + Q_OBJECT + public: + CompositeViewer(); - static CompositeViewer& get(); + static CompositeViewer& get(); - QTimer mTimer; + QTimer mTimer; - private: - osg::Timer mFrameTimer; - double mSimulationTime; + private: + osg::Timer mFrameTimer; + double mSimulationTime; - public slots: - void update(); + public slots: + void update(); + + signals: + void simulationUpdated(double dt); }; } diff --git a/apps/opencs/view/render/selectionmode.cpp b/apps/opencs/view/render/selectionmode.cpp new file mode 100644 index 000000000..cf0967e47 --- /dev/null +++ b/apps/opencs/view/render/selectionmode.cpp @@ -0,0 +1,83 @@ +#include "selectionmode.hpp" + +#include +#include + +#include "worldspacewidget.hpp" + +namespace CSVRender +{ + SelectionMode::SelectionMode(CSVWidget::SceneToolbar* parent, WorldspaceWidget& worldspaceWidget, + unsigned int interactionMask) + : SceneToolMode(parent, "Selection mode") + , mWorldspaceWidget(worldspaceWidget) + , mInteractionMask(interactionMask) + { + addButton(":placeholder", "cube-centre", + "Centred cube" + "

        • Drag with {scene-select-primary} (make instances the selection) or {scene-select-secondary} " + "(invert selection state) from the centre of the selection cube outwards
        • " + "
        • The selection cube is aligned to the word space axis
        • " + "
        • If context selection mode is enabled, a drag with {scene-edit-primary} or {scene-edit-secondary} not " + "starting on an instance will have the same effect
        • " + "
        " + "Not implemented yet"); + addButton(":placeholder", "cube-corner", + "Cube corner to corner" + "
        • Drag with {scene-select-primary} (make instances the selection) or {scene-select-secondary} " + "(invert selection state) from one corner of the selection cube to the opposite corner
        • " + "
        • The selection cube is aligned to the word space axis
        • " + "
        • If context selection mode is enabled, a drag with {scene-edit-primary} or {scene-edit-secondary} not " + "starting on an instance will have the same effect
        • " + "
        " + "Not implemented yet"); + addButton(":placeholder", "sphere", + "Centred sphere" + "
        • Drag with {scene-select-primary} (make instances the selection) or {scene-select-secondary} " + "(invert selection state) from the centre of the selection sphere outwards
        • " + "
        • If context selection mode is enabled, a drag with {scene-edit-primary} or {scene-edit-secondary} not " + "starting on an instance will have the same effect
        • " + "
        " + "Not implemented yet"); + + mSelectAll = new QAction("Select all", this); + mDeselectAll = new QAction("Clear selection", this); + mInvertSelection = new QAction("Invert selection", this); + + connect(mSelectAll, SIGNAL(triggered()), this, SLOT(selectAll())); + connect(mDeselectAll, SIGNAL(triggered()), this, SLOT(clearSelection())); + connect(mInvertSelection, SIGNAL(triggered()), this, SLOT(invertSelection())); + } + + WorldspaceWidget& SelectionMode::getWorldspaceWidget() + { + return mWorldspaceWidget; + } + + bool SelectionMode::createContextMenu (QMenu* menu) + { + if (menu) + { + menu->addAction(mSelectAll); + menu->addAction(mDeselectAll); + menu->addAction(mInvertSelection); + } + + return true; + } + + void SelectionMode::selectAll() + { + getWorldspaceWidget().selectAll(mInteractionMask); + } + + void SelectionMode::clearSelection() + { + getWorldspaceWidget().clearSelection(mInteractionMask); + } + + void SelectionMode::invertSelection() + { + getWorldspaceWidget().invertSelection(mInteractionMask); + } +} diff --git a/apps/opencs/view/render/selectionmode.hpp b/apps/opencs/view/render/selectionmode.hpp new file mode 100644 index 000000000..f28888bfd --- /dev/null +++ b/apps/opencs/view/render/selectionmode.hpp @@ -0,0 +1,51 @@ +#ifndef CSV_RENDER_SELECTION_MODE_H +#define CSV_RENDER_SELECTION_MODE_H + +#include "../widget/scenetoolmode.hpp" + +#include "mask.hpp" + +class QAction; + +namespace CSVRender +{ + class WorldspaceWidget; + + class SelectionMode : public CSVWidget::SceneToolMode + { + Q_OBJECT + + public: + + SelectionMode(CSVWidget::SceneToolbar* parent, WorldspaceWidget& worldspaceWidget, + unsigned int interactionMask); + + protected: + + WorldspaceWidget& getWorldspaceWidget(); + + /// Add context menu items to \a menu. + /// + /// \attention menu can be a 0-pointer + /// + /// \return Have there been any menu items to be added (if menu is 0 and there + /// items to be added, the function must return true anyway. + virtual bool createContextMenu (QMenu* menu); + + private: + + WorldspaceWidget& mWorldspaceWidget; + unsigned int mInteractionMask; + QAction* mSelectAll; + QAction* mDeselectAll; + QAction* mInvertSelection; + + protected slots: + + virtual void selectAll(); + virtual void clearSelection(); + virtual void invertSelection(); + }; +} + +#endif diff --git a/apps/opencs/view/render/unpagedworldspacewidget.cpp b/apps/opencs/view/render/unpagedworldspacewidget.cpp index 8d65c3694..03a97bf1a 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.cpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.cpp @@ -2,8 +2,6 @@ #include -#include - #include #include @@ -34,7 +32,7 @@ void CSVRender::UnpagedWorldspaceWidget::update() } CSVRender::UnpagedWorldspaceWidget::UnpagedWorldspaceWidget (const std::string& cellId, CSMDoc::Document& document, QWidget* parent) -: WorldspaceWidget (document, parent), mCellId (cellId) +: WorldspaceWidget (document, parent), mDocument(document), mCellId (cellId) { mCellsModel = &dynamic_cast ( *document.getData().getTableModel (CSMWorld::UniversalId::Type_Cells)); @@ -50,8 +48,6 @@ CSVRender::UnpagedWorldspaceWidget::UnpagedWorldspaceWidget (const std::string& update(); mCell.reset (new Cell (document.getData(), mRootNode, mCellId)); - - mView->setCameraManipulator(new osgGA::TrackballManipulator); } void CSVRender::UnpagedWorldspaceWidget::cellDataChanged (const QModelIndex& topLeft, @@ -84,20 +80,20 @@ void CSVRender::UnpagedWorldspaceWidget::cellRowsAboutToBeRemoved (const QModelI emit closeRequest(); } -bool CSVRender::UnpagedWorldspaceWidget::handleDrop (const std::vector& data, DropType type) +bool CSVRender::UnpagedWorldspaceWidget::handleDrop (const std::vector& universalIdData, DropType type) { - if (WorldspaceWidget::handleDrop (data, type)) + if (WorldspaceWidget::handleDrop (universalIdData, type)) return true; if (type!=Type_CellsInterior) return false; - mCellId = data.begin()->getId(); + mCellId = universalIdData.begin()->getId(); mCell.reset (new Cell (getDocument().getData(), mRootNode, mCellId)); update(); - emit cellChanged(*data.begin()); + emit cellChanged(*universalIdData.begin()); return true; } @@ -108,6 +104,12 @@ void CSVRender::UnpagedWorldspaceWidget::clearSelection (int elementMask) flagAsModified(); } +void CSVRender::UnpagedWorldspaceWidget::invertSelection (int elementMask) +{ + mCell->setSelection (elementMask, Cell::Selection_Invert); + flagAsModified(); +} + void CSVRender::UnpagedWorldspaceWidget::selectAll (int elementMask) { mCell->setSelection (elementMask, Cell::Selection_All); @@ -125,12 +127,33 @@ std::string CSVRender::UnpagedWorldspaceWidget::getCellId (const osg::Vec3f& poi return mCellId; } +CSVRender::Cell* CSVRender::UnpagedWorldspaceWidget::getCell(const osg::Vec3d& point) const +{ + return mCell.get(); +} + std::vector > CSVRender::UnpagedWorldspaceWidget::getSelection ( unsigned int elementMask) const { return mCell->getSelection (elementMask); } +std::vector > CSVRender::UnpagedWorldspaceWidget::getEdited ( + unsigned int elementMask) const +{ + return mCell->getEdited (elementMask); +} + +void CSVRender::UnpagedWorldspaceWidget::setSubMode (int subMode, unsigned int elementMask) +{ + mCell->setSubMode (subMode, elementMask); +} + +void CSVRender::UnpagedWorldspaceWidget::reset (unsigned int elementMask) +{ + mCell->reset (elementMask); +} + void CSVRender::UnpagedWorldspaceWidget::referenceableDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) { @@ -185,6 +208,75 @@ void CSVRender::UnpagedWorldspaceWidget::referenceAdded (const QModelIndex& pare flagAsModified(); } +void CSVRender::UnpagedWorldspaceWidget::pathgridDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) +{ + const CSMWorld::SubCellCollection& pathgrids = mDocument.getData().getPathgrids(); + + int rowStart = -1; + int rowEnd = -1; + + if (topLeft.parent().isValid()) + { + rowStart = topLeft.parent().row(); + rowEnd = bottomRight.parent().row(); + } + else + { + rowStart = topLeft.row(); + rowEnd = bottomRight.row(); + } + + for (int row = rowStart; row <= rowEnd; ++row) + { + const CSMWorld::Pathgrid& pathgrid = pathgrids.getRecord(row).get(); + if (mCellId == pathgrid.mId) + { + mCell->pathgridModified(); + flagAsModified(); + return; + } + } +} + +void CSVRender::UnpagedWorldspaceWidget::pathgridAboutToBeRemoved (const QModelIndex& parent, int start, int end) +{ + const CSMWorld::SubCellCollection& pathgrids = mDocument.getData().getPathgrids(); + + if (!parent.isValid()) + { + // Pathgrid going to be deleted + for (int row = start; row <= end; ++row) + { + const CSMWorld::Pathgrid& pathgrid = pathgrids.getRecord(row).get(); + if (mCellId == pathgrid.mId) + { + mCell->pathgridRemoved(); + flagAsModified(); + return; + } + } + } +} + +void CSVRender::UnpagedWorldspaceWidget::pathgridAdded (const QModelIndex& parent, int start, int end) +{ + const CSMWorld::SubCellCollection& pathgrids = mDocument.getData().getPathgrids(); + + if (!parent.isValid()) + { + for (int row = start; row <= end; ++row) + { + const CSMWorld::Pathgrid& pathgrid = pathgrids.getRecord(row).get(); + if (mCellId == pathgrid.mId) + { + mCell->pathgridModified(); + flagAsModified(); + return; + } + } + } +} + void CSVRender::UnpagedWorldspaceWidget::addVisibilitySelectorButtons ( CSVWidget::SceneToolToggle2 *tool) { diff --git a/apps/opencs/view/render/unpagedworldspacewidget.hpp b/apps/opencs/view/render/unpagedworldspacewidget.hpp index a4c517948..57e8d1a19 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.hpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.hpp @@ -25,6 +25,7 @@ namespace CSVRender { Q_OBJECT + CSMDoc::Document& mDocument; std::string mCellId; CSMWorld::IdTable *mCellsModel; CSMWorld::IdTable *mReferenceablesModel; @@ -46,6 +47,9 @@ namespace CSVRender /// \param elementMask Elements to be affected by the clear operation virtual void clearSelection (int elementMask); + /// \param elementMask Elements to be affected by the select operation + virtual void invertSelection (int elementMask); + /// \param elementMask Elements to be affected by the select operation virtual void selectAll (int elementMask); @@ -57,9 +61,19 @@ namespace CSVRender virtual std::string getCellId (const osg::Vec3f& point) const; + virtual Cell* getCell(const osg::Vec3d& point) const; + virtual std::vector > getSelection (unsigned int elementMask) const; + virtual std::vector > getEdited (unsigned int elementMask) + const; + + virtual void setSubMode (int subMode, unsigned int elementMask); + + /// Erase all overrides and restore the visual representation to its true state. + virtual void reset (unsigned int elementMask); + private: virtual void referenceableDataChanged (const QModelIndex& topLeft, @@ -75,6 +89,13 @@ namespace CSVRender virtual void referenceAdded (const QModelIndex& index, int start, int end); + virtual void pathgridDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); + + virtual void pathgridAboutToBeRemoved (const QModelIndex& parent, int start, int end); + + virtual void pathgridAdded (const QModelIndex& parent, int start, int end); + + virtual std::string getStartupInstruction(); protected: diff --git a/apps/opencs/view/render/worldspacewidget.cpp b/apps/opencs/view/render/worldspacewidget.cpp index 6ba6e9543..325fa5f6d 100644 --- a/apps/opencs/view/render/worldspacewidget.cpp +++ b/apps/opencs/view/render/worldspacewidget.cpp @@ -1,7 +1,6 @@ #include "worldspacewidget.hpp" #include -#include #include #include @@ -12,30 +11,46 @@ #include #include -#include -#include - #include #include "../../model/world/universalid.hpp" #include "../../model/world/idtable.hpp" +#include "../../model/prefs/shortcut.hpp" +#include "../../model/prefs/shortcuteventhandler.hpp" #include "../../model/prefs/state.hpp" +#include "../render/orbitcameramode.hpp" + #include "../widget/scenetoolmode.hpp" #include "../widget/scenetooltoggle2.hpp" #include "../widget/scenetoolrun.hpp" #include "object.hpp" #include "mask.hpp" -#include "editmode.hpp" #include "instancemode.hpp" +#include "pathgridmode.hpp" +#include "cameracontroller.hpp" CSVRender::WorldspaceWidget::WorldspaceWidget (CSMDoc::Document& document, QWidget* parent) -: SceneWidget (document.getData().getResourceSystem(), parent), mSceneElements(0), mRun(0), mDocument(document), - mInteractionMask (0), mEditMode (0), mLocked (false), mDragging (false), mDragX(0), mDragY(0), mDragFactor(0), - mDragWheelFactor(0), mDragShiftFactor(0), - mToolTipPos (-1, -1), mShowToolTips(false), mToolTipDelay(0) + : SceneWidget (document.getData().getResourceSystem(), parent, 0, false) + , mSceneElements(0) + , mRun(0) + , mDocument(document) + , mInteractionMask (0) + , mEditMode (0) + , mLocked (false) + , mDragMode(InteractionType_None) + , mDragging (false) + , mDragX(0) + , mDragY(0) + , mSpeedMode(false) + , mDragFactor(0) + , mDragWheelFactor(0) + , mDragShiftFactor(0) + , mToolTipPos (-1, -1) + , mShowToolTips(false) + , mToolTipDelay(0) { setAcceptDrops(true); @@ -59,6 +74,15 @@ CSVRender::WorldspaceWidget::WorldspaceWidget (CSMDoc::Document& document, QWidg connect (references, SIGNAL (rowsInserted (const QModelIndex&, int, int)), this, SLOT (referenceAdded (const QModelIndex&, int, int))); + QAbstractItemModel *pathgrids = document.getData().getTableModel (CSMWorld::UniversalId::Type_Pathgrids); + + connect (pathgrids, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), + this, SLOT (pathgridDataChanged (const QModelIndex&, const QModelIndex&))); + connect (pathgrids, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), + this, SLOT (pathgridAboutToBeRemoved (const QModelIndex&, int, int))); + connect (pathgrids, SIGNAL (rowsInserted (const QModelIndex&, int, int)), + this, SLOT (pathgridAdded (const QModelIndex&, int, int))); + QAbstractItemModel *debugProfiles = document.getData().getTableModel (CSMWorld::UniversalId::Type_DebugProfiles); @@ -67,13 +91,29 @@ CSVRender::WorldspaceWidget::WorldspaceWidget (CSMDoc::Document& document, QWidg connect (debugProfiles, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), this, SLOT (debugProfileAboutToBeRemoved (const QModelIndex&, int, int))); - connect (&CSMPrefs::State::get(), SIGNAL (settingChanged (const CSMPrefs::Setting *)), - this, SLOT (settingChanged (const CSMPrefs::Setting *))); + mToolTipDelayTimer.setSingleShot (true); + connect (&mToolTipDelayTimer, SIGNAL (timeout()), this, SLOT (showToolTip())); + CSMPrefs::get()["3D Scene Input"].update(); CSMPrefs::get()["Tooltips"].update(); - mToolTipDelayTimer.setSingleShot (true); - connect (&mToolTipDelayTimer, SIGNAL (timeout()), this, SLOT (showToolTip())); + // Shortcuts + CSMPrefs::Shortcut* primaryEditShortcut = new CSMPrefs::Shortcut("scene-edit-primary", "scene-speed-modifier", + CSMPrefs::Shortcut::SM_Detach, this); + connect(primaryEditShortcut, SIGNAL(activated(bool)), this, SLOT(primaryEdit(bool))); + connect(primaryEditShortcut, SIGNAL(secondary(bool)), this, SLOT(speedMode(bool))); + + CSMPrefs::Shortcut* secondaryEditShortcut = new CSMPrefs::Shortcut("scene-edit-secondary", this); + connect(secondaryEditShortcut, SIGNAL(activated(bool)), this, SLOT(secondaryEdit(bool))); + + CSMPrefs::Shortcut* primarySelectShortcut = new CSMPrefs::Shortcut("scene-select-primary", this); + connect(primarySelectShortcut, SIGNAL(activated(bool)), this, SLOT(primarySelect(bool))); + + CSMPrefs::Shortcut* secondarySelectShortcut = new CSMPrefs::Shortcut("scene-select-secondary", this); + connect(secondarySelectShortcut, SIGNAL(activated(bool)), this, SLOT(secondarySelect(bool))); + + CSMPrefs::Shortcut* abortShortcut = new CSMPrefs::Shortcut("scene-edit-abort", this); + connect(abortShortcut, SIGNAL(activated()), this, SLOT(abortDrag())); } CSVRender::WorldspaceWidget::~WorldspaceWidget () @@ -82,9 +122,6 @@ CSVRender::WorldspaceWidget::~WorldspaceWidget () void CSVRender::WorldspaceWidget::settingChanged (const CSMPrefs::Setting *setting) { - if (storeMappingSetting (setting)) - return; - if (*setting=="3D Scene Input/drag-factor") mDragFactor = setting->toDouble(); else if (*setting=="3D Scene Input/drag-wheel-factor") @@ -95,23 +132,29 @@ void CSVRender::WorldspaceWidget::settingChanged (const CSMPrefs::Setting *setti mToolTipDelay = setting->toInt(); else if (*setting=="Tooltips/scene") mShowToolTips = setting->isTrue(); + else + SceneWidget::settingChanged(setting); } -void CSVRender::WorldspaceWidget::selectNavigationMode (const std::string& mode) -{ - if (mode=="1st") - mView->setCameraManipulator(new osgGA::FirstPersonManipulator); - else if (mode=="free") - mView->setCameraManipulator(new osgGA::FirstPersonManipulator); - else if (mode=="orbit") - mView->setCameraManipulator(new osgGA::OrbitManipulator); -} void CSVRender::WorldspaceWidget::useViewHint (const std::string& hint) {} void CSVRender::WorldspaceWidget::selectDefaultNavigationMode() { - mView->setCameraManipulator(new osgGA::FirstPersonManipulator); + selectNavigationMode("1st"); +} + +void CSVRender::WorldspaceWidget::centerOrbitCameraOnSelection() +{ + std::vector > selection = getSelection(~0); + + for (std::vector >::iterator it = selection.begin(); it!=selection.end(); ++it) + { + if (CSVRender::ObjectTag *objectTag = dynamic_cast (it->get())) + { + mOrbitCamControl->setCenter(objectTag->mObject->getPosition().asVec3()); + } + } } CSVWidget::SceneToolMode *CSVRender::WorldspaceWidget::makeNavigationSelector ( @@ -123,30 +166,34 @@ CSVWidget::SceneToolMode *CSVRender::WorldspaceWidget::makeNavigationSelector ( /// \todo consider user-defined button-mapping tool->addButton (":scenetoolbar/1st-person", "1st", "First Person" - "
        • Mouse-Look while holding the left button
        • " - "
        • WASD movement keys
        • " + "
          • Camera is held upright
          • " + "
          • Mouse-Look while holding {scene-navi-primary}
          • " + "
          • Movement keys: {free-forward}(forward), {free-left}(left), {free-backward}(back), {free-right}(right)
          • " + "
          • Strafing (also vertically) by holding {scene-navi-secondary}
          • " "
          • Mouse wheel moves the camera forward/backward
          • " - "
          • Strafing (also vertically) by holding the left mouse button and control
          • " - "
          • Camera is held upright
          • " - "
          • Hold shift to speed up movement
          • " + "
          • Hold {scene-speed-modifier} to speed up movement
          • " "
          "); tool->addButton (":scenetoolbar/free-camera", "free", "Free Camera" - "
          • Mouse-Look while holding the left button
          • " - "
          • Strafing (also vertically) via WASD or by holding the left mouse button and control
          • " + "
            • Mouse-Look while holding {scene-navi-primary}
            • " + "
            • Movement keys: {free-forward}(forward), {free-left}(left), {free-backward}(back), {free-right}(right)
            • " + "
            • Roll camera with {free-roll-left} and {free-roll-right} keys
            • " + "
            • Strafing (also vertically) by holding {scene-navi-secondary}
            • " "
            • Mouse wheel moves the camera forward/backward
            • " - "
            • Roll camera with Q and E keys
            • " - "
            • Hold shift to speed up movement
            • " - "
            "); - tool->addButton (":scenetoolbar/orbiting-camera", "orbit", - "Orbiting Camera" - "
            • Always facing the centre point
            • " - "
            • Rotate around the centre point via WASD or by moving the mouse while holding the left button
            • " - "
            • Mouse wheel moves camera away or towards centre point but can not pass through it
            • " - "
            • Roll camera with Q and E keys
            • " - "
            • Strafing (also vertically) by holding the left mouse button and control (includes relocation of the centre point)
            • " - "
            • Hold shift to speed up movement
            • " + "
            • Hold {free-forward:mod} to speed up movement
            • " "
            "); + tool->addButton( + new CSVRender::OrbitCameraMode(this, QIcon(":scenetoolbar/orbiting-camera"), + "Orbiting Camera" + "
            • Always facing the centre point
            • " + "
            • Rotate around the centre point via {orbit-up}, {orbit-left}, {orbit-down}, {orbit-right} or by moving " + "the mouse while holding {scene-navi-primary}
            • " + "
            • Roll camera with {orbit-roll-left} and {orbit-roll-right} keys
            • " + "
            • Strafing (also vertically) by holding {scene-navi-secondary} (includes relocation of the centre point)
            • " + "
            • Mouse wheel moves camera away or towards centre point but can not pass through it
            • " + "
            • Hold {scene-speed-modifier} to speed up movement
            • " + "
            ", tool), + "orbit"); connect (tool, SIGNAL (modeChanged (const std::string&)), this, SLOT (selectNavigationMode (const std::string&))); @@ -256,15 +303,15 @@ CSVRender::WorldspaceWidget::dropRequirments return ignored; } -bool CSVRender::WorldspaceWidget::handleDrop (const std::vector& data, +bool CSVRender::WorldspaceWidget::handleDrop (const std::vector& universalIdData, DropType type) { if (type==Type_DebugProfile) { if (mRun) { - for (std::vector::const_iterator iter (data.begin()); - iter!=data.end(); ++iter) + for (std::vector::const_iterator iter (universalIdData.begin()); + iter!=universalIdData.end(); ++iter) mRun->addProfile (iter->getId()); } @@ -306,9 +353,7 @@ void CSVRender::WorldspaceWidget::addEditModeSelectorButtons (CSVWidget::SceneTo { /// \todo replace EditMode with suitable subclasses tool->addButton (new InstanceMode (this, tool), "object"); - tool->addButton ( - new EditMode (this, QIcon (":placeholder"), Mask_Pathgrid, "Pathgrid editing"), - "pathgrid"); + tool->addButton (new PathgridMode (this, tool), "pathgrid"); } CSMDoc::Document& CSVRender::WorldspaceWidget::getDocument() @@ -316,52 +361,92 @@ CSMDoc::Document& CSVRender::WorldspaceWidget::getDocument() return mDocument; } -osg::Vec3f CSVRender::WorldspaceWidget::getIntersectionPoint (const QPoint& localPos, - unsigned int interactionMask, bool ignoreHidden) const +CSVRender::WorldspaceHitResult CSVRender::WorldspaceWidget::mousePick (const QPoint& localPos, + unsigned int interactionMask) const { // (0,0) is considered the lower left corner of an OpenGL window int x = localPos.x(); int y = height() - localPos.y(); - osg::ref_ptr intersector ( - new osgUtil::LineSegmentIntersector (osgUtil::Intersector::WINDOW, x, y)); + // Convert from screen space to world space + osg::Matrixd wpvMat; + wpvMat.preMult (mView->getCamera()->getViewport()->computeWindowMatrix()); + wpvMat.preMult (mView->getCamera()->getProjectionMatrix()); + wpvMat.preMult (mView->getCamera()->getViewMatrix()); + wpvMat = osg::Matrixd::inverse (wpvMat); - intersector->setIntersectionLimit (osgUtil::LineSegmentIntersector::NO_LIMIT); - osgUtil::IntersectionVisitor visitor (intersector); + osg::Vec3d start = wpvMat.preMult (osg::Vec3d(x, y, 0)); + osg::Vec3d end = wpvMat.preMult (osg::Vec3d(x, y, 1)); + osg::Vec3d direction = end - start; - unsigned int mask = interactionMask; + // Get intersection + osg::ref_ptr intersector (new osgUtil::LineSegmentIntersector( + osgUtil::Intersector::MODEL, start, end)); - if (ignoreHidden) - mask &= getVisibilityMask(); + intersector->setIntersectionLimit(osgUtil::LineSegmentIntersector::NO_LIMIT); + osgUtil::IntersectionVisitor visitor(intersector); - visitor.setTraversalMask (mask); + visitor.setTraversalMask(interactionMask); - mView->getCamera()->accept (visitor); + mView->getCamera()->accept(visitor); - for (osgUtil::LineSegmentIntersector::Intersections::iterator iter = intersector->getIntersections().begin(); - iter!=intersector->getIntersections().end(); ++iter) + // Get relevant data + for (osgUtil::LineSegmentIntersector::Intersections::iterator it = intersector->getIntersections().begin(); + it != intersector->getIntersections().end(); ++it) { + osgUtil::LineSegmentIntersector::Intersection intersection = *it; + // reject back-facing polygons - osg::Vec3f normal = osg::Matrix::transform3x3 ( - iter->getWorldIntersectNormal(), mView->getCamera()->getViewMatrix()); + if (direction * intersection.getWorldIntersectNormal() > 0) + { + continue; + } + + for (std::vector::iterator nodeIter = intersection.nodePath.begin(); nodeIter != intersection.nodePath.end(); ++nodeIter) + { + osg::Node* node = *nodeIter; + if (osg::ref_ptr tag = dynamic_cast(node->getUserData())) + { + WorldspaceHitResult hit = { true, tag, 0, 0, 0, intersection.getWorldIntersectPoint() }; + if (intersection.indexList.size() >= 3) + { + hit.index0 = intersection.indexList[0]; + hit.index1 = intersection.indexList[1]; + hit.index2 = intersection.indexList[2]; + } + return hit; + } + } - if (normal.z()>=0) - return iter->getWorldIntersectPoint(); + // Something untagged, probably terrain + WorldspaceHitResult hit = { true, 0, 0, 0, 0, intersection.getWorldIntersectPoint() }; + if (intersection.indexList.size() >= 3) + { + hit.index0 = intersection.indexList[0]; + hit.index1 = intersection.indexList[1]; + hit.index2 = intersection.indexList[2]; + } + return hit; } - osg::Matrixd matrix; - matrix.preMult (mView->getCamera()->getViewport()->computeWindowMatrix()); - matrix.preMult (mView->getCamera()->getProjectionMatrix()); - matrix.preMult (mView->getCamera()->getViewMatrix()); - matrix = osg::Matrixd::inverse (matrix); + // Default placement + direction.normalize(); + direction *= CSMPrefs::get()["Scene Drops"]["distance"].toInt(); - osg::Vec3d start = matrix.preMult (intersector->getStart()); - osg::Vec3d end = matrix.preMult (intersector->getEnd()); + WorldspaceHitResult hit = { false, 0, 0, 0, 0, start + direction }; + return hit; +} - osg::Vec3d direction = end-start; - direction.normalize(); +void CSVRender::WorldspaceWidget::abortDrag() +{ + if (mDragging) + { + EditMode& editMode = dynamic_cast (*mEditMode->getCurrent()); - return start + direction * CSMPrefs::get()["Scene Drops"]["distance"].toInt(); + editMode.dragAborted(); + mDragging = false; + mDragMode = InteractionType_None; + } } void CSVRender::WorldspaceWidget::dragEnterEvent (QDragEnterEvent* event) @@ -404,100 +489,6 @@ void CSVRender::WorldspaceWidget::dragMoveEvent(QDragMoveEvent *event) } } -bool CSVRender::WorldspaceWidget::storeMappingSetting (const CSMPrefs::Setting *setting) -{ - if (setting->getParent()->getKey()!="3D Scene Input") - return false; - - static const char * const sMappingSettings[] = - { - "p-navi", "s-navi", - "p-edit", "s-edit", - "p-select", "s-select", - 0 - }; - - for (int i=0; sMappingSettings[i]; ++i) - if (setting->getKey()==sMappingSettings[i]) - { - QString value = QString::fromUtf8 (setting->toString().c_str()); - - Qt::MouseButton button = Qt::NoButton; - - if (value.endsWith ("Left Mouse-Button")) - button = Qt::LeftButton; - else if (value.endsWith ("Right Mouse-Button")) - button = Qt::RightButton; - else if (value.endsWith ("Middle Mouse-Button")) - button = Qt::MiddleButton; - else - return false; - - bool ctrl = value.startsWith ("Ctrl-"); - - mButtonMapping[std::make_pair (button, ctrl)] = sMappingSettings[i]; - return true; - } - - return false; -} - -osg::ref_ptr CSVRender::WorldspaceWidget::mousePick (const QPoint& localPos) -{ - // (0,0) is considered the lower left corner of an OpenGL window - int x = localPos.x(); - int y = height() - localPos.y(); - - osg::ref_ptr intersector (new osgUtil::LineSegmentIntersector(osgUtil::Intersector::WINDOW, x, y)); - - intersector->setIntersectionLimit(osgUtil::LineSegmentIntersector::NO_LIMIT); - osgUtil::IntersectionVisitor visitor(intersector); - - visitor.setTraversalMask(getInteractionMask()); - - mView->getCamera()->accept(visitor); - - for (osgUtil::LineSegmentIntersector::Intersections::iterator it = intersector->getIntersections().begin(); - it != intersector->getIntersections().end(); ++it) - { - osgUtil::LineSegmentIntersector::Intersection intersection = *it; - - // reject back-facing polygons - osg::Vec3f normal = intersection.getWorldIntersectNormal(); - normal = osg::Matrix::transform3x3(normal, mView->getCamera()->getViewMatrix()); - if (normal.z() < 0) - continue; - - for (std::vector::iterator it = intersection.nodePath.begin(); it != intersection.nodePath.end(); ++it) - { - osg::Node* node = *it; - if (osg::ref_ptr tag = dynamic_cast(node->getUserData())) - return tag; - } - -// ignoring terrain for now - // must be terrain, report coordinates -// std::cout << "Terrain hit at " << intersection.getWorldIntersectPoint().x() << " " << intersection.getWorldIntersectPoint().y() << std::endl; -// return; - } - - return osg::ref_ptr(); -} - -std::string CSVRender::WorldspaceWidget::mapButton (QMouseEvent *event) -{ - std::pair phyiscal ( - event->button(), event->modifiers() & Qt::ControlModifier); - - std::map, std::string>::const_iterator iter = - mButtonMapping.find (phyiscal); - - if (iter!=mButtonMapping.end()) - return iter->second; - - return ""; -} - void CSVRender::WorldspaceWidget::dropEvent (QDropEvent* event) { const CSMWorld::TableMimeData* mime = dynamic_cast (event->mimeData()); @@ -573,6 +564,7 @@ void CSVRender::WorldspaceWidget::editModeChanged (const std::string& id) { dynamic_cast (*mEditMode->getCurrent()).setEditLock (mLocked); mDragging = false; + mDragMode = InteractionType_None; } void CSVRender::WorldspaceWidget::showToolTip() @@ -581,10 +573,11 @@ void CSVRender::WorldspaceWidget::showToolTip() { QPoint pos = QCursor::pos(); - if (osg::ref_ptr tag = mousePick (mapFromGlobal (pos))) + WorldspaceHitResult hit = mousePick (mapFromGlobal (pos), getInteractionMask()); + if (hit.tag) { bool hideBasics = CSMPrefs::get()["Tooltips"]["scene-hide-basic"].isTrue(); - QToolTip::showText (pos, tag->getToolTip (hideBasics), this); + QToolTip::showText (pos, hit.tag->getToolTip (hideBasics), this); } } } @@ -602,50 +595,7 @@ void CSVRender::WorldspaceWidget::updateOverlay() void CSVRender::WorldspaceWidget::mouseMoveEvent (QMouseEvent *event) { - if (!mDragging) - { - if (mDragMode.empty()) - { - if (event->globalPos()!=mToolTipPos) - { - mToolTipPos = event->globalPos(); - - if (mShowToolTips) - mToolTipDelayTimer.start (mToolTipDelay); - } - } - else if (mDragMode=="p-navi" || mDragMode=="s-navi") - { - - } - else if (mDragMode=="p-edit" || mDragMode=="s-edit" || mDragMode=="p-select" || mDragMode=="s-select") - { - osg::ref_ptr tag = mousePick (event->pos()); - - EditMode& editMode = dynamic_cast (*mEditMode->getCurrent()); - - if (mDragMode=="p-edit") - mDragging = editMode.primaryEditStartDrag (tag); - else if (mDragMode=="s-edit") - mDragging = editMode.secondaryEditStartDrag (tag); - else if (mDragMode=="p-select") - mDragging = editMode.primarySelectStartDrag (tag); - else if (mDragMode=="s-select") - mDragging = editMode.secondarySelectStartDrag (tag); - - if (mDragging) - { -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) - mDragX = event->localPos().x(); - mDragY = height() - event->localPos().y(); -#else - mDragX = event->posF().x(); - mDragY = height() - event->posF().y(); -#endif - } - } - } - else + if (mDragging) { int diffX = event->x() - mDragX; int diffY = (height() - event->y()) - mDragY; @@ -655,65 +605,51 @@ void CSVRender::WorldspaceWidget::mouseMoveEvent (QMouseEvent *event) double factor = mDragFactor; - if (event->modifiers() & Qt::ShiftModifier) + if (mSpeedMode) factor *= mDragShiftFactor; EditMode& editMode = dynamic_cast (*mEditMode->getCurrent()); - editMode.drag (diffX, diffY, factor); + editMode.drag (event->pos(), diffX, diffY, factor); } -} - -void CSVRender::WorldspaceWidget::mousePressEvent (QMouseEvent *event) -{ - std::string button = mapButton (event); - - if (!mDragging) - mDragMode = button; -} - -void CSVRender::WorldspaceWidget::mouseReleaseEvent (QMouseEvent *event) -{ - std::string button = mapButton (event); - - if (mDragging) + else if (mDragMode != InteractionType_None) { - if (mDragMode=="p-navi" || mDragMode=="s-navi") - { + EditMode& editMode = dynamic_cast (*mEditMode->getCurrent()); - } - else if (mDragMode=="p-edit" || mDragMode=="s-edit" || - mDragMode=="p-select" || mDragMode=="s-select") - { - EditMode& editMode = dynamic_cast (*mEditMode->getCurrent()); + if (mDragMode == InteractionType_PrimaryEdit) + mDragging = editMode.primaryEditStartDrag (event->pos()); + else if (mDragMode == InteractionType_SecondaryEdit) + mDragging = editMode.secondaryEditStartDrag (event->pos()); + else if (mDragMode == InteractionType_PrimarySelect) + mDragging = editMode.primarySelectStartDrag (event->pos()); + else if (mDragMode == InteractionType_SecondarySelect) + mDragging = editMode.secondarySelectStartDrag (event->pos()); - editMode.dragCompleted(); - mDragging = false; + if (mDragging) + { +#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) + mDragX = event->localPos().x(); + mDragY = height() - event->localPos().y(); +#else + mDragX = event->posF().x(); + mDragY = height() - event->posF().y(); +#endif } } else { - if (button=="p-navi" || button=="s-navi") - { - - } - else if (button=="p-edit" || button=="s-edit" || - button=="p-select" || button=="s-select") + if (event->globalPos()!=mToolTipPos) { - osg::ref_ptr tag = mousePick (event->pos()); + mToolTipPos = event->globalPos(); - handleMouseClick (tag, button, event->modifiers() & Qt::ShiftModifier); + if (mShowToolTips) + { + QToolTip::hideText(); + mToolTipDelayTimer.start (mToolTipDelay); + } } - } - - mDragMode.clear(); -} -void CSVRender::WorldspaceWidget::mouseDoubleClickEvent (QMouseEvent *event) -{ - if(event->button() == Qt::RightButton) - { - //mMouse->mouseDoubleClickEvent(event); + SceneWidget::mouseMoveEvent(event); } } @@ -723,41 +659,82 @@ void CSVRender::WorldspaceWidget::wheelEvent (QWheelEvent *event) { double factor = mDragWheelFactor; - if (event->modifiers() & Qt::ShiftModifier) + if (mSpeedMode) factor *= mDragShiftFactor; EditMode& editMode = dynamic_cast (*mEditMode->getCurrent()); editMode.dragWheel (event->delta(), factor); } + else + SceneWidget::wheelEvent(event); +} + +void CSVRender::WorldspaceWidget::handleInteractionPress (const WorldspaceHitResult& hit, InteractionType type) +{ + EditMode& editMode = dynamic_cast (*mEditMode->getCurrent()); + + if (type == InteractionType_PrimaryEdit) + editMode.primaryEditPressed (hit); + else if (type == InteractionType_SecondaryEdit) + editMode.secondaryEditPressed (hit); + else if (type == InteractionType_PrimarySelect) + editMode.primarySelectPressed (hit); + else if (type == InteractionType_SecondarySelect) + editMode.secondarySelectPressed (hit); +} + +CSVRender::EditMode *CSVRender::WorldspaceWidget::getEditMode() +{ + return dynamic_cast (mEditMode->getCurrent()); +} + +void CSVRender::WorldspaceWidget::primaryEdit(bool activate) +{ + handleInteraction(InteractionType_PrimaryEdit, activate); +} + +void CSVRender::WorldspaceWidget::secondaryEdit(bool activate) +{ + handleInteraction(InteractionType_SecondaryEdit, activate); } -void CSVRender::WorldspaceWidget::keyPressEvent (QKeyEvent *event) +void CSVRender::WorldspaceWidget::primarySelect(bool activate) { - if(event->key() == Qt::Key_Escape) + handleInteraction(InteractionType_PrimarySelect, activate); +} + +void CSVRender::WorldspaceWidget::secondarySelect(bool activate) +{ + handleInteraction(InteractionType_SecondarySelect, activate); +} + +void CSVRender::WorldspaceWidget::speedMode(bool activate) +{ + mSpeedMode = activate; +} + +void CSVRender::WorldspaceWidget::handleInteraction(InteractionType type, bool activate) +{ + if (activate) + { + if (!mDragging) + mDragMode = type; + } + else { + mDragMode = InteractionType_None; + if (mDragging) { - EditMode& editMode = dynamic_cast (*mEditMode->getCurrent()); - - editMode.dragAborted(); + EditMode* editMode = getEditMode(); + editMode->dragCompleted(mapFromGlobal(QCursor::pos())); mDragging = false; } + else + { + WorldspaceHitResult hit = mousePick(mapFromGlobal(QCursor::pos()), getInteractionMask()); + handleInteractionPress(hit, type); + } } - else - RenderWidget::keyPressEvent(event); -} - -void CSVRender::WorldspaceWidget::handleMouseClick (osg::ref_ptr tag, const std::string& button, bool shift) -{ - EditMode& editMode = dynamic_cast (*mEditMode->getCurrent()); - - if (button=="p-edit") - editMode.primaryEditPressed (tag); - else if (button=="s-edit") - editMode.secondaryEditPressed (tag); - else if (button=="p-select") - editMode.primarySelectPressed (tag); - else if (button=="s-select") - editMode.secondarySelectPressed (tag); } diff --git a/apps/opencs/view/render/worldspacewidget.hpp b/apps/opencs/view/render/worldspacewidget.hpp index ac6426b42..b30d7de8a 100644 --- a/apps/opencs/view/render/worldspacewidget.hpp +++ b/apps/opencs/view/render/worldspacewidget.hpp @@ -1,11 +1,10 @@ #ifndef OPENCS_VIEW_WORLDSPACEWIDGET_H #define OPENCS_VIEW_WORLDSPACEWIDGET_H -#include - #include #include +#include #include "../../model/doc/document.hpp" #include "../../model/world/tablemimedata.hpp" @@ -34,7 +33,17 @@ namespace CSVWidget namespace CSVRender { class TagBase; + class Cell; class CellArrow; + class EditMode; + + struct WorldspaceHitResult + { + bool hit; + osg::ref_ptr tag; + unsigned int index0, index1, index2; // indices of mesh vertices + osg::Vec3d worldPos; + }; class WorldspaceWidget : public SceneWidget { @@ -44,13 +53,13 @@ namespace CSVRender CSVWidget::SceneToolRun *mRun; CSMDoc::Document& mDocument; unsigned int mInteractionMask; - std::map, std::string> mButtonMapping; CSVWidget::SceneToolMode *mEditMode; bool mLocked; - std::string mDragMode; + int mDragMode; bool mDragging; int mDragX; int mDragY; + bool mSpeedMode; double mDragFactor; double mDragWheelFactor; double mDragShiftFactor; @@ -77,6 +86,15 @@ namespace CSVRender ignored //either mixed cells, or not cells }; + enum InteractionType + { + InteractionType_PrimaryEdit, + InteractionType_PrimarySelect, + InteractionType_SecondaryEdit, + InteractionType_SecondarySelect, + InteractionType_None + }; + WorldspaceWidget (CSMDoc::Document& document, QWidget *parent = 0); ~WorldspaceWidget (); @@ -99,6 +117,8 @@ namespace CSVRender void selectDefaultNavigationMode(); + void centerOrbitCameraOnSelection(); + static DropType getDropType(const std::vector& data); virtual dropRequirments getDropRequirements(DropType type) const; @@ -127,6 +147,9 @@ namespace CSVRender /// \param elementMask Elements to be affected by the clear operation virtual void clearSelection (int elementMask) = 0; + /// \param elementMask Elements to be affected by the select operation + virtual void invertSelection (int elementMask) = 0; + /// \param elementMask Elements to be affected by the select operation virtual void selectAll (int elementMask) = 0; @@ -136,22 +159,28 @@ namespace CSVRender /// \param elementMask Elements to be affected by the select operation virtual void selectAllWithSameParentId (int elementMask) = 0; - /// Return the next intersection point with scene elements matched by + /// Return the next intersection with scene elements matched by /// \a interactionMask based on \a localPos and the camera vector. - /// If there is no such point, instead a point "in front" of \a localPos will be + /// If there is no such intersection, instead a point "in front" of \a localPos will be /// returned. - /// - /// \param ignoreHidden ignore elements specified in interactionMask that are - /// flagged as not visible. - osg::Vec3f getIntersectionPoint (const QPoint& localPos, - unsigned int interactionMask = Mask_Reference | Mask_Terrain, - bool ignoreHidden = false) const; + WorldspaceHitResult mousePick (const QPoint& localPos, unsigned int interactionMask) const; virtual std::string getCellId (const osg::Vec3f& point) const = 0; + /// \note Returns the cell if it exists, otherwise a null pointer + virtual Cell* getCell(const osg::Vec3d& point) const = 0; + virtual std::vector > getSelection (unsigned int elementMask) const = 0; + virtual std::vector > getEdited (unsigned int elementMask) + const = 0; + + virtual void setSubMode (int subMode, unsigned int elementMask) = 0; + + /// Erase all overrides and restore the visual representation to its true state. + virtual void reset (unsigned int elementMask) = 0; + protected: /// Visual elements in a scene @@ -172,14 +201,15 @@ namespace CSVRender virtual void updateOverlay(); virtual void mouseMoveEvent (QMouseEvent *event); - virtual void mousePressEvent (QMouseEvent *event); - virtual void mouseReleaseEvent (QMouseEvent *event); - virtual void mouseDoubleClickEvent (QMouseEvent *event); virtual void wheelEvent (QWheelEvent *event); - virtual void keyPressEvent (QKeyEvent *event); - virtual void handleMouseClick (osg::ref_ptr tag, const std::string& button, - bool shift); + virtual void handleInteractionPress (const WorldspaceHitResult& hit, InteractionType type); + + virtual void settingChanged (const CSMPrefs::Setting *setting); + + EditMode *getEditMode(); + + bool getSpeedMode(); private: @@ -189,21 +219,20 @@ namespace CSVRender void dragMoveEvent(QDragMoveEvent *event); - /// \return Is \a key a button mapping setting? (ignored otherwise) - bool storeMappingSetting (const CSMPrefs::Setting *setting); + virtual std::string getStartupInstruction() = 0; - osg::ref_ptr mousePick (const QPoint& localPos); + void handleInteraction(InteractionType type, bool activate); - std::string mapButton (QMouseEvent *event); + public slots: - virtual std::string getStartupInstruction() = 0; + /// \note Drags will be automatically aborted when the aborting is triggered + /// (either explicitly or implicitly) from within this class. This function only + /// needs to be called, when the drag abort is triggered externally (e.g. from + /// an edit mode). + void abortDrag(); private slots: - void settingChanged (const CSMPrefs::Setting *setting); - - void selectNavigationMode (const std::string& mode); - virtual void referenceableDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) = 0; @@ -217,6 +246,13 @@ namespace CSVRender virtual void referenceAdded (const QModelIndex& index, int start, int end) = 0; + virtual void pathgridDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) = 0; + + virtual void pathgridAboutToBeRemoved (const QModelIndex& parent, int start, int end) = 0; + + virtual void pathgridAdded (const QModelIndex& parent, int start, int end) = 0; + + virtual void runRequest (const std::string& profile); void debugProfileDataChanged (const QModelIndex& topLeft, @@ -228,6 +264,16 @@ namespace CSVRender void showToolTip(); + void primaryEdit(bool activate); + + void secondaryEdit(bool activate); + + void primarySelect(bool activate); + + void secondarySelect(bool activate); + + void speedMode(bool activate); + protected slots: void elementSelectionChanged(); diff --git a/apps/opencs/view/tools/reporttable.cpp b/apps/opencs/view/tools/reporttable.cpp index bfc002933..4d1456cd9 100644 --- a/apps/opencs/view/tools/reporttable.cpp +++ b/apps/opencs/view/tools/reporttable.cpp @@ -15,6 +15,7 @@ #include "../../model/tools/reportmodel.hpp" #include "../../model/prefs/state.hpp" +#include "../../model/prefs/shortcut.hpp" #include "../../view/world/idtypedelegate.hpp" @@ -156,6 +157,7 @@ CSVTools::ReportTable::ReportTable (CSMDoc::Document& document, mProxyModel = new QSortFilterProxyModel (this); mProxyModel->setSourceModel (mModel); + mProxyModel->setSortRole(Qt::UserRole); setModel (mProxyModel); setColumnHidden (2, true); @@ -171,14 +173,20 @@ CSVTools::ReportTable::ReportTable (CSMDoc::Document& document, mShowAction = new QAction (tr ("Show"), this); connect (mShowAction, SIGNAL (triggered()), this, SLOT (showSelection())); addAction (mShowAction); + CSMPrefs::Shortcut* showShortcut = new CSMPrefs::Shortcut("reporttable-show", this); + showShortcut->associateAction(mShowAction); mRemoveAction = new QAction (tr ("Remove from list"), this); connect (mRemoveAction, SIGNAL (triggered()), this, SLOT (removeSelection())); addAction (mRemoveAction); + CSMPrefs::Shortcut* removeShortcut = new CSMPrefs::Shortcut("reporttable-remove", this); + removeShortcut->associateAction(mRemoveAction); mReplaceAction = new QAction (tr ("Replace"), this); connect (mReplaceAction, SIGNAL (triggered()), this, SIGNAL (replaceRequest())); addAction (mReplaceAction); + CSMPrefs::Shortcut* replaceShortcut = new CSMPrefs::Shortcut("reporttable-replace", this); + replaceShortcut->associateAction(mReplaceAction); if (mRefreshState) { @@ -186,6 +194,8 @@ CSVTools::ReportTable::ReportTable (CSMDoc::Document& document, mRefreshAction->setEnabled (!(mDocument.getState() & mRefreshState)); connect (mRefreshAction, SIGNAL (triggered()), this, SIGNAL (refreshRequest())); addAction (mRefreshAction); + CSMPrefs::Shortcut* refreshShortcut = new CSMPrefs::Shortcut("reporttable-refresh", this); + refreshShortcut->associateAction(mRefreshAction); } mDoubleClickActions.insert (std::make_pair (Qt::NoModifier, Action_Edit)); diff --git a/apps/opencs/view/widget/pushbutton.cpp b/apps/opencs/view/widget/pushbutton.cpp index 424aaf68a..c4e6a4144 100644 --- a/apps/opencs/view/widget/pushbutton.cpp +++ b/apps/opencs/view/widget/pushbutton.cpp @@ -3,9 +3,17 @@ #include #include +#include "../../model/prefs/state.hpp" +#include "../../model/prefs/shortcutmanager.hpp" + +void CSVWidget::PushButton::processShortcuts() +{ + mProcessedToolTip = CSMPrefs::State::get().getShortcutManager().processToolTip(mToolTip); +} + void CSVWidget::PushButton::setExtendedToolTip() { - QString tooltip = mToolTip; + QString tooltip = mProcessedToolTip; if (tooltip.isEmpty()) tooltip = "(Tool tip not implemented yet)"; @@ -77,13 +85,18 @@ CSVWidget::PushButton::PushButton (const QIcon& icon, Type type, const QString& connect (this, SIGNAL (toggled (bool)), this, SLOT (checkedStateChanged (bool))); } setCheckable (type==Type_Mode || type==Type_Toggle); + processShortcuts(); setExtendedToolTip(); + + connect (&CSMPrefs::State::get(), SIGNAL (settingChanged (const CSMPrefs::Setting *)), + this, SLOT (settingChanged (const CSMPrefs::Setting *))); } CSVWidget::PushButton::PushButton (Type type, const QString& tooltip, QWidget *parent) : QPushButton (parent), mKeepOpen (false), mType (type), mToolTip (tooltip) { setCheckable (type==Type_Mode || type==Type_Toggle); + processShortcuts(); setExtendedToolTip(); } @@ -94,7 +107,7 @@ bool CSVWidget::PushButton::hasKeepOpen() const QString CSVWidget::PushButton::getBaseToolTip() const { - return mToolTip; + return mProcessedToolTip; } CSVWidget::PushButton::Type CSVWidget::PushButton::getType() const @@ -106,3 +119,12 @@ void CSVWidget::PushButton::checkedStateChanged (bool checked) { setExtendedToolTip(); } + +void CSVWidget::PushButton::settingChanged (const CSMPrefs::Setting *setting) +{ + if (setting->getParent()->getKey() == "Key Bindings") + { + processShortcuts(); + setExtendedToolTip(); + } +} diff --git a/apps/opencs/view/widget/pushbutton.hpp b/apps/opencs/view/widget/pushbutton.hpp index 09cf22757..bdbdc9c4d 100644 --- a/apps/opencs/view/widget/pushbutton.hpp +++ b/apps/opencs/view/widget/pushbutton.hpp @@ -3,6 +3,11 @@ #include +namespace CSMPrefs +{ + class Setting; +} + namespace CSVWidget { class PushButton : public QPushButton @@ -24,9 +29,11 @@ namespace CSVWidget bool mKeepOpen; Type mType; QString mToolTip; + QString mProcessedToolTip; private: + void processShortcuts(); void setExtendedToolTip(); protected: @@ -57,6 +64,7 @@ namespace CSVWidget private slots: void checkedStateChanged (bool checked); + void settingChanged (const CSMPrefs::Setting *setting); }; } diff --git a/apps/opencs/view/widget/scenetoolbar.cpp b/apps/opencs/view/widget/scenetoolbar.cpp index b2e988dc9..a2458397f 100644 --- a/apps/opencs/view/widget/scenetoolbar.cpp +++ b/apps/opencs/view/widget/scenetoolbar.cpp @@ -1,7 +1,8 @@ #include "scenetoolbar.hpp" #include -#include + +#include "../../model/prefs/shortcut.hpp" #include "scenetool.hpp" @@ -25,9 +26,8 @@ CSVWidget::SceneToolbar::SceneToolbar (int buttonSize, QWidget *parent) setLayout (mLayout); - /// \todo make shortcut configurable - QShortcut *focusScene = new QShortcut (Qt::Key_T, this, 0, 0, Qt::WidgetWithChildrenShortcut); - connect (focusScene, SIGNAL (activated()), this, SIGNAL (focusSceneRequest())); + CSMPrefs::Shortcut* focusSceneShortcut = new CSMPrefs::Shortcut("scene-focus-toolbar", this); + connect(focusSceneShortcut, SIGNAL(activated()), this, SIGNAL(focusSceneRequest())); } void CSVWidget::SceneToolbar::addTool (SceneTool *tool, SceneTool *insertPoint) diff --git a/apps/opencs/view/widget/scenetoolmode.cpp b/apps/opencs/view/widget/scenetoolmode.cpp index 125f4ac79..7b2ff64db 100644 --- a/apps/opencs/view/widget/scenetoolmode.cpp +++ b/apps/opencs/view/widget/scenetoolmode.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include "scenetoolbar.hpp" #include "modebutton.hpp" @@ -38,6 +39,27 @@ void CSVWidget::SceneToolMode::adjustToolTip (const ModeButton *activeMode) setToolTip (toolTip); } +void CSVWidget::SceneToolMode::setButton (std::map::iterator iter) +{ + for (std::map::const_iterator iter2 = mButtons.begin(); + iter2!=mButtons.end(); ++iter2) + iter2->first->setChecked (iter2==iter); + + setIcon (iter->first->icon()); + adjustToolTip (iter->first); + + if (mCurrent!=iter->first) + { + if (mCurrent) + mCurrent->deactivate (mToolbar); + + mCurrent = iter->first; + mCurrent->activate (mToolbar); + } + + emit modeChanged (iter->second); +} + CSVWidget::SceneToolMode::SceneToolMode (SceneToolbar *parent, const QString& toolTip) : SceneTool (parent), mButtonSize (parent->getButtonSize()), mIconSize (parent->getIconSize()), mToolTip (toolTip), mFirst (0), mCurrent (0), mToolbar (parent) @@ -96,9 +118,35 @@ CSVWidget::ModeButton *CSVWidget::SceneToolMode::getCurrent() return mCurrent; } +std::string CSVWidget::SceneToolMode::getCurrentId() const +{ + return mButtons.find (mCurrent)->second; +} + +void CSVWidget::SceneToolMode::setButton (const std::string& id) +{ + for (std::map::iterator iter = mButtons.begin(); + iter!=mButtons.end(); ++iter) + if (iter->second==id) + { + setButton (iter); + break; + } +} + +bool CSVWidget::SceneToolMode::event(QEvent* event) +{ + if (event->type() == QEvent::ToolTip) + { + adjustToolTip(mCurrent); + } + + return SceneTool::event(event); +} + void CSVWidget::SceneToolMode::selected() { - std::map::const_iterator iter = + std::map::iterator iter = mButtons.find (dynamic_cast (sender())); if (iter!=mButtons.end()) @@ -106,22 +154,6 @@ void CSVWidget::SceneToolMode::selected() if (!iter->first->hasKeepOpen()) mPanel->hide(); - for (std::map::const_iterator iter2 = mButtons.begin(); - iter2!=mButtons.end(); ++iter2) - iter2->first->setChecked (iter2==iter); - - setIcon (iter->first->icon()); - adjustToolTip (iter->first); - - if (mCurrent!=iter->first) - { - if (mCurrent) - mCurrent->deactivate (mToolbar); - - mCurrent = iter->first; - mCurrent->activate (mToolbar); - } - - emit modeChanged (iter->second); + setButton (iter); } } diff --git a/apps/opencs/view/widget/scenetoolmode.hpp b/apps/opencs/view/widget/scenetoolmode.hpp index 3aa8e6799..90f1dc419 100644 --- a/apps/opencs/view/widget/scenetoolmode.hpp +++ b/apps/opencs/view/widget/scenetoolmode.hpp @@ -7,6 +7,7 @@ class QHBoxLayout; class QMenu; +class QEvent; namespace CSVWidget { @@ -41,6 +42,12 @@ namespace CSVWidget /// items to be added, the function must return true anyway. virtual bool createContextMenu (QMenu *menu); + void setButton (std::map::iterator iter); + + protected: + + bool event(QEvent* event); + public: SceneToolMode (SceneToolbar *parent, const QString& toolTip); @@ -56,6 +63,12 @@ namespace CSVWidget /// Will return a 0-pointer only if the mode does not have any buttons yet. ModeButton *getCurrent(); + /// Must not be called if there aren't any buttons yet. + std::string getCurrentId() const; + + /// Manually change the current mode + void setButton (const std::string& id); + signals: void modeChanged (const std::string& id); diff --git a/apps/opencs/view/world/dialoguesubview.cpp b/apps/opencs/view/world/dialoguesubview.cpp index 0a79aac2b..87d5b3d7f 100644 --- a/apps/opencs/view/world/dialoguesubview.cpp +++ b/apps/opencs/view/world/dialoguesubview.cpp @@ -578,35 +578,31 @@ void CSVWorld::EditWidget::remake(int row) fixedRows = true; } - NestedTable* table = - new NestedTable(mDocument, id, mNestedModels.back(), this, editable, fixedRows); - table->resizeColumnsToContents(); - if (!editable) + // Create and display nested table only if it's editable. + if (editable) { - table->setEditTriggers(QAbstractItemView::NoEditTriggers); - table->setEnabled(false); - } - - int rows = mTable->rowCount(mTable->index(row, i)); - int rowHeight = (rows == 0) ? table->horizontalHeader()->height() : table->rowHeight(0); - int tableMaxHeight = (5 * rowHeight) - + table->horizontalHeader()->height() + 2 * table->frameWidth(); - table->setMinimumHeight(tableMaxHeight); - - QLabel* label = - new QLabel (mTable->headerData (i, Qt::Horizontal, Qt::DisplayRole).toString(), mMainWidget); - - label->setSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed); - if(!editable) - label->setEnabled(false); + NestedTable* table = + new NestedTable(mDocument, id, mNestedModels.back(), this, editable, fixedRows); + table->resizeColumnsToContents(); + + int rows = mTable->rowCount(mTable->index(row, i)); + int rowHeight = (rows == 0) ? table->horizontalHeader()->height() : table->rowHeight(0); + int headerHeight = table->horizontalHeader()->height(); + int tableMaxHeight = (5 * rowHeight) + headerHeight + (2 * table->frameWidth()); + table->setMinimumHeight(tableMaxHeight); + + QString headerText = mTable->headerData (i, Qt::Horizontal, Qt::DisplayRole).toString(); + QLabel* label = new QLabel (headerText, mMainWidget); + label->setSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed); - tablesLayout->addWidget(label); - tablesLayout->addWidget(table); + tablesLayout->addWidget(label); + tablesLayout->addWidget(table); - connect(table, - SIGNAL(editRequest(const CSMWorld::UniversalId &, const std::string &)), - this, - SIGNAL(editIdRequest(const CSMWorld::UniversalId &, const std::string &))); + connect(table, + SIGNAL(editRequest(const CSMWorld::UniversalId &, const std::string &)), + this, + SIGNAL(editIdRequest(const CSMWorld::UniversalId &, const std::string &))); + } } else if (!(flags & CSMWorld::ColumnBase::Flag_Dialogue_List)) { @@ -662,8 +658,7 @@ void CSVWorld::EditWidget::remake(int row) int displayRole = tree->nestedHeaderData (i, col, Qt::Horizontal, CSMWorld::ColumnBase::Role_Display).toInt(); - CSMWorld::ColumnBase::Display display = - static_cast (displayRole); + display = static_cast (displayRole); mNestedTableDispatcher->makeDelegate (display); diff --git a/apps/opencs/view/world/dragrecordtable.cpp b/apps/opencs/view/world/dragrecordtable.cpp index a5f933283..6d980e171 100644 --- a/apps/opencs/view/world/dragrecordtable.cpp +++ b/apps/opencs/view/world/dragrecordtable.cpp @@ -67,8 +67,8 @@ void CSVWorld::DragRecordTable::dropEvent(QDropEvent *event) CSMWorld::ColumnBase::Display display = getIndexDisplayType(index); if (CSVWorld::DragDropUtils::canAcceptData(*event, display)) { - const CSMWorld::TableMimeData *data = CSVWorld::DragDropUtils::getTableMimeData(*event); - if (data->fromDocument(mDocument)) + const CSMWorld::TableMimeData *tableMimeData = CSVWorld::DragDropUtils::getTableMimeData(*event); + if (tableMimeData->fromDocument(mDocument)) { CSMWorld::UniversalId id = CSVWorld::DragDropUtils::getAcceptedData(*event, display); QVariant newIndexData = QString::fromUtf8(id.getId().c_str()); diff --git a/apps/opencs/view/world/genericcreator.cpp b/apps/opencs/view/world/genericcreator.cpp index df7739941..dd2bd82df 100644 --- a/apps/opencs/view/world/genericcreator.cpp +++ b/apps/opencs/view/world/genericcreator.cpp @@ -40,6 +40,10 @@ void CSVWorld::GenericCreator::insertAtBeginning (QWidget *widget, bool stretche void CSVWorld::GenericCreator::insertBeforeButtons (QWidget *widget, bool stretched) { mLayout->insertWidget (mLayout->count()-2, widget, stretched ? 1 : 0); + + // Reset tab order relative to buttons. + setTabOrder(widget, mCreate); + setTabOrder(mCreate, mCancel); } std::string CSVWorld::GenericCreator::getId() const @@ -161,15 +165,16 @@ CSVWorld::GenericCreator::GenericCreator (CSMWorld::Data& data, QUndoStack& undo mCreate = new QPushButton ("Create"); mLayout->addWidget (mCreate); - QPushButton *cancelButton = new QPushButton ("Cancel"); - mLayout->addWidget (cancelButton); + mCancel = new QPushButton("Cancel"); + mLayout->addWidget(mCancel); setLayout (mLayout); - connect (cancelButton, SIGNAL (clicked (bool)), this, SIGNAL (done())); + connect (mCancel, SIGNAL (clicked (bool)), this, SIGNAL (done())); connect (mCreate, SIGNAL (clicked (bool)), this, SLOT (create())); connect (mId, SIGNAL (textChanged (const QString&)), this, SLOT (textChanged (const QString&))); + connect (mId, SIGNAL (returnPressed()), this, SLOT (inputReturnPressed())); connect (&mData, SIGNAL (idListChanged()), this, SLOT (dataIdListChanged())); } @@ -205,6 +210,14 @@ void CSVWorld::GenericCreator::textChanged (const QString& text) update(); } +void CSVWorld::GenericCreator::inputReturnPressed() +{ + if (mCreate->isEnabled()) + { + create(); + } +} + void CSVWorld::GenericCreator::create() { if (!mLocked) diff --git a/apps/opencs/view/world/genericcreator.hpp b/apps/opencs/view/world/genericcreator.hpp index f63c45109..f3fc82ec1 100644 --- a/apps/opencs/view/world/genericcreator.hpp +++ b/apps/opencs/view/world/genericcreator.hpp @@ -33,6 +33,7 @@ namespace CSVWorld QUndoStack& mUndoStack; CSMWorld::UniversalId mListId; QPushButton *mCreate; + QPushButton *mCancel; QLineEdit *mId; std::string mErrors; QHBoxLayout *mLayout; @@ -56,6 +57,9 @@ namespace CSVWorld void insertAtBeginning (QWidget *widget, bool stretched); + /// \brief Insert given widget before Create and Cancel buttons. + /// \param widget Widget to add to layout. + /// \param stretched Whether widget should be streched or not. void insertBeforeButtons (QWidget *widget, bool stretched); virtual std::string getId() const; @@ -112,6 +116,9 @@ namespace CSVWorld void textChanged (const QString& text); + /// \brief Create record if able to after Return key is pressed on input. + void inputReturnPressed(); + void create(); void scopeChanged (int index); diff --git a/apps/opencs/view/world/globalcreator.cpp b/apps/opencs/view/world/globalcreator.cpp new file mode 100644 index 000000000..c7b140e15 --- /dev/null +++ b/apps/opencs/view/world/globalcreator.cpp @@ -0,0 +1,26 @@ +#include "globalcreator.hpp" + +#include + +#include "../../model/world/data.hpp" +#include "../../model/world/commands.hpp" +#include "../../model/world/columns.hpp" +#include "../../model/world/idtable.hpp" + +namespace CSVWorld +{ + void GlobalCreator::configureCreateCommand (CSMWorld::CreateCommand& command) const + { + CSMWorld::IdTable* table = static_cast(getData().getTableModel(getCollectionId())); + + int index = table->findColumnIndex(CSMWorld::Columns::ColumnId_ValueType); + int type = (int)ESM::VT_Float; + + command.addValue(index, type); + } + + GlobalCreator::GlobalCreator(CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id) + : GenericCreator (data, undoStack, id, true) + { + } +} diff --git a/apps/opencs/view/world/globalcreator.hpp b/apps/opencs/view/world/globalcreator.hpp new file mode 100644 index 000000000..8c6cc628c --- /dev/null +++ b/apps/opencs/view/world/globalcreator.hpp @@ -0,0 +1,22 @@ +#ifndef CSV_WORLD_GLOBALCREATOR_H +#define CSV_WORLD_GLOBALCREATOR_H + +#include "genericcreator.hpp" + +namespace CSVWorld +{ + class GlobalCreator : public GenericCreator + { + Q_OBJECT + + public: + + GlobalCreator(CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id); + + protected: + + virtual void configureCreateCommand(CSMWorld::CreateCommand& command) const; + }; +} + +#endif diff --git a/apps/opencs/view/world/idcompletiondelegate.cpp b/apps/opencs/view/world/idcompletiondelegate.cpp index 970490828..7f0f4ae46 100644 --- a/apps/opencs/view/world/idcompletiondelegate.cpp +++ b/apps/opencs/view/world/idcompletiondelegate.cpp @@ -1,6 +1,7 @@ #include "idcompletiondelegate.hpp" #include "../../model/world/idcompletionmanager.hpp" +#include "../../model/world/infoselectwrapper.hpp" #include "../widget/droplineedit.hpp" @@ -27,6 +28,56 @@ QWidget *CSVWorld::IdCompletionDelegate::createEditor(QWidget *parent, return NULL; } + // The completer for InfoCondVar needs to return a completer based on the first column + if (display == CSMWorld::ColumnBase::Display_InfoCondVar) + { + QModelIndex sibling = index.sibling(index.row(), 0); + int conditionFunction = sibling.model()->data(sibling, Qt::EditRole).toInt(); + + switch (conditionFunction) + { + case CSMWorld::ConstInfoSelectWrapper::Function_Global: + { + return createEditor (parent, option, index, CSMWorld::ColumnBase::Display_GlobalVariable); + } + case CSMWorld::ConstInfoSelectWrapper::Function_Journal: + { + return createEditor (parent, option, index, CSMWorld::ColumnBase::Display_Journal); + } + case CSMWorld::ConstInfoSelectWrapper::Function_Item: + { + return createEditor (parent, option, index, CSMWorld::ColumnBase::Display_Referenceable); + } + case CSMWorld::ConstInfoSelectWrapper::Function_Dead: + case CSMWorld::ConstInfoSelectWrapper::Function_NotId: + { + return createEditor (parent, option, index, CSMWorld::ColumnBase::Display_Referenceable); + } + case CSMWorld::ConstInfoSelectWrapper::Function_NotFaction: + { + return createEditor (parent, option, index, CSMWorld::ColumnBase::Display_Faction); + } + case CSMWorld::ConstInfoSelectWrapper::Function_NotClass: + { + return createEditor (parent, option, index, CSMWorld::ColumnBase::Display_Class); + } + case CSMWorld::ConstInfoSelectWrapper::Function_NotRace: + { + return createEditor (parent, option, index, CSMWorld::ColumnBase::Display_Race); + } + case CSMWorld::ConstInfoSelectWrapper::Function_NotCell: + { + return createEditor (parent, option, index, CSMWorld::ColumnBase::Display_Cell); + } + case CSMWorld::ConstInfoSelectWrapper::Function_Local: + case CSMWorld::ConstInfoSelectWrapper::Function_NotLocal: + { + return new CSVWidget::DropLineEdit(display, parent); + } + default: return 0; // The rest of them can't be edited anyway + } + } + CSMWorld::IdCompletionManager &completionManager = getDocument().getIdCompletionManager(); CSVWidget::DropLineEdit *editor = new CSVWidget::DropLineEdit(display, parent); editor->setCompleter(completionManager.getCompleter(display).get()); diff --git a/apps/opencs/view/world/infocreator.cpp b/apps/opencs/view/world/infocreator.cpp index 1139afd69..243b7d879 100644 --- a/apps/opencs/view/world/infocreator.cpp +++ b/apps/opencs/view/world/infocreator.cpp @@ -60,6 +60,7 @@ CSVWorld::InfoCreator::InfoCreator (CSMWorld::Data& data, QUndoStack& undoStack, setManualEditing (false); connect (mTopic, SIGNAL (textChanged (const QString&)), this, SLOT (topicChanged())); + connect (mTopic, SIGNAL (returnPressed()), this, SLOT (inputReturnPressed())); } void CSVWorld::InfoCreator::cloneMode (const std::string& originId, @@ -110,7 +111,7 @@ void CSVWorld::InfoCreator::topicChanged() update(); } -CSVWorld::Creator *CSVWorld::InfoCreatorFactory::makeCreator(CSMDoc::Document& document, +CSVWorld::Creator *CSVWorld::InfoCreatorFactory::makeCreator(CSMDoc::Document& document, const CSMWorld::UniversalId& id) const { return new InfoCreator(document.getData(), diff --git a/apps/opencs/view/world/nestedtable.cpp b/apps/opencs/view/world/nestedtable.cpp index 23d566439..02bd93920 100644 --- a/apps/opencs/view/world/nestedtable.cpp +++ b/apps/opencs/view/world/nestedtable.cpp @@ -5,10 +5,13 @@ #include #include +#include "../../model/prefs/shortcut.hpp" + #include "../../model/world/nestedtableproxymodel.hpp" #include "../../model/world/universalid.hpp" #include "../../model/world/commands.hpp" #include "../../model/world/commanddispatcher.hpp" +#include "../../model/world/commandmacro.hpp" #include "tableeditidaction.hpp" #include "util.hpp" @@ -59,14 +62,16 @@ CSVWorld::NestedTable::NestedTable(CSMDoc::Document& document, if (!fixedRows) { mAddNewRowAction = new QAction (tr ("Add new row"), this); - connect(mAddNewRowAction, SIGNAL(triggered()), this, SLOT(addNewRowActionTriggered())); + CSMPrefs::Shortcut* addRowShortcut = new CSMPrefs::Shortcut("table-add", this); + addRowShortcut->associateAction(mAddNewRowAction); - mRemoveRowAction = new QAction (tr ("Remove row"), this); - + mRemoveRowAction = new QAction (tr ("Remove rows"), this); connect(mRemoveRowAction, SIGNAL(triggered()), this, SLOT(removeRowActionTriggered())); + CSMPrefs::Shortcut* removeRowShortcut = new CSMPrefs::Shortcut("table-remove", this); + removeRowShortcut->associateAction(mRemoveRowAction); } mEditIdAction = new TableEditIdAction(*this, this); @@ -100,10 +105,8 @@ void CSVWorld::NestedTable::contextMenuEvent (QContextMenuEvent *event) if (mAddNewRowAction && mRemoveRowAction) { - if (selectionModel()->selectedRows().size() == 1) - menu.addAction(mRemoveRowAction); - menu.addAction(mAddNewRowAction); + menu.addAction(mRemoveRowAction); } menu.exec (event->globalPos()); @@ -111,17 +114,27 @@ void CSVWorld::NestedTable::contextMenuEvent (QContextMenuEvent *event) void CSVWorld::NestedTable::removeRowActionTriggered() { - mDocument.getUndoStack().push(new CSMWorld::DeleteNestedCommand(*(mModel->model()), - mModel->getParentId(), - selectionModel()->selectedRows().begin()->row(), - mModel->getParentColumn())); + CSMWorld::CommandMacro macro(mDocument.getUndoStack(), + selectionModel()->selectedRows().size() > 1 ? tr("Remove rows") : ""); + + // Remove rows in reverse order + for (int i = selectionModel()->selectedRows().size() - 1; i >= 0; --i) + { + macro.push(new CSMWorld::DeleteNestedCommand(*(mModel->model()), mModel->getParentId(), + selectionModel()->selectedRows()[i].row(), mModel->getParentColumn())); + } } void CSVWorld::NestedTable::addNewRowActionTriggered() { + int row = 0; + + if (!selectionModel()->selectedRows().empty()) + row = selectionModel()->selectedRows().back().row() + 1; + mDocument.getUndoStack().push(new CSMWorld::AddNestedCommand(*(mModel->model()), mModel->getParentId(), - selectionModel()->selectedRows().size(), + row, mModel->getParentColumn())); } diff --git a/apps/opencs/view/world/pathgridcreator.cpp b/apps/opencs/view/world/pathgridcreator.cpp new file mode 100644 index 000000000..a305b1249 --- /dev/null +++ b/apps/opencs/view/world/pathgridcreator.cpp @@ -0,0 +1,29 @@ +#include "pathgridcreator.hpp" + +#include "../../model/world/data.hpp" + +CSVWorld::PathgridCreator::PathgridCreator( + CSMWorld::Data& data, + QUndoStack& undoStack, + const CSMWorld::UniversalId& id, + bool relaxedIdRules +) : GenericCreator(data, undoStack, id, relaxedIdRules) +{} + +std::string CSVWorld::PathgridCreator::getErrors() const +{ + std::string pathgridId = getId(); + + // Check user input for any errors. + std::string errors; + if (pathgridId.empty()) + { + errors = "No Pathgrid ID entered"; + } + else if (getData().getPathgrids().searchId(pathgridId) > -1) + { + errors = "Pathgrid with this ID already exists"; + } + + return errors; +} diff --git a/apps/opencs/view/world/pathgridcreator.hpp b/apps/opencs/view/world/pathgridcreator.hpp new file mode 100644 index 000000000..10f64a0a7 --- /dev/null +++ b/apps/opencs/view/world/pathgridcreator.hpp @@ -0,0 +1,26 @@ +#ifndef PATHGRIDCREATOR_HPP +#define PATHGRIDCREATOR_HPP + +#include "genericcreator.hpp" + +namespace CSVWorld +{ + /// \brief Record creator for pathgrids. + class PathgridCreator : public GenericCreator + { + Q_OBJECT + + public: + + PathgridCreator( + CSMWorld::Data& data, + QUndoStack& undoStack, + const CSMWorld::UniversalId& id, + bool relaxedIdRules = false); + + /// \return Error description for current user input. + virtual std::string getErrors() const; + }; +} + +#endif // PATHGRIDCREATOR_HPP diff --git a/apps/opencs/view/world/referenceablecreator.cpp b/apps/opencs/view/world/referenceablecreator.cpp index 1357ca46f..836e8ac7d 100644 --- a/apps/opencs/view/world/referenceablecreator.cpp +++ b/apps/opencs/view/world/referenceablecreator.cpp @@ -26,10 +26,10 @@ CSVWorld::ReferenceableCreator::ReferenceableCreator (CSMWorld::Data& data, QUnd for (std::vector::const_iterator iter (types.begin()); iter!=types.end(); ++iter) { - CSMWorld::UniversalId id (*iter, ""); + CSMWorld::UniversalId id2 (*iter, ""); - mType->addItem (QIcon (id.getIcon().c_str()), id.getTypeName().c_str(), - static_cast (id.getType())); + mType->addItem (QIcon (id2.getIcon().c_str()), id2.getTypeName().c_str(), + static_cast (id2.getType())); } insertBeforeButtons (mType, false); diff --git a/apps/opencs/view/world/referencecreator.cpp b/apps/opencs/view/world/referencecreator.cpp index 73ca62e02..cc7ae545a 100644 --- a/apps/opencs/view/world/referencecreator.cpp +++ b/apps/opencs/view/world/referencecreator.cpp @@ -9,6 +9,7 @@ #include "../../model/world/columns.hpp" #include "../../model/world/idtable.hpp" #include "../../model/world/idcompletionmanager.hpp" +#include "../../model/world/commandmacro.hpp" #include "../widget/droplineedit.hpp" @@ -25,52 +26,6 @@ void CSVWorld::ReferenceCreator::configureCreateCommand (CSMWorld::CreateCommand findColumnIndex (CSMWorld::Columns::ColumnId_Cell); command.addValue (cellIdColumn, mCell->text()); - - // Set RefNum - int refNumColumn = dynamic_cast ( - *getData().getTableModel (CSMWorld::UniversalId::Type_References)). - findColumnIndex (CSMWorld::Columns::ColumnId_RefNum); - - command.addValue (refNumColumn, getRefNumCount()); -} - -void CSVWorld::ReferenceCreator::pushCommand (std::auto_ptr command, - const std::string& id) -{ - // get the old count - std::string cellId = mCell->text().toUtf8().constData(); - - CSMWorld::IdTable& cellTable = dynamic_cast ( - *getData().getTableModel (CSMWorld::UniversalId::Type_Cells)); - - int countColumn = cellTable.findColumnIndex (CSMWorld::Columns::ColumnId_RefNumCounter); - - QModelIndex countIndex = cellTable.getModelIndex (cellId, countColumn); - - int count = cellTable.data (countIndex).toInt(); - - // command for incrementing counter - std::auto_ptr increment (new CSMWorld::ModifyCommand - (cellTable, countIndex, count+1)); - - getUndoStack().beginMacro (command->text()); - GenericCreator::pushCommand (command, id); - getUndoStack().push (increment.release()); - getUndoStack().endMacro(); -} - -int CSVWorld::ReferenceCreator::getRefNumCount() const -{ - std::string cellId = mCell->text().toUtf8().constData(); - - CSMWorld::IdTable& cellTable = dynamic_cast ( - *getData().getTableModel (CSMWorld::UniversalId::Type_Cells)); - - int countColumn = cellTable.findColumnIndex (CSMWorld::Columns::ColumnId_RefNumCounter); - - QModelIndex countIndex = cellTable.getModelIndex (cellId, countColumn); - - return cellTable.data (countIndex).toInt(); } CSVWorld::ReferenceCreator::ReferenceCreator (CSMWorld::Data& data, QUndoStack& undoStack, @@ -87,6 +42,7 @@ CSVWorld::ReferenceCreator::ReferenceCreator (CSMWorld::Data& data, QUndoStack& setManualEditing (false); connect (mCell, SIGNAL (textChanged (const QString&)), this, SLOT (cellChanged())); + connect (mCell, SIGNAL (returnPressed()), this, SLOT (inputReturnPressed())); } void CSVWorld::ReferenceCreator::reset() @@ -147,11 +103,11 @@ void CSVWorld::ReferenceCreator::cloneMode(const std::string& originId, cellChanged(); //otherwise ok button will remain disabled } -CSVWorld::Creator *CSVWorld::ReferenceCreatorFactory::makeCreator (CSMDoc::Document& document, +CSVWorld::Creator *CSVWorld::ReferenceCreatorFactory::makeCreator (CSMDoc::Document& document, const CSMWorld::UniversalId& id) const { - return new ReferenceCreator(document.getData(), - document.getUndoStack(), + return new ReferenceCreator(document.getData(), + document.getUndoStack(), id, document.getIdCompletionManager()); } diff --git a/apps/opencs/view/world/referencecreator.hpp b/apps/opencs/view/world/referencecreator.hpp index c230d0126..31010fa24 100644 --- a/apps/opencs/view/world/referencecreator.hpp +++ b/apps/opencs/view/world/referencecreator.hpp @@ -29,11 +29,6 @@ namespace CSVWorld virtual void configureCreateCommand (CSMWorld::CreateCommand& command) const; - virtual void pushCommand (std::auto_ptr command, - const std::string& id); - - int getRefNumCount() const; - public: ReferenceCreator (CSMWorld::Data& data, QUndoStack& undoStack, @@ -50,7 +45,7 @@ namespace CSVWorld /// Focus main input widget virtual void focus(); - + private slots: void cellChanged(); diff --git a/apps/opencs/view/world/regionmap.cpp b/apps/opencs/view/world/regionmap.cpp index 49764bd17..31e5d94ca 100644 --- a/apps/opencs/view/world/regionmap.cpp +++ b/apps/opencs/view/world/regionmap.cpp @@ -17,6 +17,7 @@ #include "../../model/world/commands.hpp" #include "../../model/world/columns.hpp" #include "../../model/world/tablemimedata.hpp" +#include "../../model/world/commandmacro.hpp" void CSVWorld::RegionMap::contextMenuEvent (QContextMenuEvent *event) { @@ -159,8 +160,7 @@ void CSVWorld::RegionMap::setRegion (const std::string& regionId) QString regionId2 = QString::fromUtf8 (regionId.c_str()); - if (selected.size()>1) - mDocument.getUndoStack().beginMacro (tr ("Set Region")); + CSMWorld::CommandMacro macro (mDocument.getUndoStack(), selected.size()>1 ? tr ("Set Region") : ""); for (QModelIndexList::const_iterator iter (selected.begin()); iter!=selected.end(); ++iter) { @@ -170,12 +170,8 @@ void CSVWorld::RegionMap::setRegion (const std::string& regionId) QModelIndex index = cellsModel->getModelIndex (cellId, cellsModel->findColumnIndex (CSMWorld::Columns::ColumnId_Region)); - mDocument.getUndoStack().push ( - new CSMWorld::ModifyCommand (*cellsModel, index, regionId2)); + macro.push (new CSMWorld::ModifyCommand (*cellsModel, index, regionId2)); } - - if (selected.size()>1) - mDocument.getUndoStack().endMacro(); } CSVWorld::RegionMap::RegionMap (const CSMWorld::UniversalId& universalId, @@ -258,19 +254,15 @@ void CSVWorld::RegionMap::createCells() CSMWorld::IdTable *cellsModel = &dynamic_cast (* mDocument.getData().getTableModel (CSMWorld::UniversalId::Type_Cells)); - if (selected.size()>1) - mDocument.getUndoStack().beginMacro (tr ("Create cells")); + CSMWorld::CommandMacro macro (mDocument.getUndoStack(), selected.size()>1 ? tr ("Create cells"): ""); for (QModelIndexList::const_iterator iter (selected.begin()); iter!=selected.end(); ++iter) { std::string cellId = model()->data (*iter, CSMWorld::RegionMap::Role_CellId). toString().toUtf8().constData(); - mDocument.getUndoStack().push (new CSMWorld::CreateCommand (*cellsModel, cellId)); + macro.push (new CSMWorld::CreateCommand (*cellsModel, cellId)); } - - if (selected.size()>1) - mDocument.getUndoStack().endMacro(); } void CSVWorld::RegionMap::setRegion() @@ -311,7 +303,7 @@ void CSVWorld::RegionMap::view() hint << cellId; } - emit editRequest (CSMWorld::UniversalId (CSMWorld::UniversalId::Type_Scene, "sys::default"), + emit editRequest (CSMWorld::UniversalId (CSMWorld::UniversalId::Type_Scene, ESM::CellId::sDefaultWorldspace), hint.str()); } diff --git a/apps/opencs/view/world/regionmap.hpp b/apps/opencs/view/world/regionmap.hpp index 0097a16dc..ba773224f 100644 --- a/apps/opencs/view/world/regionmap.hpp +++ b/apps/opencs/view/world/regionmap.hpp @@ -45,8 +45,8 @@ namespace CSVWorld ///< \note Non-existent cells are not listed. QModelIndexList getSelectedCells (bool existent = true, bool nonExistent = false) const; - ///< \param existant Include existant cells. - /// \param nonExistant Include non-existant cells. + ///< \param existent Include existent cells. + /// \param nonExistent Include non-existent cells. QModelIndexList getMissingRegionCells() const; ///< Unselected cells within all regions that have at least one selected cell. diff --git a/apps/opencs/view/world/scenesubview.cpp b/apps/opencs/view/world/scenesubview.cpp index 7014b1486..b14d708da 100644 --- a/apps/opencs/view/world/scenesubview.cpp +++ b/apps/opencs/view/world/scenesubview.cpp @@ -38,7 +38,7 @@ CSVWorld::SceneSubView::SceneSubView (const CSMWorld::UniversalId& id, CSMDoc::D CSVRender::WorldspaceWidget* worldspaceWidget = NULL; widgetType whatWidget; - if (id.getId()=="sys::default") + if (id.getId()==ESM::CellId::sDefaultWorldspace) { whatWidget = widget_Paged; @@ -112,7 +112,7 @@ CSVWidget::SceneToolbar* CSVWorld::SceneSubView::makeToolbar (CSVRender::Worldsp if (type==widget_Paged) { - CSVWidget::SceneToolToggle *controlVisibilityTool = + CSVWidget::SceneToolToggle2 *controlVisibilityTool = dynamic_cast (*widget). makeControlVisibilitySelector (toolbar); @@ -160,7 +160,7 @@ void CSVWorld::SceneSubView::cellSelectionChanged (const CSMWorld::UniversalId& void CSVWorld::SceneSubView::cellSelectionChanged (const CSMWorld::CellSelection& selection) { - setUniversalId(CSMWorld::UniversalId(CSMWorld::UniversalId::Type_Scene, "sys::default")); + setUniversalId(CSMWorld::UniversalId(CSMWorld::UniversalId::Type_Scene, ESM::CellId::sDefaultWorldspace)); int size = selection.getSize(); std::ostringstream stream; @@ -187,18 +187,18 @@ void CSVWorld::SceneSubView::cellSelectionChanged (const CSMWorld::CellSelection emit updateTitle(); } -void CSVWorld::SceneSubView::handleDrop (const std::vector< CSMWorld::UniversalId >& data) +void CSVWorld::SceneSubView::handleDrop (const std::vector< CSMWorld::UniversalId >& universalIdData) { CSVRender::PagedWorldspaceWidget* pagedNewWidget = NULL; CSVRender::UnpagedWorldspaceWidget* unPagedNewWidget = NULL; CSVWidget::SceneToolbar* toolbar = NULL; - CSVRender::WorldspaceWidget::DropType type = CSVRender::WorldspaceWidget::getDropType (data); + CSVRender::WorldspaceWidget::DropType type = CSVRender::WorldspaceWidget::getDropType (universalIdData); switch (mScene->getDropRequirements (type)) { case CSVRender::WorldspaceWidget::canHandle: - mScene->handleDrop (data, type); + mScene->handleDrop (universalIdData, type); break; case CSVRender::WorldspaceWidget::needPaged: @@ -206,15 +206,15 @@ void CSVWorld::SceneSubView::handleDrop (const std::vector< CSMWorld::UniversalI toolbar = makeToolbar(pagedNewWidget, widget_Paged); makeConnections(pagedNewWidget); replaceToolbarAndWorldspace(pagedNewWidget, toolbar); - mScene->handleDrop (data, type); + mScene->handleDrop (universalIdData, type); break; case CSVRender::WorldspaceWidget::needUnpaged: - unPagedNewWidget = new CSVRender::UnpagedWorldspaceWidget(data.begin()->getId(), mDocument, this); + unPagedNewWidget = new CSVRender::UnpagedWorldspaceWidget(universalIdData.begin()->getId(), mDocument, this); toolbar = makeToolbar(unPagedNewWidget, widget_Unpaged); makeConnections(unPagedNewWidget); replaceToolbarAndWorldspace(unPagedNewWidget, toolbar); - cellSelectionChanged(*(data.begin())); + cellSelectionChanged(*(universalIdData.begin())); break; case CSVRender::WorldspaceWidget::ignored: diff --git a/apps/opencs/view/world/scriptedit.cpp b/apps/opencs/view/world/scriptedit.cpp index d0146445a..6f27d5656 100644 --- a/apps/opencs/view/world/scriptedit.cpp +++ b/apps/opencs/view/world/scriptedit.cpp @@ -48,11 +48,12 @@ CSVWorld::ScriptEdit::ScriptEdit( mLineNumberArea(0), mDefaultFont(font()), mMonoFont(QFont("Monospace")), + mTabCharCount(4), mDocument(document), mWhiteListQoutes("^[a-z|_]{1}[a-z|0-9|_]{0,}$", Qt::CaseInsensitive) { wrapLines(false); - setTabStopWidth (4); + setTabWidth(); setUndoRedoEnabled (false); // we use OpenCS-wide undo/redo instead mAllowedTypes <isTrue() ? mMonoFont : mDefaultFont); + setTabWidth(); } else if (*setting == "Scripts/show-linenum") { @@ -225,6 +225,11 @@ void CSVWorld::ScriptEdit::settingChanged(const CSMPrefs::Setting *setting) { wrapLines(setting->isTrue()); } + else if (*setting == "Scripts/tab-width") + { + mTabCharCount = setting->toInt(); + setTabWidth(); + } } void CSVWorld::ScriptEdit::idListChanged() diff --git a/apps/opencs/view/world/scriptedit.hpp b/apps/opencs/view/world/scriptedit.hpp index 1f03f050a..4977ed8e0 100644 --- a/apps/opencs/view/world/scriptedit.hpp +++ b/apps/opencs/view/world/scriptedit.hpp @@ -53,6 +53,7 @@ namespace CSVWorld LineNumberArea *mLineNumberArea; QFont mDefaultFont; QFont mMonoFont; + int mTabCharCount; protected: @@ -71,7 +72,6 @@ namespace CSVWorld void lineNumberAreaPaintEvent(QPaintEvent *event); int lineNumberAreaWidth(); void showLineNum(bool show); - void setMonoFont(bool show); protected: @@ -91,6 +91,9 @@ namespace CSVWorld bool stringNeedsQuote(const std::string& id) const; + /// \brief Set tab width for script editor. + void setTabWidth(); + /// \brief Turn line wrapping in script editor on or off. /// \param wrap Whether or not to wrap lines. void wrapLines(bool wrap); diff --git a/apps/opencs/view/world/scriptsubview.cpp b/apps/opencs/view/world/scriptsubview.cpp index ee0acb560..c9b8127f6 100644 --- a/apps/opencs/view/world/scriptsubview.cpp +++ b/apps/opencs/view/world/scriptsubview.cpp @@ -324,7 +324,9 @@ void CSVWorld::ScriptSubView::switchToRow (int row) std::string id = mModel->data (mModel->index (row, idColumn)).toString().toUtf8().constData(); setUniversalId (CSMWorld::UniversalId (CSMWorld::UniversalId::Type_Script, id)); - mEditor->setPlainText (mModel->data (mModel->index (row, mColumn)).toString()); + bool oldSignalsState = mEditor->blockSignals( true ); + mEditor->setPlainText( mModel->data(mModel->index(row, mColumn)).toString() ); + mEditor->blockSignals( oldSignalsState ); std::vector selection (1, id); mCommandDispatcher.setSelection (selection); diff --git a/apps/opencs/view/world/startscriptcreator.cpp b/apps/opencs/view/world/startscriptcreator.cpp index 7495da035..891199027 100644 --- a/apps/opencs/view/world/startscriptcreator.cpp +++ b/apps/opencs/view/world/startscriptcreator.cpp @@ -24,15 +24,6 @@ CSMWorld::IdTable& CSVWorld::StartScriptCreator::getStartScriptsTable() const ); } -void CSVWorld::StartScriptCreator::configureCreateCommand(CSMWorld::CreateCommand& command) const -{ - CSMWorld::IdTable& table = getStartScriptsTable(); - int column = table.findColumnIndex(CSMWorld::Columns::ColumnId_Id); - - // Set script ID to be added to start scripts table. - command.addValue(column, mScript->text()); -} - CSVWorld::StartScriptCreator::StartScriptCreator( CSMWorld::Data &data, QUndoStack &undoStack, @@ -53,6 +44,7 @@ CSVWorld::StartScriptCreator::StartScriptCreator( insertBeforeButtons(mScript, true); connect(mScript, SIGNAL (textChanged(const QString&)), this, SLOT (scriptChanged())); + connect(mScript, SIGNAL (returnPressed()), this, SLOT (inputReturnPressed())); } void CSVWorld::StartScriptCreator::cloneMode( diff --git a/apps/opencs/view/world/startscriptcreator.hpp b/apps/opencs/view/world/startscriptcreator.hpp index 473e2fd5f..72eb67bcc 100644 --- a/apps/opencs/view/world/startscriptcreator.hpp +++ b/apps/opencs/view/world/startscriptcreator.hpp @@ -31,10 +31,6 @@ namespace CSVWorld /// \return reference to table containing start scripts. CSMWorld::IdTable& getStartScriptsTable() const; - /// \brief Add user input to command for creating start script. - /// \param command Creation command to configure. - virtual void configureCreateCommand(CSMWorld::CreateCommand& command) const; - public: StartScriptCreator( diff --git a/apps/opencs/view/world/subviews.cpp b/apps/opencs/view/world/subviews.cpp index 650f344ed..32661ed93 100644 --- a/apps/opencs/view/world/subviews.cpp +++ b/apps/opencs/view/world/subviews.cpp @@ -7,6 +7,7 @@ #include "scriptsubview.hpp" #include "regionmapsubview.hpp" #include "genericcreator.hpp" +#include "globalcreator.hpp" #include "cellcreator.hpp" #include "referenceablecreator.hpp" #include "referencecreator.hpp" @@ -14,6 +15,7 @@ #include "scenesubview.hpp" #include "dialoguecreator.hpp" #include "infocreator.hpp" +#include "pathgridcreator.hpp" #include "previewsubview.hpp" void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) @@ -31,7 +33,6 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) static const CSMWorld::UniversalId::Type sTableTypes[] = { - CSMWorld::UniversalId::Type_Globals, CSMWorld::UniversalId::Type_Classes, CSMWorld::UniversalId::Type_Factions, CSMWorld::UniversalId::Type_Races, @@ -42,7 +43,6 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) CSMWorld::UniversalId::Type_Enchantments, CSMWorld::UniversalId::Type_BodyParts, CSMWorld::UniversalId::Type_SoundGens, - CSMWorld::UniversalId::Type_Pathgrids, CSMWorld::UniversalId::Type_None // end marker }; @@ -75,6 +75,12 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) manager.add (CSMWorld::UniversalId::Type_JournalInfos, new CSVDoc::SubViewFactoryWithCreator); + manager.add (CSMWorld::UniversalId::Type_Pathgrids, + new CSVDoc::SubViewFactoryWithCreator >); + + manager.add (CSMWorld::UniversalId::Type_Globals, + new CSVDoc::SubViewFactoryWithCreator >); + // Subviews for resources tables manager.add (CSMWorld::UniversalId::Type_Meshes, new CSVDoc::SubViewFactoryWithCreator); @@ -125,7 +131,6 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) CSMWorld::UniversalId::Type_Enchantment, CSMWorld::UniversalId::Type_BodyPart, CSMWorld::UniversalId::Type_SoundGen, - CSMWorld::UniversalId::Type_Pathgrid, CSMWorld::UniversalId::Type_None // end marker }; @@ -168,6 +173,9 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) manager.add (CSMWorld::UniversalId::Type_Journal, new CSVDoc::SubViewFactoryWithCreator (false)); + manager.add (CSMWorld::UniversalId::Type_Pathgrid, + new CSVDoc::SubViewFactoryWithCreator > (false)); + manager.add (CSMWorld::UniversalId::Type_DebugProfile, new CSVDoc::SubViewFactoryWithCreator > (false)); diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index 45e50dba7..07db5b477 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -21,6 +21,7 @@ #include "../../model/world/commanddispatcher.hpp" #include "../../model/prefs/state.hpp" +#include "../../model/prefs/shortcut.hpp" #include "tableeditidaction.hpp" #include "util.hpp" @@ -283,49 +284,72 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id, mEditAction = new QAction (tr ("Edit Record"), this); connect (mEditAction, SIGNAL (triggered()), this, SLOT (editRecord())); addAction (mEditAction); + CSMPrefs::Shortcut* editShortcut = new CSMPrefs::Shortcut("table-edit", this); + editShortcut->associateAction(mEditAction); if (createAndDelete) { mCreateAction = new QAction (tr ("Add Record"), this); connect (mCreateAction, SIGNAL (triggered()), this, SIGNAL (createRequest())); addAction (mCreateAction); + CSMPrefs::Shortcut* createShortcut = new CSMPrefs::Shortcut("table-add", this); + createShortcut->associateAction(mCreateAction); mCloneAction = new QAction (tr ("Clone Record"), this); connect(mCloneAction, SIGNAL (triggered()), this, SLOT (cloneRecord())); addAction(mCloneAction); + CSMPrefs::Shortcut* cloneShortcut = new CSMPrefs::Shortcut("table-clone", this); + cloneShortcut->associateAction(mCloneAction); } mRevertAction = new QAction (tr ("Revert Record"), this); connect (mRevertAction, SIGNAL (triggered()), mDispatcher, SLOT (executeRevert())); addAction (mRevertAction); + CSMPrefs::Shortcut* revertShortcut = new CSMPrefs::Shortcut("table-revert", this); + revertShortcut->associateAction(mRevertAction); mDeleteAction = new QAction (tr ("Delete Record"), this); connect (mDeleteAction, SIGNAL (triggered()), mDispatcher, SLOT (executeDelete())); addAction (mDeleteAction); + CSMPrefs::Shortcut* deleteShortcut = new CSMPrefs::Shortcut("table-remove", this); + deleteShortcut->associateAction(mDeleteAction); + mMoveUpAction = new QAction (tr ("Move Up"), this); connect (mMoveUpAction, SIGNAL (triggered()), this, SLOT (moveUpRecord())); addAction (mMoveUpAction); + CSMPrefs::Shortcut* moveUpShortcut = new CSMPrefs::Shortcut("table-moveup", this); + moveUpShortcut->associateAction(mMoveUpAction); mMoveDownAction = new QAction (tr ("Move Down"), this); connect (mMoveDownAction, SIGNAL (triggered()), this, SLOT (moveDownRecord())); addAction (mMoveDownAction); + CSMPrefs::Shortcut* moveDownShortcut = new CSMPrefs::Shortcut("table-movedown", this); + moveDownShortcut->associateAction(mMoveDownAction); mViewAction = new QAction (tr ("View"), this); connect (mViewAction, SIGNAL (triggered()), this, SLOT (viewRecord())); addAction (mViewAction); + CSMPrefs::Shortcut* viewShortcut = new CSMPrefs::Shortcut("table-view", this); + viewShortcut->associateAction(mViewAction); mPreviewAction = new QAction (tr ("Preview"), this); connect (mPreviewAction, SIGNAL (triggered()), this, SLOT (previewRecord())); addAction (mPreviewAction); + CSMPrefs::Shortcut* previewShortcut = new CSMPrefs::Shortcut("table-preview", this); + previewShortcut->associateAction(mPreviewAction); mExtendedDeleteAction = new QAction (tr ("Extended Delete Record"), this); connect (mExtendedDeleteAction, SIGNAL (triggered()), this, SLOT (executeExtendedDelete())); addAction (mExtendedDeleteAction); + CSMPrefs::Shortcut* extendedDeleteShortcut = new CSMPrefs::Shortcut("table-extendeddelete", this); + extendedDeleteShortcut->associateAction(mExtendedDeleteAction); mExtendedRevertAction = new QAction (tr ("Extended Revert Record"), this); connect (mExtendedRevertAction, SIGNAL (triggered()), this, SLOT (executeExtendedRevert())); addAction (mExtendedRevertAction); + CSMPrefs::Shortcut* extendedRevertShortcut = new CSMPrefs::Shortcut("table-extendedrevert", this); + extendedRevertShortcut->associateAction(mExtendedRevertAction); mEditIdAction = new TableEditIdAction (*this, this); connect (mEditIdAction, SIGNAL (triggered()), this, SLOT (editCell())); diff --git a/apps/opencs/view/world/tablesubview.cpp b/apps/opencs/view/world/tablesubview.cpp index 1b25e1c08..fcf66ed81 100644 --- a/apps/opencs/view/world/tablesubview.cpp +++ b/apps/opencs/view/world/tablesubview.cpp @@ -148,14 +148,14 @@ bool CSVWorld::TableSubView::eventFilter (QObject* object, QEvent* event) { if (QDropEvent* drop = dynamic_cast(event)) { - const CSMWorld::TableMimeData* data = dynamic_cast(drop->mimeData()); - if (!data) // May happen when non-records (e.g. plain text) are dragged and dropped + const CSMWorld::TableMimeData* tableMimeData = dynamic_cast(drop->mimeData()); + if (!tableMimeData) // May happen when non-records (e.g. plain text) are dragged and dropped return false; - bool handled = data->holdsType(CSMWorld::UniversalId::Type_Filter); + bool handled = tableMimeData->holdsType(CSMWorld::UniversalId::Type_Filter); if (handled) { - mFilterBox->setRecordFilter(data->returnMatching(CSMWorld::UniversalId::Type_Filter).getId()); + mFilterBox->setRecordFilter(tableMimeData->returnMatching(CSMWorld::UniversalId::Type_Filter).getId()); } return handled; } diff --git a/apps/opencs/view/world/util.cpp b/apps/opencs/view/world/util.cpp index b0431f71a..e44606652 100644 --- a/apps/opencs/view/world/util.cpp +++ b/apps/opencs/view/world/util.cpp @@ -212,6 +212,13 @@ QWidget *CSVWorld::CommandDelegate::createEditor (QWidget *parent, const QStyleO return sb; } + case CSMWorld::ColumnBase::Display_UnsignedInteger8: + { + DialogueSpinBox *sb = new DialogueSpinBox(parent); + sb->setRange(0, UCHAR_MAX); + return sb; + } + case CSMWorld::ColumnBase::Display_Var: return new QLineEdit(parent); diff --git a/apps/opencs/view/world/vartypedelegate.cpp b/apps/opencs/view/world/vartypedelegate.cpp index a63e6028c..8aa43d15b 100644 --- a/apps/opencs/view/world/vartypedelegate.cpp +++ b/apps/opencs/view/world/vartypedelegate.cpp @@ -4,6 +4,7 @@ #include "../../model/world/commands.hpp" #include "../../model/world/columns.hpp" +#include "../../model/world/commandmacro.hpp" void CSVWorld::VarTypeDelegate::addCommands (QAbstractItemModel *model, const QModelIndex& index, int type) const @@ -36,13 +37,10 @@ void CSVWorld::VarTypeDelegate::addCommands (QAbstractItemModel *model, const QM default: break; // ignore the rest } - getUndoStack().beginMacro ( - "Modify " + model->headerData (index.column(), Qt::Horizontal, Qt::DisplayRole).toString()); + CSMWorld::CommandMacro macro (getUndoStack(), "Modify " + model->headerData (index.column(), Qt::Horizontal, Qt::DisplayRole).toString()); - getUndoStack().push (new CSMWorld::ModifyCommand (*model, index, type)); - getUndoStack().push (new CSMWorld::ModifyCommand (*model, next, value)); - - getUndoStack().endMacro(); + macro.push (new CSMWorld::ModifyCommand (*model, index, type)); + macro.push (new CSMWorld::ModifyCommand (*model, next, value)); } CSVWorld::VarTypeDelegate::VarTypeDelegate (const std::vector >& values, diff --git a/apps/openmw-mp/CMakeLists.txt b/apps/openmw-mp/CMakeLists.txt new file mode 100644 index 000000000..abb770efb --- /dev/null +++ b/apps/openmw-mp/CMakeLists.txt @@ -0,0 +1,142 @@ +project(tes3mp-server) + +option(BUILD_WITH_PAWN "Enable Pawn language" OFF) +option(ENABLE_BREAKPAD "Enable Google Breakpad for Crash reporting" OFF) + +if(ENABLE_BREAKPAD) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DENABLE_BREAKPAD") + if (UNIX) + set(Breakpad_Headers "${CMAKE_SOURCE_DIR}/extern/breakpad/src/client/linux") + set(Breakpad_Library "${CMAKE_SOURCE_DIR}/extern/breakpad/src/client/linux/libbreakpad_client.a") + elseif(WIN32) + set(Breakpad_Headers "${CMAKE_SOURCE_DIR}/extern/breakpad/src/client/windows") + set(Breakpad_Library "-lbreakpad_client") + endif (UNIX) + include_directories(${CMAKE_SOURCE_DIR}/extern/breakpad/src ${Breakpad_Headers}) +endif(ENABLE_BREAKPAD) + +if(BUILD_WITH_PAWN) + + add_subdirectory(amx) + + #set(Pawn_ROOT ${CMAKE_SOURCE_DIR}/external/pawn/) + set(Pawn_INCLUDES ${Pawn_ROOT}/include) + set(Pawn_LIBRARY ${Pawn_ROOT}/lib/libamx.a) + set(PawnScript_Sources + Script/LangPawn/LangPAWN.cpp + Script/LangPawn/PawnFunc.cpp) + set(PawnScript_Headers ${Pawn_INCLUDES} + Script/LangPawn/LangPAWN.hpp + ) + + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DENABLE_PAWN -DPAWN_CELL_SIZE=64") + #include_directories(${Pawn_INCLUDES}) + include_directories("./amx/linux") +endif(BUILD_WITH_PAWN) + +option(BUILD_WITH_LUA "Enable Terra/Lua language" ON) +option(FORCE_LUA "Use Lua instead Terra" OFF) +if(BUILD_WITH_LUA) + #set(Terra_ROOT ${CMAKE_SOURCE_DIR}/external/terra/) + if(WIN32 OR FORCE_LUA) + find_package(Lua51 REQUIRED) + MESSAGE(STATUS "Found LUA_LIBRARY: ${LUA_LIBRARY}") + MESSAGE(STATUS "Found LUA_INCLUDE_DIR: ${LUA_INCLUDE_DIR}") + else() + find_package(Terra REQUIRED) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DENABLE_TERRA") + endif() + set(LuaScript_Sources + Script/LangLua/LangLua.cpp + Script/LangLua/LuaFunc.cpp) + set(LuaScript_Headers ${Terra_INCLUDES} ${LUA_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/extern/LuaBridge ${CMAKE_SOURCE_DIR}/extern/LuaBridge/detail + Script/LangLua/LangLua.hpp) + + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DENABLE_LUA") + include_directories(${Terra_INCLUDES} ${LUA_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/extern/LuaBridge) +endif(BUILD_WITH_LUA) + +set(NativeScript_Sources + Script/LangNative/LangNative.cpp + ) +set(NativeScript_Headers + Script/LangNative/LangNative.hpp + ) + +# local files +set(SERVER + main.cpp + Player.cpp + Networking.cpp + Utils.cpp + MasterClient.cpp + Cell.cpp + Script/Script.cpp Script/ScriptFunction.cpp + Script/ScriptFunctions.cpp + + Script/Functions/CharClass.cpp Script/Functions/Chat.cpp Script/Functions/GUI.cpp + Script/Functions/Items.cpp Script/Functions/Quests.cpp Script/Functions/Stats.cpp + Script/Functions/Spells.cpp Script/Functions/Timer.cpp Script/Functions/Positions.cpp + Script/Functions/Cells.cpp Script/Functions/World.cpp + + Script/API/TimerAPI.cpp Script/API/PublicFnAPI.cpp + ${PawnScript_Sources} + ${LuaScript_Sources} + ${NativeScript_Sources} + +) + +set(SERVER_HEADER + Script/Types.hpp Script/Script.hpp Script/SystemInterface.hpp + Script/ScriptFunction.hpp Script/Platform.hpp Script/Language.hpp + Script/ScriptFunctions.hpp Script/API/TimerAPI.hpp Script/API/PublicFnAPI.hpp + ${PawnScript_Headers} + ${LuaScript_Headers} + ${NativeScript_Headers} +) +source_group(tes3mp-server FILES ${SERVER} ${SERVER_HEADER}) + +include_directories("./") + +# Main executable + +add_executable(tes3mp-server +${SERVER_FILES} +${SERVER} ${SERVER_HEADER} +${APPLE_BUNDLE_RESOURCES} +) +add_definitions(-std=gnu++14 -Wno-ignored-qualifiers) + +target_link_libraries(tes3mp-server + #${Boost_SYSTEM_LIBRARY} + #${Boost_THREAD_LIBRARY} + #${Boost_FILESYSTEM_LIBRARY} + #${Boost_PROGRAM_OPTIONS_LIBRARY} + ${RakNet_LIBRARY} + components + ${Terra_LIBRARY} + ${LUA_LIBRARIES} + ${Pawn_LIBRARY} + ${Breakpad_Library} +) + +if (UNIX) + target_link_libraries(tes3mp-server dl) + # Fix for not visible pthreads functions for linker with glibc 2.15 + if(NOT APPLE) + target_link_libraries(tes3mp-server ${CMAKE_THREAD_LIBS_INIT}) + endif(NOT APPLE) +endif(UNIX) + +if (BUILD_WITH_CODE_COVERAGE) + add_definitions (--coverage) + target_link_libraries(tes3mp-server gcov) +endif() + +if (MSVC) + # Debug version needs increased number of sections beyond 2^16 + if (CMAKE_CL_64) + set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /bigobj") + endif (CMAKE_CL_64) + add_definitions("-D_USE_MATH_DEFINES") +endif (MSVC) diff --git a/apps/openmw-mp/Cell.cpp b/apps/openmw-mp/Cell.cpp new file mode 100644 index 000000000..b3176d9ea --- /dev/null +++ b/apps/openmw-mp/Cell.cpp @@ -0,0 +1,231 @@ +// +// Created by koncord on 18.02.17. +// + +#include "Cell.hpp" + +#include +#include "Player.hpp" + +using namespace std; + +void Cell::addPlayer(Player *player) +{ + auto it = find(player->cells.begin(), player->cells.end(), this); + if (it == player->cells.end()) + player->cells.push_back(this); + players.push_back(player); +} + +void Cell::removePlayer(Player *player) +{ + for (Iterator it = begin(); it != end(); it++) + { + if (*it == player) + { + auto it2 = find(player->cells.begin(), player->cells.end(), this); + if (it2 != player->cells.end()) + player->cells.erase(it2); + players.erase(it); + return; + } + } +} + +Cell::TPlayers Cell::getPlayers() const +{ + return players; +} + +void Cell::sendToLoaded(mwmp::WorldPacket *worldPacket, mwmp::BaseEvent *baseEvent) const +{ + if (players.empty()) + return; + + std::list plList; + + for (auto pl : players) + plList.push_back(pl); + + plList.sort(); + plList.unique(); + + for (auto pl : plList) + { + if (pl->guid == baseEvent->guid) continue; + worldPacket->Send(baseEvent, pl->guid); + } +} + +std::string Cell::getDescription() const +{ + return cell.getDescription(); +} + +CellController::CellController() +{ + +} + +CellController::~CellController() +{ + for(auto cell : cells) + { + delete cell; + } +} + +CellController *CellController::sThis = nullptr; + +void CellController::create() +{ + assert(!sThis); + sThis = new CellController; +} + +void CellController::destroy() +{ + assert(sThis); + delete sThis; + sThis = nullptr; +} + +CellController *CellController::get() +{ + assert(sThis); + return sThis; +} + +Cell *CellController::getCell(ESM::Cell *esmCell) +{ + if (esmCell->isExterior()) + return getCellByXY(esmCell->mData.mX, esmCell->mData.mY); + else + return getCellByName(esmCell->mName); +} + + +Cell *CellController::getCellByXY(int x, int y) +{ + auto it = find_if(cells.begin(), cells.end(), [x, y](const Cell *c) { + return c->cell.mData.mX == x && c->cell.mData.mY == y; + }); + if (it == cells.end()) + return nullptr; + return *it; +} + +Cell *CellController::getCellByName(std::string cellName) +{ + auto it = find_if(cells.begin(), cells.end(), [cellName](const Cell *c) { + return c->cell.mName == cellName; + }); + if (it == cells.end()) + return nullptr; + return *it; +} + +Cell *CellController::addCell(ESM::Cell cellData) +{ + LOG_MESSAGE_SIMPLE(Log::LOG_VERBOSE, "Loaded cells: %d", cells.size()); + auto it = find_if(cells.begin(), cells.end(), [cellData](const Cell *c) { + //return c->cell.sRecordId == cellData.sRecordId; // Currently we cannot compare because plugin lists can be loaded in different order + return c->cell.isExterior() ? (c->cell.mData.mX == cellData.mData.mX && c->cell.mData.mY == cellData.mData.mY) : + (c->cell.mName == cellData.mName); + }); + Cell *cell; + if (it == cells.end()) + { + cell = new Cell(cellData); + cells.push_back(cell); + } + else + cell = *it; + return cell; + + +} + +void CellController::removeCell(Cell *cell) +{ + if (cell == nullptr) + return; + for (auto it = cells.begin(); it != cells.end();) + { + if (*it != nullptr && *it == cell) + { + delete *it; + it = cells.erase(it); + } + else + ++it; + } +} + +void CellController::removePlayer(Cell *cell, Player *player) +{ + cell->removePlayer(player); + + if (cell->players.empty()) + { + LOG_MESSAGE_SIMPLE(Log::LOG_VERBOSE, "Deleting empty cell from memory: %s", player->npc.mName.c_str(), + player->getId(), cell->cell.getDescription().c_str()); + auto it = find(cells.begin(), cells.end(), cell); + delete *it; + cells.erase(it); + } +} + +void CellController::deletePlayer(Player *player) +{ + + for_each(player->getCells()->begin(), player->getCells()->end(), [&player](Cell *cell) { + for (auto it = cell->begin(); it != cell->end(); ++it) + { + if (*it == player) + { + cell->players.erase(it); + break; + } + } + }); +} + +void CellController::update(Player *player) +{ + for (auto cell : player->cellStateChanges.cellStates) + { + if (cell.type == mwmp::CellState::LOAD) + { + Cell *c = addCell(cell.cell); + c->addPlayer(player); + } + else + { + LOG_MESSAGE_SIMPLE(Log::LOG_VERBOSE, "Player %s (%d) unloaded cell: %s", player->npc.mName.c_str(), player->getId(), cell.cell.getDescription().c_str()); + Cell *c; + if (!cell.cell.isExterior()) + c = getCellByName(cell.cell.mName); + else + c = getCellByXY(cell.cell.getGridX(), cell.cell.getGridY()); + + if (c != nullptr) + removePlayer(c, player); + } + } +} + +Cell::Cell(ESM::Cell cell): cell(cell) +{ + +} + +Cell::Iterator Cell::begin() const +{ + return players.begin(); +} + +Cell::Iterator Cell::end() const +{ + return players.end(); +} diff --git a/apps/openmw-mp/Cell.hpp b/apps/openmw-mp/Cell.hpp new file mode 100644 index 000000000..26eb26bc6 --- /dev/null +++ b/apps/openmw-mp/Cell.hpp @@ -0,0 +1,76 @@ +// +// Created by koncord on 18.02.17. +// + +#ifndef OPENMW_CELL_HPP +#define OPENMW_CELL_HPP + +#include +#include +#include +#include +#include + +class Player; +class Cell; + + +class CellController +{ +private: + CellController(); + ~CellController(); + + CellController(CellController&); // not used +public: + static void create(); + static void destroy(); + static CellController *get(); +public: + typedef std::deque TContainer; + typedef TContainer::iterator TIter; + + Cell * addCell(ESM::Cell cell); + void removeCell(Cell *); + + void removePlayer(Cell *cell, Player *player); + void deletePlayer(Player *player); + + Cell *getCell(ESM::Cell *esmCell); + Cell *getCellByXY(int x, int y); + Cell *getCellByName(std::string cellName); + + void update(Player *player); + +private: + static CellController *sThis; + TContainer cells; +}; + +class Cell +{ + friend class CellController; +public: + Cell(ESM::Cell cell); + typedef std::deque TPlayers; + typedef TPlayers::const_iterator Iterator; + + Iterator begin() const; + Iterator end() const; + + void addPlayer(Player *player); + void removePlayer(Player *player); + + TPlayers getPlayers() const; + void sendToLoaded(mwmp::WorldPacket *worldPacket, mwmp::BaseEvent *baseEvent) const; + + std::string getDescription() const; + + +private: + TPlayers players; + ESM::Cell cell; +}; + + +#endif //OPENMW_CELL_HPP diff --git a/apps/openmw-mp/MasterClient.cpp b/apps/openmw-mp/MasterClient.cpp new file mode 100644 index 000000000..72a50af84 --- /dev/null +++ b/apps/openmw-mp/MasterClient.cpp @@ -0,0 +1,197 @@ +// +// Created by koncord on 14.08.16. +// + +#include +#include +#include +#include +#include +#include +#include "MasterClient.hpp" +#include +#include +#include "Networking.hpp" + +using namespace std; + +bool MasterClient::sRun = false; + +MasterClient::MasterClient(std::string queryAddr, unsigned short queryPort, std::string serverAddr, + unsigned short serverPort) : queryAddr(queryAddr), queryPort(queryPort), + serverAddr(serverAddr), serverPort(serverPort) +{ + players = 0; + maxPlayers = 0; + hostname = ""; + modname = ""; + timeout = 1000; // every 1 seconds +} + +void MasterClient::SetPlayers(unsigned pl) +{ + mutexData.lock(); + players = pl; + mutexData.unlock(); +} + +void MasterClient::SetMaxPlayers(unsigned pl) +{ + mutexData.lock(); + maxPlayers = pl; + mutexData.unlock(); +} + +void MasterClient::SetHostname(std::string hostname) +{ + mutexData.lock(); + this->hostname = hostname.substr(0, 200); + mutexData.unlock(); +} + +void MasterClient::SetModname(std::string modname) +{ + mutexData.lock(); + this->modname = modname.substr(0, 200); + mutexData.unlock(); +} + +RakNet::RakString +MasterClient::Send(std::string hostname, std::string modname, unsigned maxPlayers, bool update, unsigned players) +{ + /*static unsigned short oldServerPort, oldQueryPort; + static string oldMotd; + static unsigned oldPlayers, oldMaxPlayers;*/ + std::stringstream sstr; + mutexData.lock(); + sstr << "{"; + sstr << "\"port\": " << serverPort << ", "; + sstr << "\"query_port\": " << queryPort << ", "; + sstr << "\"hostname\": \"" << hostname.c_str() << "\", "; + sstr << "\"modname\": \"" << modname.c_str() << "\", "; + sstr << "\"players\": " << players << ", "; + sstr << "\"max_players\": " << maxPlayers << ", "; + sstr << "\"version\": \"" << TES3MP_VERSION << "\", "; + sstr << "\"passw\": " << (mwmp::Networking::get().isPassworded() ? "true" : "false"); + sstr << "}"; + mutexData.unlock(); + + std::string contentType = "application/json"; + RakNet::RakString createRequest; + + if (update) + createRequest = RakNet::RakString::FormatForPUT( + string("/api/servers/" + serverAddr + ":" + to_string(serverPort)).c_str(), contentType.c_str(), + sstr.str().c_str()); + else + createRequest = RakNet::RakString::FormatForPOST("/api/servers", contentType.c_str(), + sstr.str().c_str()); + + httpConnection->TransmitRequest(createRequest.C_String(), queryAddr.c_str(), queryPort); + + RakNet::Packet *packet; + RakNet::SystemAddress sa; + + RakNet::RakString transmitted, hostTransmitted; + RakNet::RakString response; + RakNet::SystemAddress hostReceived; + int contentOffset; + + while (true) + { + + // This is kind of crappy, but for TCP plugins, always do HasCompletedConnectionAttempt, + // then Receive(), then HasFailedConnectionAttempt(),HasLostConnection() + sa = tcpInterface.HasCompletedConnectionAttempt(); + if (sa != RakNet::UNASSIGNED_SYSTEM_ADDRESS) + LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Connected to master server: %s", sa.ToString()); + + sa = tcpInterface.HasFailedConnectionAttempt(); + if (sa != RakNet::UNASSIGNED_SYSTEM_ADDRESS) + { + LOG_MESSAGE_SIMPLE(Log::LOG_WARN, "Failed to connect to master server: %s", sa.ToString()); + return "FAIL_CONNECT"; + } + sa = tcpInterface.HasLostConnection(); + if (sa != RakNet::UNASSIGNED_SYSTEM_ADDRESS) + { + LOG_MESSAGE_SIMPLE(Log::LOG_WARN, "Lost connection to master server: %s", sa.ToString()); + return "LOST_CONNECTION"; + } + + for (packet = tcpInterface.Receive(); packet; tcpInterface.DeallocatePacket( + packet), packet = tcpInterface.Receive()); + + if (httpConnection->GetResponse(transmitted, hostTransmitted, response, hostReceived, contentOffset)) + { + if (contentOffset < 0) + return "NO_CONTENT"; // no content + tcpInterface.CloseConnection(sa); + return (response.C_String() + contentOffset); + } + RakSleep(30); + } + +} + +void MasterClient::Update() +{ + assert(!sRun); + + httpConnection = RakNet::HTTPConnection2::GetInstance(); + tcpInterface.Start(0, 64); + tcpInterface.AttachPlugin(httpConnection); + + RakNet::RakString response = Send(hostname, modname, maxPlayers, false, players); + bool update = true; + sRun = true; + while (sRun) + { + if (response == "Created") + LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Server registered on the master server."); + else if (response == "Accepted") + LOG_MESSAGE_SIMPLE(Log::LOG_VERBOSE, "Sent info update to master server."); + else if (response == "bad request") + { + LOG_MESSAGE_SIMPLE(Log::LOG_WARN, "Update rate is too low, and the master server has deleted information about" + " the server. Trying low rate..."); + if ((timeout - step_rate) >= step_rate) + SetUpdateRate(timeout - step_rate); + update = false; + } + else + { + /*cout << "Error: \""<< response << "\"" << endl; + cout << response.GetLength() << endl;*/ + } + + RakSleep(timeout); + + players = Players::getPlayers()->size(); + response = Send(hostname, modname, maxPlayers, update, players); + update = true; + } +} + +void MasterClient::Start() +{ + thrQuery = thread(&MasterClient::Update, this); +} + +void MasterClient::Stop() +{ + if (!sRun) + return; + sRun = false; + if (thrQuery.joinable()) + thrQuery.join(); +} + +void MasterClient::SetUpdateRate(unsigned int rate) +{ + if (timeout < min_rate) + timeout = min_rate; + else if (timeout > max_rate) + timeout = max_rate; + timeout = rate; +} diff --git a/apps/openmw-mp/MasterClient.hpp b/apps/openmw-mp/MasterClient.hpp new file mode 100644 index 000000000..c34c8b9e1 --- /dev/null +++ b/apps/openmw-mp/MasterClient.hpp @@ -0,0 +1,52 @@ +// +// Created by koncord on 14.08.16. +// + +#ifndef OPENMW_MASTERCLIENT_HPP +#define OPENMW_MASTERCLIENT_HPP + +#include +#include +#include +#include +#include + +class MasterClient +{ +public: + static const unsigned int step_rate = 1000; + static const unsigned int min_rate = 1000; + static const unsigned int max_rate = 30000; +public: + MasterClient(std::string queryAddr, unsigned short queryPort, std::string serverAddr, unsigned short serverPort); + void SetPlayers(unsigned pl); + void SetMaxPlayers(unsigned pl); + void SetHostname(std::string hostname); + void SetModname(std::string hostname); + void Update(); + void Start(); + void Stop(); + void SetUpdateRate(unsigned int rate); + +private: + RakNet::RakString + Send(std::string hostname, std::string modname, unsigned maxPlayers, bool update, unsigned players); +private: + std::string queryAddr; + unsigned short queryPort; + std::string serverAddr; + unsigned short serverPort; + std::string hostname; + std::string modname; + unsigned players, maxPlayers; + RakNet::HTTPConnection2 *httpConnection; + RakNet::TCPInterface tcpInterface; + unsigned int timeout; + static bool sRun; + std::mutex mutexData; + std::thread thrQuery; + +}; + + +#endif //OPENMW_MASTERCLIENT_HPP diff --git a/apps/openmw-mp/Networking.cpp b/apps/openmw-mp/Networking.cpp new file mode 100644 index 000000000..54c737d62 --- /dev/null +++ b/apps/openmw-mp/Networking.cpp @@ -0,0 +1,936 @@ +// +// Created by koncord on 12.01.16. +// + +#include "Player.hpp" +#include +#include +#include +#include +#include +#include