diff --git a/.gitignore b/.gitignore index b68d13cde..9a24b0107 100644 --- a/.gitignore +++ b/.gitignore @@ -10,7 +10,6 @@ prebuilt ## doxygen Doxygen -!docs/cs-manual/Makefile ## ides/editors *~ @@ -35,17 +34,15 @@ resources ## binaries /esmtool -/mwiniimport -/omwlauncher /openmw /opencs /niftest -bsatool -openmw-cs -openmw-essimporter -openmw-iniimporter -openmw-launcher -openmw-wizard +/bsatool +/openmw-cs +/openmw-essimporter +/openmw-iniimporter +/openmw-launcher +/openmw-wizard ## generated objects apps/openmw/config.hpp @@ -80,4 +77,4 @@ moc_*.cxx *.so gamecontrollerdb.txt openmw.appdata.xml - +venv/ diff --git a/CMakeLists.txt b/CMakeLists.txt index cfd34c261..25d34709e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -77,6 +77,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) diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index f8cf1e2d8..6e19c03b2 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -86,12 +86,12 @@ opencs_units (view/widget opencs_units (view/render scenewidget worldspacewidget pagedworldspacewidget unpagedworldspacewidget previewwidget editmode instancemode instanceselectionmode instancemovemode - orbitcameramode pathgridmode selectionmode pathgridselectionmode + orbitcameramode pathgridmode selectionmode pathgridselectionmode cameracontroller ) opencs_units_noqt (view/render lighting lightingday lightingnight lightingbright object cell terrainstorage tagbase - cellarrow cellmarker cellborder cameracontroller pathgrid + cellarrow cellmarker cellborder pathgrid ) opencs_hdrs_noqt (view/render @@ -108,11 +108,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 diff --git a/apps/opencs/model/prefs/modifiersetting.cpp b/apps/opencs/model/prefs/modifiersetting.cpp new file mode 100644 index 000000000..d8bf84342 --- /dev/null +++ b/apps/opencs/model/prefs/modifiersetting.cpp @@ -0,0 +1,146 @@ +#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) + , 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..7a90a1d08 --- /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 independant 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..726566fdd --- /dev/null +++ b/apps/opencs/model/prefs/shortcutsetting.cpp @@ -0,0 +1,196 @@ +#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) + , 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 c70e71deb..718d9a107 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; @@ -165,16 +167,6 @@ 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); 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); @@ -186,10 +178,6 @@ void CSMPrefs::State::declare() 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); - 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); declareSeparator(); declareBool ("context-select", "Context Sensitive Selection", false); declareDouble ("drag-factor", "Mouse sensitivity during drag operations", 1.0). @@ -224,6 +212,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) @@ -340,6 +441,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()) @@ -351,6 +496,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); @@ -369,10 +525,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() @@ -396,6 +552,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 fffadee5e..1e46c68ee 100644 --- a/apps/opencs/model/prefs/state.hpp +++ b/apps/opencs/model/prefs/state.hpp @@ -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/view/doc/view.cpp b/apps/opencs/view/doc/view.cpp index 6f14e5a4d..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; diff --git a/apps/opencs/view/doc/view.hpp b/apps/opencs/view/doc/view.hpp index d95499191..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(); 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 index dc356827a..7e3570657 100644 --- a/apps/opencs/view/render/cameracontroller.cpp +++ b/apps/opencs/view/render/cameracontroller.cpp @@ -2,7 +2,7 @@ #include -#include +#include #include #include @@ -14,6 +14,10 @@ #include +#include "../../model/prefs/shortcut.hpp" + +#include "scenewidget.hpp" + namespace CSVRender { @@ -27,8 +31,9 @@ namespace CSVRender const osg::Vec3d CameraController::LocalLeft = osg::Vec3d(1, 0, 0); const osg::Vec3d CameraController::LocalForward = osg::Vec3d(0, 0, 1); - CameraController::CameraController() - : mActive(false) + CameraController::CameraController(QObject* parent) + : QObject(parent) + , mActive(false) , mInverted(false) , mCameraSensitivity(1/650.f) , mSecondaryMoveMult(50) @@ -73,11 +78,19 @@ namespace CSVRender void CameraController::setCamera(osg::Camera* camera) { + bool wasActive = mActive; + mCamera = camera; mActive = (mCamera != NULL); - if (mActive) - onActivate(); + 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) @@ -136,14 +149,23 @@ namespace CSVRender getCamera()->setViewMatrixAsLookAt(eye, center, up); } + void CameraController::addShortcut(CSMPrefs::Shortcut* shortcut) + { + mShortcuts.push_back(shortcut); + } + /* Free Camera Controller */ - FreeCameraController::FreeCameraController() - : mLockUpright(false) + FreeCameraController::FreeCameraController(QWidget* widget) + : CameraController(widget) + , mLockUpright(false) , mModified(false) + , mNaviPrimary(false) + , mNaviSecondary(false) , mFast(false) + , mFastAlternate(false) , mLeft(false) , mRight(false) , mForward(false) @@ -155,6 +177,61 @@ namespace CSVRender , 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 @@ -199,59 +276,18 @@ namespace CSVRender mLockUpright = false; } - bool FreeCameraController::handleKeyEvent(QKeyEvent* event, bool pressed) + void FreeCameraController::handleMouseMoveEvent(int x, int y) { if (!isActive()) - return false; - - if (event->key() == Qt::Key_Q) - { - mRollLeft = pressed; - } - else if (event->key() == Qt::Key_E) - { - mRollRight = pressed; - } - else if (event->key() == Qt::Key_A) - { - mLeft = pressed; - } - else if (event->key() == Qt::Key_D) - { - mRight = pressed; - } - else if (event->key() == Qt::Key_W) - { - mForward = pressed; - } - else if (event->key() == Qt::Key_S) - { - mBackward = pressed; - } - else if (event->key() == Qt::Key_Shift) - { - mFast = pressed; - } - else - { - return false; - } - - return true; - } - - bool FreeCameraController::handleMouseMoveEvent(std::string mode, int x, int y) - { - if (!isActive()) - return false; + return; - if (mode == "p-navi") + if (mNaviPrimary) { double scalar = getCameraSensitivity() * (getInverted() ? -1.0 : 1.0); yaw(x * scalar); pitch(y * scalar); } - else if (mode == "s-navi") + else if (mNaviSecondary) { osg::Vec3d movement; movement += LocalLeft * -x * getSecondaryMovementMultiplier(); @@ -259,16 +295,14 @@ namespace CSVRender translate(movement); } - else if (mode == "t-navi") - { - translate(LocalForward * x * (mFast ? getWheelMovementMultiplier() : 1)); - } - else - { - return false; - } + } - return true; + void FreeCameraController::handleMouseScrollEvent(int x) + { + if (!isActive()) + return; + + translate(LocalForward * x * ((mFast ^ mFastAlternate) ? getWheelMovementMultiplier() : 1)); } void FreeCameraController::update(double dt) @@ -279,7 +313,7 @@ namespace CSVRender double linDist = mLinSpeed * dt; double rotDist = mRotSpeed * dt; - if (mFast) + if (mFast ^ mFastAlternate) linDist *= mSpeedMult; if (mLeft) @@ -308,17 +342,6 @@ namespace CSVRender getCamera()->getViewMatrix().orthoNormal(getCamera()->getViewMatrix()); } - void FreeCameraController::resetInput() - { - mFast = false; - mLeft = false; - mRight = false; - mForward = false; - mBackward = false; - mRollLeft = false; - mRollRight = false; - } - void FreeCameraController::yaw(double value) { getCamera()->getViewMatrix() *= osg::Matrixd::rotate(value, LocalUp); @@ -368,13 +391,67 @@ namespace CSVRender 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() - : mInitialized(false) + OrbitCameraController::OrbitCameraController(QWidget* widget) + : CameraController(widget) + , mInitialized(false) + , mNaviPrimary(false) + , mNaviSecondary(false) , mFast(false) + , mFastAlternate(false) , mLeft(false) , mRight(false) , mUp(false) @@ -387,6 +464,61 @@ namespace CSVRender , 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 @@ -437,65 +569,21 @@ namespace CSVRender mPickingMask = value; } - bool OrbitCameraController::handleKeyEvent(QKeyEvent* event, bool pressed) - { - if (!isActive()) - return false; - - if (!mInitialized) - initialize(); - - if (event->key() == Qt::Key_Q) - { - mRollLeft = pressed; - } - else if (event->key() == Qt::Key_E) - { - mRollRight = pressed; - } - else if (event->key() == Qt::Key_A) - { - mLeft = pressed; - } - else if (event->key() == Qt::Key_D) - { - mRight = pressed; - } - else if (event->key() == Qt::Key_W) - { - mUp = pressed; - } - else if (event->key() == Qt::Key_S) - { - mDown = pressed; - } - else if (event->key() == Qt::Key_Shift) - { - mFast = pressed; - } - else - { - return false; - } - - return true; - } - - bool OrbitCameraController::handleMouseMoveEvent(std::string mode, int x, int y) + void OrbitCameraController::handleMouseMoveEvent(int x, int y) { if (!isActive()) - return false; + return; if (!mInitialized) initialize(); - if (mode == "p-navi") + if (mNaviPrimary) { double scalar = getCameraSensitivity() * (getInverted() ? -1.0 : 1.0); rotateHorizontal(x * scalar); rotateVertical(-y * scalar); } - else if (mode == "s-navi") + else if (mNaviSecondary) { osg::Vec3d movement; movement += LocalLeft * x * getSecondaryMovementMultiplier(); @@ -503,16 +591,14 @@ namespace CSVRender translate(movement); } - else if (mode == "t-navi") - { - zoom(-x * (mFast ? getWheelMovementMultiplier() : 1)); - } - else - { - return false; - } + } + + void OrbitCameraController::handleMouseScrollEvent(int x) + { + if (!isActive()) + return; - return true; + zoom(-x * ((mFast ^ mFastAlternate) ? getWheelMovementMultiplier() : 1)); } void OrbitCameraController::update(double dt) @@ -525,7 +611,7 @@ namespace CSVRender double rotDist = mOrbitSpeed * dt; - if (mFast) + if (mFast ^ mFastAlternate) rotDist *= mOrbitSpeedMult; if (mLeft) @@ -546,17 +632,6 @@ namespace CSVRender getCamera()->getViewMatrix().orthoNormal(getCamera()->getViewMatrix()); } - void OrbitCameraController::resetInput() - { - mFast = false; - mLeft = false; - mRight =false; - mUp = false; - mDown = false; - mRollLeft = false; - mRollRight = false; - } - void OrbitCameraController::onActivate() { mInitialized = false; @@ -647,4 +722,55 @@ namespace CSVRender 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 index f9021f04b..97af85790 100644 --- a/apps/opencs/view/render/cameracontroller.hpp +++ b/apps/opencs/view/render/cameracontroller.hpp @@ -2,22 +2,32 @@ #define OPENCS_VIEW_CAMERACONTROLLER_H #include +#include + +#include #include #include -class QKeyEvent; - namespace osg { class Camera; class Group; } +namespace CSMPrefs +{ + class Shortcut; +} + namespace CSVRender { - class CameraController + class SceneWidget; + + class CameraController : public QObject { + Q_OBJECT + public: static const osg::Vec3d WorldUp; @@ -26,7 +36,7 @@ namespace CSVRender static const osg::Vec3d LocalLeft; static const osg::Vec3d LocalForward; - CameraController(); + CameraController(QObject* parent); virtual ~CameraController(); bool isActive() const; @@ -46,17 +56,17 @@ namespace CSVRender // moves the camera to an intelligent position void setup(osg::Group* root, unsigned int mask, const osg::Vec3d& up); - virtual bool handleKeyEvent(QKeyEvent* event, bool pressed) = 0; - virtual bool handleMouseMoveEvent(std::string mode, int x, int y) = 0; + virtual void handleMouseMoveEvent(int x, int y) = 0; + virtual void handleMouseScrollEvent(int x) = 0; virtual void update(double dt) = 0; - virtual void resetInput() = 0; - protected: virtual void onActivate(){} + void addShortcut(CSMPrefs::Shortcut* shortcut); + private: bool mActive, mInverted; @@ -65,13 +75,17 @@ namespace CSVRender double mWheelMoveMult; osg::Camera* mCamera; + + std::vector mShortcuts; }; class FreeCameraController : public CameraController { + Q_OBJECT + public: - FreeCameraController(); + FreeCameraController(QWidget* parent); double getLinearSpeed() const; double getRotationalSpeed() const; @@ -84,13 +98,11 @@ namespace CSVRender void fixUpAxis(const osg::Vec3d& up); void unfixUpAxis(); - bool handleKeyEvent(QKeyEvent* event, bool pressed); - bool handleMouseMoveEvent(std::string mode, int x, int y); + void handleMouseMoveEvent(int x, int y); + void handleMouseScrollEvent(int x); void update(double dt); - void resetInput(); - private: void yaw(double value); @@ -101,19 +113,36 @@ namespace CSVRender void stabilize(); bool mLockUpright, mModified; - bool mFast, mLeft, mRight, mForward, mBackward, mRollLeft, mRollRight; + 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(); + OrbitCameraController(QWidget* parent); osg::Vec3d getCenter() const; double getOrbitSpeed() const; @@ -125,13 +154,11 @@ namespace CSVRender void setOrbitSpeedMultiplier(double value); void setPickingMask(unsigned int value); - bool handleKeyEvent(QKeyEvent* event, bool pressed); - bool handleMouseMoveEvent(std::string mode, int x, int y); + void handleMouseMoveEvent(int x, int y); + void handleMouseScrollEvent(int x); void update(double dt); - void resetInput(); - private: void onActivate(); @@ -145,13 +172,28 @@ namespace CSVRender void zoom(double value); bool mInitialized; - bool mFast, mLeft, mRight, mUp, mDown, mRollLeft, mRollRight; + 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(); }; } 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/instancemode.cpp b/apps/opencs/view/render/instancemode.cpp index df5ba7621..4eb9ea388 100644 --- a/apps/opencs/view/render/instancemode.cpp +++ b/apps/opencs/view/render/instancemode.cpp @@ -42,14 +42,14 @@ void CSVRender::InstanceMode::activate (CSVWidget::SceneToolbar *toolbar) 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
      • " "
      " "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
        • " "
        " "Not implemented yet"); diff --git a/apps/opencs/view/render/instancemovemode.cpp b/apps/opencs/view/render/instancemovemode.cpp index 9929f5fcb..fe58b581b 100644 --- a/apps/opencs/view/render/instancemovemode.cpp +++ b/apps/opencs/view/render/instancemovemode.cpp @@ -4,8 +4,8 @@ CSVRender::InstanceMoveMode::InstanceMoveMode (QWidget *parent) : ModeButton (QIcon (QPixmap (":placeholder")), "Move selected instances" - "
        • Use primary edit to move instances around freely
        • " - "
        • Use secondary edit to move instances around within the grid
        • " + "
          • 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/orbitcameramode.cpp b/apps/opencs/view/render/orbitcameramode.cpp index e6f3612c6..c7d980454 100644 --- a/apps/opencs/view/render/orbitcameramode.cpp +++ b/apps/opencs/view/render/orbitcameramode.cpp @@ -2,6 +2,9 @@ #include +#include "../../model/prefs/shortcut.hpp" +#include "../../model/prefs/shortcuteventhandler.hpp" + #include "worldspacewidget.hpp" namespace CSVRender @@ -11,13 +14,29 @@ namespace CSVRender : ModeButton(icon, tooltip, parent) , mWorldspaceWidget(worldspaceWidget) , mCenterOnSelection(0) + { + mCenterShortcut.reset(new CSMPrefs::Shortcut("orbit-center-selection", worldspaceWidget)); + mCenterShortcut->enable(false); + connect(mCenterShortcut.get(), 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) diff --git a/apps/opencs/view/render/orbitcameramode.hpp b/apps/opencs/view/render/orbitcameramode.hpp index cd8387084..4f72de957 100644 --- a/apps/opencs/view/render/orbitcameramode.hpp +++ b/apps/opencs/view/render/orbitcameramode.hpp @@ -1,8 +1,15 @@ #ifndef CSV_RENDER_ORBITCAMERAPICKMODE_H #define CSV_RENDER_ORBITCAMERAPICKMODE_H +#include + #include "../widget/modebutton.hpp" +namespace CSMPrefs +{ + class Shortcut; +} + namespace CSVRender { class WorldspaceWidget; @@ -15,14 +22,17 @@ namespace CSVRender 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; + std::auto_ptr mCenterShortcut; private slots: diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index a01df4392..ab2e252af 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -8,6 +8,8 @@ #include +#include "../../model/prefs/shortcut.hpp" + #include "../../model/world/tablemimedata.hpp" #include "../../model/world/idtable.hpp" @@ -142,75 +144,71 @@ void CSVRender::PagedWorldspaceWidget::addEditModeSelectorButtons ( "terrain-move"); } -void CSVRender::PagedWorldspaceWidget::handleMouseClick (const WorldspaceHitResult& hit, const std::string& button, - bool shift) +void CSVRender::PagedWorldspaceWidget::handleInteractionPress (const WorldspaceHitResult& hit, InteractionType type) { 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 (hit.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 (hit, button, shift); + WorldspaceWidget::handleInteractionPress (hit, type); } void CSVRender::PagedWorldspaceWidget::referenceableDataChanged (const QModelIndex& topLeft, @@ -437,6 +435,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) @@ -450,6 +469,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() @@ -722,3 +757,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 692000708..b963c6144 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.hpp +++ b/apps/opencs/view/render/pagedworldspacewidget.hpp @@ -74,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); @@ -138,7 +140,7 @@ namespace CSVRender virtual void addEditModeSelectorButtons (CSVWidget::SceneToolMode *tool); - virtual void handleMouseClick (const WorldspaceHitResult& hit, const std::string& button, bool shift); + virtual void handleInteractionPress (const WorldspaceHitResult& hit, InteractionType type); signals: @@ -152,6 +154,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/pathgridmode.cpp b/apps/opencs/view/render/pathgridmode.cpp index 87ec80556..228b2b5e7 100644 --- a/apps/opencs/view/render/pathgridmode.cpp +++ b/apps/opencs/view/render/pathgridmode.cpp @@ -35,10 +35,10 @@ namespace CSVRender { return QString( "Pathgrid editing" - "
          • Primary edit: Add node to scene
          • " - "
          • Secondary edit: Connect selected nodes to node
          • " - "
          • Primary drag: Move selected nodes
          • " - "
          • Secondary drag: Connect one node to another
          • " + "
            • 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"); } @@ -53,6 +53,16 @@ namespace CSVRender 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() && diff --git a/apps/opencs/view/render/pathgridmode.hpp b/apps/opencs/view/render/pathgridmode.hpp index 908eefa5b..e34208f8c 100644 --- a/apps/opencs/view/render/pathgridmode.hpp +++ b/apps/opencs/view/render/pathgridmode.hpp @@ -19,6 +19,8 @@ namespace CSVRender virtual void activate(CSVWidget::SceneToolbar* toolbar); + virtual void deactivate(CSVWidget::SceneToolbar* toolbar); + virtual void primaryEditPressed(const WorldspaceHitResult& hit); virtual void secondaryEditPressed(const WorldspaceHitResult& hit); diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp index b2b5b30d6..db0637a24 100644 --- a/apps/opencs/view/render/scenewidget.cpp +++ b/apps/opencs/view/render/scenewidget.cpp @@ -3,7 +3,6 @@ #include #include #include -#include #include #include @@ -19,6 +18,8 @@ #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" @@ -75,7 +76,7 @@ RenderWidget::RenderWidget(QWidget *parent, Qt::WindowFlags f) 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)); @@ -105,6 +106,15 @@ osg::Camera *RenderWidget::getCamera() 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); +} + // -------------------------------------------------- @@ -158,13 +168,13 @@ SceneWidget::SceneWidget(boost::shared_ptr resourceSys , mHasDefaultAmbient(false) , mPrevMouseX(0) , mPrevMouseY(0) - , mFreeCamControl(new FreeCameraController()) - , mOrbitCamControl(new OrbitCameraController()) - , mCurrentCamControl(mFreeCamControl.get()) , mCamPositionSet(false) { + mFreeCamControl = new FreeCameraController(this); + mOrbitCamControl = new OrbitCameraController(this); + mCurrentCamControl = mFreeCamControl; + mOrbitCamControl->setPickingMask(Mask_Reference | Mask_Terrain); - selectNavigationMode("free"); // we handle lighting manually mView->setLightingMode(osgViewer::View::NO_LIGHT); @@ -175,11 +185,7 @@ SceneWidget::SceneWidget(boost::shared_ptr resourceSys // Recieve mouse move event even if mouse button is not pressed setMouseTracking(true); - setFocusPolicy(Qt::StrongFocus); - - /// \todo make shortcut configurable - QShortcut *focusToolbar = new QShortcut (Qt::Key_T, this, 0, 0, Qt::WidgetWithChildrenShortcut); - connect (focusToolbar, SIGNAL (activated()), this, SIGNAL (focusToolbarRequest())); + setFocusPolicy(Qt::ClickFocus); connect (&CSMPrefs::State::get(), SIGNAL (settingChanged (const CSMPrefs::Setting *)), this, SLOT (settingChanged (const CSMPrefs::Setting *))); @@ -192,6 +198,13 @@ SceneWidget::SceneWidget(boost::shared_ptr resourceSys } 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() @@ -271,45 +284,17 @@ void SceneWidget::setDefaultAmbient (const osg::Vec4f& colour) setAmbient(mLighting->getAmbientColour(&mDefaultAmbient)); } -void SceneWidget::mousePressEvent (QMouseEvent *event) -{ - mMouseMode = mapButton(event); - - mPrevMouseX = event->x(); - mPrevMouseY = event->y(); -} - -void SceneWidget::mouseReleaseEvent (QMouseEvent *event) -{ - mMouseMode = ""; -} - void SceneWidget::mouseMoveEvent (QMouseEvent *event) { - mCurrentCamControl->handleMouseMoveEvent(mMouseMode, event->x() - mPrevMouseX, event->y() - mPrevMouseY); + mCurrentCamControl->handleMouseMoveEvent(event->x() - mPrevMouseX, event->y() - mPrevMouseY); mPrevMouseX = event->x(); mPrevMouseY = event->y(); } -void SceneWidget::focusOutEvent (QFocusEvent *event) -{ - mCurrentCamControl->resetInput(); -} - void SceneWidget::wheelEvent(QWheelEvent *event) { - mCurrentCamControl->handleMouseMoveEvent("t-navi", event->delta(), 0); -} - -void SceneWidget::keyPressEvent (QKeyEvent *event) -{ - mCurrentCamControl->handleKeyEvent(event, true); -} - -void SceneWidget::keyReleaseEvent (QKeyEvent *event) -{ - mCurrentCamControl->handleKeyEvent(event, false); + mCurrentCamControl->handleMouseScrollEvent(event->delta()); } void SceneWidget::update(double dt) @@ -373,10 +358,6 @@ void SceneWidget::settingChanged (const CSMPrefs::Setting *setting) { mOrbitCamControl->setOrbitSpeedMultiplier(setting->toDouble()); } - else - { - storeMappingSetting(setting); - } } void SceneWidget::selectNavigationMode (const std::string& mode) @@ -384,73 +365,23 @@ void SceneWidget::selectNavigationMode (const std::string& mode) if (mode=="1st") { mCurrentCamControl->setCamera(NULL); - mCurrentCamControl = mFreeCamControl.get(); - mCurrentCamControl->setCamera(getCamera()); + mCurrentCamControl = mFreeCamControl; + mFreeCamControl->setCamera(getCamera()); mFreeCamControl->fixUpAxis(CameraController::WorldUp); } else if (mode=="free") { mCurrentCamControl->setCamera(NULL); - mCurrentCamControl = mFreeCamControl.get(); - mCurrentCamControl->setCamera(getCamera()); + mCurrentCamControl = mFreeCamControl; + mFreeCamControl->setCamera(getCamera()); mFreeCamControl->unfixUpAxis(); } else if (mode=="orbit") { mCurrentCamControl->setCamera(NULL); - mCurrentCamControl = mOrbitCamControl.get(); - mCurrentCamControl->setCamera(getCamera()); + mCurrentCamControl = mOrbitCamControl; + mOrbitCamControl->setCamera(getCamera()); } } -bool SceneWidget::storeMappingSetting (const CSMPrefs::Setting *setting) -{ - if (setting->getParent()->getKey()!="3D Scene Input") - return false; - - static const char * const sMappingSettings[] = - { - "p-navi", "s-navi", - 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; -} - -std::string SceneWidget::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 ""; -} - } diff --git a/apps/opencs/view/render/scenewidget.hpp b/apps/opencs/view/render/scenewidget.hpp index 4df49543a..723d93c00 100644 --- a/apps/opencs/view/render/scenewidget.hpp +++ b/apps/opencs/view/render/scenewidget.hpp @@ -7,14 +7,15 @@ #include #include +#include +#include + #include #include "lightingday.hpp" #include "lightingnight.hpp" #include "lightingbright.hpp" -#include -#include namespace Resource { @@ -53,6 +54,7 @@ namespace CSVRender RenderWidget(QWidget* parent = 0, Qt::WindowFlags f = 0); virtual ~RenderWidget(); + /// Initiates a request to redraw the view void flagAsModified(); void setVisibilityMask(int mask); @@ -62,13 +64,16 @@ namespace CSVRender protected: osg::ref_ptr mView; - - osg::Group* mRootNode; + osg::ref_ptr mRootNode; QTimer mTimer; + + protected slots: + + 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 @@ -90,18 +95,8 @@ namespace CSVRender void setAmbient(const osg::Vec4f& ambient); - virtual void mousePressEvent (QMouseEvent *event); - virtual void mouseReleaseEvent (QMouseEvent *event); virtual void mouseMoveEvent (QMouseEvent *event); virtual void wheelEvent (QWheelEvent *event); - virtual void keyPressEvent (QKeyEvent *event); - virtual void keyReleaseEvent (QKeyEvent *event); - virtual void focusOutEvent (QFocusEvent *event); - - /// \return Is \a key a button mapping setting? (ignored otherwise) - virtual bool storeMappingSetting (const CSMPrefs::Setting *setting); - - std::string mapButton (QMouseEvent *event); boost::shared_ptr mResourceSystem; @@ -114,12 +109,10 @@ namespace CSVRender LightingBright mLightingBright; int mPrevMouseX, mPrevMouseY; - std::string mMouseMode; - std::auto_ptr mFreeCamControl; - std::auto_ptr mOrbitCamControl; - CameraController* mCurrentCamControl; - std::map, std::string> mButtonMapping; + FreeCameraController* mFreeCamControl; + OrbitCameraController* mOrbitCamControl; + CameraController* mCurrentCamControl; private: bool mCamPositionSet; diff --git a/apps/opencs/view/render/selectionmode.cpp b/apps/opencs/view/render/selectionmode.cpp index 82a3c49e4..cf0967e47 100644 --- a/apps/opencs/view/render/selectionmode.cpp +++ b/apps/opencs/view/render/selectionmode.cpp @@ -15,22 +15,28 @@ namespace CSVRender { 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
            • " + "
              • 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 primary/secondary edit not starting on an instance will have the same effect
              • " + "
              • 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 primary (make instances the selection) or secondary (invert selection state) select button from one corner of the selection cube to the opposite 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 primary/secondary edit not starting on an instance will have the same effect
                • " + "
                • 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 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
                • " + "
                  • 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"); diff --git a/apps/opencs/view/render/worldspacewidget.cpp b/apps/opencs/view/render/worldspacewidget.cpp index 782dc5354..dd2ec1bdd 100644 --- a/apps/opencs/view/render/worldspacewidget.cpp +++ b/apps/opencs/view/render/worldspacewidget.cpp @@ -16,6 +16,8 @@ #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" @@ -31,10 +33,24 @@ #include "cameracontroller.hpp" CSVRender::WorldspaceWidget::WorldspaceWidget (CSMDoc::Document& document, QWidget* parent) -: SceneWidget (document.getData().getResourceSystem(), parent, 0, false), 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); @@ -80,6 +96,24 @@ CSVRender::WorldspaceWidget::WorldspaceWidget (CSMDoc::Document& document, QWidg CSMPrefs::get()["3D Scene Input"].update(); CSMPrefs::get()["Tooltips"].update(); + + // 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 () @@ -132,30 +166,32 @@ 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
                      • " + "
                      • 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 WASD or by moving the mouse while holding the left button
                      • " + "
                      • 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
                      • " - "
                      • 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 {scene-speed-modifier} to speed up movement
                      • " "
                      ", tool), "orbit"); @@ -409,7 +445,7 @@ void CSVRender::WorldspaceWidget::abortDrag() editMode.dragAborted(); mDragging = false; - mDragMode.clear(); + mDragMode = InteractionType_None; } } @@ -453,45 +489,6 @@ void CSVRender::WorldspaceWidget::dragMoveEvent(QDragMoveEvent *event) } } -bool CSVRender::WorldspaceWidget::storeMappingSetting (const CSMPrefs::Setting *setting) -{ - static const char * const sMappingSettings[] = - { - "p-edit", "s-edit", - "p-select", "s-select", - 0 - }; - - if (setting->getParent()->getKey()=="3D Scene Input") - { - 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 SceneWidget::storeMappingSetting(setting); -} - void CSVRender::WorldspaceWidget::dropEvent (QDropEvent* event) { const CSMWorld::TableMimeData* mime = dynamic_cast (event->mimeData()); @@ -567,7 +564,7 @@ void CSVRender::WorldspaceWidget::editModeChanged (const std::string& id) { dynamic_cast (*mEditMode->getCurrent()).setEditLock (mLocked); mDragging = false; - mDragMode.clear(); + mDragMode = InteractionType_None; } void CSVRender::WorldspaceWidget::showToolTip() @@ -608,24 +605,24 @@ 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 (event->pos(), diffX, diffY, factor); } - else if (mDragMode=="p-edit" || mDragMode=="s-edit" || mDragMode=="p-select" || mDragMode=="s-select") + else if (mDragMode != InteractionType_None) { EditMode& editMode = dynamic_cast (*mEditMode->getCurrent()); - if (mDragMode=="p-edit") + if (mDragMode == InteractionType_PrimaryEdit) mDragging = editMode.primaryEditStartDrag (event->pos()); - else if (mDragMode=="s-edit") + else if (mDragMode == InteractionType_SecondaryEdit) mDragging = editMode.secondaryEditStartDrag (event->pos()); - else if (mDragMode=="p-select") + else if (mDragMode == InteractionType_PrimarySelect) mDragging = editMode.primarySelectStartDrag (event->pos()); - else if (mDragMode=="s-select") + else if (mDragMode == InteractionType_SecondarySelect) mDragging = editMode.secondarySelectStartDrag (event->pos()); if (mDragging) @@ -656,53 +653,13 @@ void CSVRender::WorldspaceWidget::mouseMoveEvent (QMouseEvent *event) } } -void CSVRender::WorldspaceWidget::mousePressEvent (QMouseEvent *event) -{ - std::string button = mapButton (event); - - if (button=="p-edit" || button=="s-edit" || - button=="p-select" || button=="s-select") - { - if (!mDragging) - mDragMode = button; - } - else - SceneWidget::mousePressEvent(event); -} - -void CSVRender::WorldspaceWidget::mouseReleaseEvent (QMouseEvent *event) -{ - std::string button = mapButton (event); - mDragMode.clear(); - - if (button=="p-edit" || button=="s-edit" || - button=="p-select" || button=="s-select") - { - if (mDragging) - { - EditMode& editMode = dynamic_cast (*mEditMode->getCurrent()); - - editMode.dragCompleted(event->pos()); - mDragging = false; - } - else - { - WorldspaceHitResult hit = mousePick(event->pos(), getInteractionMask()); - - handleMouseClick (hit, button, event->modifiers() & Qt::ShiftModifier); - } - } - else - SceneWidget::mouseReleaseEvent(event); -} - void CSVRender::WorldspaceWidget::wheelEvent (QWheelEvent *event) { if (mDragging) { double factor = mDragWheelFactor; - if (event->modifiers() & Qt::ShiftModifier) + if (mSpeedMode) factor *= mDragShiftFactor; EditMode& editMode = dynamic_cast (*mEditMode->getCurrent()); @@ -713,27 +670,17 @@ void CSVRender::WorldspaceWidget::wheelEvent (QWheelEvent *event) SceneWidget::wheelEvent(event); } -void CSVRender::WorldspaceWidget::keyPressEvent (QKeyEvent *event) -{ - if(event->key() == Qt::Key_Escape) - { - abortDrag(); - } - else - SceneWidget::keyPressEvent(event); -} - -void CSVRender::WorldspaceWidget::handleMouseClick (const WorldspaceHitResult& hit, const std::string& button, bool shift) +void CSVRender::WorldspaceWidget::handleInteractionPress (const WorldspaceHitResult& hit, InteractionType type) { EditMode& editMode = dynamic_cast (*mEditMode->getCurrent()); - if (button=="p-edit") + if (type == InteractionType_PrimaryEdit) editMode.primaryEditPressed (hit); - else if (button=="s-edit") + else if (type == InteractionType_SecondaryEdit) editMode.secondaryEditPressed (hit); - else if (button=="p-select") + else if (type == InteractionType_PrimarySelect) editMode.primarySelectPressed (hit); - else if (button=="s-select") + else if (type == InteractionType_SecondarySelect) editMode.secondarySelectPressed (hit); } @@ -741,3 +688,53 @@ 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::primarySelect(bool activate) +{ + 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 = getEditMode(); + editMode->dragCompleted(mapFromGlobal(QCursor::pos())); + mDragging = false; + } + else + { + WorldspaceHitResult hit = mousePick(mapFromGlobal(QCursor::pos()), getInteractionMask()); + handleInteractionPress(hit, type); + } + } +} diff --git a/apps/opencs/view/render/worldspacewidget.hpp b/apps/opencs/view/render/worldspacewidget.hpp index 92d31eb9e..b30d7de8a 100644 --- a/apps/opencs/view/render/worldspacewidget.hpp +++ b/apps/opencs/view/render/worldspacewidget.hpp @@ -55,10 +55,11 @@ namespace CSVRender unsigned int mInteractionMask; CSVWidget::SceneToolMode *mEditMode; bool mLocked; - std::string mDragMode; + int mDragMode; bool mDragging; int mDragX; int mDragY; + bool mSpeedMode; double mDragFactor; double mDragWheelFactor; double mDragShiftFactor; @@ -85,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 (); @@ -171,12 +181,6 @@ namespace CSVRender /// Erase all overrides and restore the visual representation to its true state. virtual void reset (unsigned int elementMask) = 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(); - protected: /// Visual elements in a scene @@ -197,21 +201,16 @@ namespace CSVRender virtual void updateOverlay(); virtual void mouseMoveEvent (QMouseEvent *event); - virtual void mousePressEvent (QMouseEvent *event); - virtual void mouseReleaseEvent (QMouseEvent *event); virtual void wheelEvent (QWheelEvent *event); - virtual void keyPressEvent (QKeyEvent *event); - virtual void handleMouseClick (const WorldspaceHitResult& hit, const std::string& button, - bool shift); - - /// \return Is \a key a button mapping setting? (ignored otherwise) - virtual bool storeMappingSetting (const CSMPrefs::Setting *setting); + virtual void handleInteractionPress (const WorldspaceHitResult& hit, InteractionType type); virtual void settingChanged (const CSMPrefs::Setting *setting); EditMode *getEditMode(); + bool getSpeedMode(); + private: void dragEnterEvent(QDragEnterEvent *event); @@ -222,6 +221,16 @@ namespace CSVRender virtual std::string getStartupInstruction() = 0; + void handleInteraction(InteractionType type, bool activate); + + public slots: + + /// \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: virtual void referenceableDataChanged (const QModelIndex& topLeft, @@ -255,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..58feda7c9 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" @@ -171,14 +172,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 +193,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 c91890c69..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" @@ -133,6 +134,16 @@ void CSVWidget::SceneToolMode::setButton (const std::string& id) } } +bool CSVWidget::SceneToolMode::event(QEvent* event) +{ + if (event->type() == QEvent::ToolTip) + { + adjustToolTip(mCurrent); + } + + return SceneTool::event(event); +} + void CSVWidget::SceneToolMode::selected() { std::map::iterator iter = diff --git a/apps/opencs/view/widget/scenetoolmode.hpp b/apps/opencs/view/widget/scenetoolmode.hpp index 192b3ee78..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 { @@ -43,6 +44,10 @@ namespace CSVWidget void setButton (std::map::iterator iter); + protected: + + bool event(QEvent* event); + public: SceneToolMode (SceneToolbar *parent, const QString& toolTip); diff --git a/apps/opencs/view/world/nestedtable.cpp b/apps/opencs/view/world/nestedtable.cpp index d791377b8..02bd93920 100644 --- a/apps/opencs/view/world/nestedtable.cpp +++ b/apps/opencs/view/world/nestedtable.cpp @@ -5,6 +5,8 @@ #include #include +#include "../../model/prefs/shortcut.hpp" + #include "../../model/world/nestedtableproxymodel.hpp" #include "../../model/world/universalid.hpp" #include "../../model/world/commands.hpp" @@ -60,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 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); 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/openmw/main.cpp b/apps/openmw/main.cpp index f54905a2c..cfe7fe305 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -76,7 +77,7 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat desc.add_options() ("help", "print help message") ("version", "print version information and quit") - ("data", bpo::value()->default_value(Files::PathContainer(), "data") + ("data", bpo::value()->default_value(Files::EscapePathContainer(), "data") ->multitoken()->composing(), "set data directories (later directories have higher priority)") ("data-local", bpo::value()->default_value(""), @@ -193,7 +194,7 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat // directory settings engine.enableFSStrict(variables["fs-strict"].as()); - Files::PathContainer dataDirs(variables["data"].as()); + Files::PathContainer dataDirs(Files::EscapePath::toPathContainer(variables["data"].as())); std::string local(variables["data-local"].as().toStdString()); if (!local.empty()) diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index cb3d0d0a1..9e26cb0f9 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -395,15 +395,18 @@ namespace MWGui const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); if (info.enchant != "") { - enchant = store.get().find(info.enchant); - if (enchant->mData.mType == ESM::Enchantment::CastOnce) - text += "\n#{sItemCastOnce}"; - else if (enchant->mData.mType == ESM::Enchantment::WhenStrikes) - text += "\n#{sItemCastWhenStrikes}"; - else if (enchant->mData.mType == ESM::Enchantment::WhenUsed) - text += "\n#{sItemCastWhenUsed}"; - else if (enchant->mData.mType == ESM::Enchantment::ConstantEffect) - text += "\n#{sItemCastConstant}"; + enchant = store.get().search(info.enchant); + if (enchant) + { + if (enchant->mData.mType == ESM::Enchantment::CastOnce) + text += "\n#{sItemCastOnce}"; + else if (enchant->mData.mType == ESM::Enchantment::WhenStrikes) + text += "\n#{sItemCastWhenStrikes}"; + else if (enchant->mData.mType == ESM::Enchantment::WhenUsed) + text += "\n#{sItemCastWhenUsed}"; + else if (enchant->mData.mType == ESM::Enchantment::ConstantEffect) + text += "\n#{sItemCastConstant}"; + } } // this the maximum width of the tooltip before it starts word-wrapping @@ -472,9 +475,8 @@ namespace MWGui totalSize.width = std::max(totalSize.width, coord.width); } - if (info.enchant != "") + if (enchant) { - assert(enchant); MyGUI::Widget* enchantArea = mDynamicToolTipBox->createWidget("", MyGUI::IntCoord(padding.left, totalSize.height, 300-padding.left, 300-totalSize.height), MyGUI::Align::Stretch); diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index ddff61ac7..fa7542060 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1143,10 +1143,18 @@ namespace MWRender std::string enchantmentName = item.getClass().getEnchantment(item); if (enchantmentName.empty()) return result; - const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find(enchantmentName); + + const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().search(enchantmentName); + if (!enchantment) + return result; + assert (enchantment->mEffects.mList.size()); - const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find( + + const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get().search( enchantment->mEffects.mList.front().mEffectID); + if (!magicEffect) + return result; + result.x() = magicEffect->mData.mRed / 255.f; result.y() = magicEffect->mData.mGreen / 255.f; result.z() = magicEffect->mData.mBlue / 255.f; diff --git a/apps/openmw/mwworld/cellreflist.hpp b/apps/openmw/mwworld/cellreflist.hpp index 4d503dcc8..30be4a661 100644 --- a/apps/openmw/mwworld/cellreflist.hpp +++ b/apps/openmw/mwworld/cellreflist.hpp @@ -29,6 +29,18 @@ namespace MWWorld mList.push_back(item); return mList.back(); } + + /// Remove all references with the given refNum from this list. + void remove (const ESM::RefNum &refNum) + { + for (typename List::iterator it = mList.begin(); it != mList.end();) + { + if (*it == refNum) + mList.erase(it++); + else + ++it; + } + } }; } diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index f418f8bad..e5c9e779e 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -524,9 +524,11 @@ namespace MWWorld // List moved references, from separately tracked list. for (ESM::CellRefTracker::const_iterator it = mCell->mLeasedRefs.begin(); it != mCell->mLeasedRefs.end(); ++it) { - const ESM::CellRef &ref = *it; + const ESM::CellRef &ref = it->first; + bool deleted = it->second; - mIds.push_back(Misc::StringUtils::lowerCase(ref.mRefID)); + if (!deleted) + mIds.push_back(Misc::StringUtils::lowerCase(ref.mRefID)); } std::sort (mIds.begin(), mIds.end()); @@ -541,6 +543,8 @@ namespace MWWorld if (mCell->mContextList.empty()) return; // this is a dynamically generated cell -> skipping. + std::map refNumToID; // used to detect refID modifications + // Load references from all plugins that do something with this cell. for (size_t i = 0; i < mCell->mContextList.size(); i++) { @@ -564,7 +568,7 @@ namespace MWWorld continue; } - loadRef (ref, deleted); + loadRef (ref, deleted, refNumToID); } } catch (std::exception& e) @@ -576,9 +580,10 @@ namespace MWWorld // Load moved references, from separately tracked list. for (ESM::CellRefTracker::const_iterator it = mCell->mLeasedRefs.begin(); it != mCell->mLeasedRefs.end(); ++it) { - ESM::CellRef &ref = const_cast(*it); + ESM::CellRef &ref = const_cast(it->first); + bool deleted = it->second; - loadRef (ref, false); + loadRef (ref, deleted, refNumToID); } updateMergedRefs(); @@ -609,12 +614,47 @@ namespace MWWorld return Ptr(); } - void CellStore::loadRef (ESM::CellRef& ref, bool deleted) + void CellStore::loadRef (ESM::CellRef& ref, bool deleted, std::map& refNumToID) { Misc::StringUtils::lowerCaseInPlace (ref.mRefID); const MWWorld::ESMStore& store = mStore; + std::map::iterator it = refNumToID.find(ref.mRefNum); + if (it != refNumToID.end()) + { + if (it->second != ref.mRefID) + { + // refID was modified, make sure we don't end up with duplicated refs + switch (store.find(it->second)) + { + case ESM::REC_ACTI: mActivators.remove(ref.mRefNum); break; + case ESM::REC_ALCH: mPotions.remove(ref.mRefNum); break; + case ESM::REC_APPA: mAppas.remove(ref.mRefNum); break; + case ESM::REC_ARMO: mArmors.remove(ref.mRefNum); break; + case ESM::REC_BOOK: mBooks.remove(ref.mRefNum); break; + case ESM::REC_CLOT: mClothes.remove(ref.mRefNum); break; + case ESM::REC_CONT: mContainers.remove(ref.mRefNum); break; + case ESM::REC_CREA: mCreatures.remove(ref.mRefNum); break; + case ESM::REC_DOOR: mDoors.remove(ref.mRefNum); break; + case ESM::REC_INGR: mIngreds.remove(ref.mRefNum); break; + case ESM::REC_LEVC: mCreatureLists.remove(ref.mRefNum); break; + case ESM::REC_LEVI: mItemLists.remove(ref.mRefNum); break; + case ESM::REC_LIGH: mLights.remove(ref.mRefNum); break; + case ESM::REC_LOCK: mLockpicks.remove(ref.mRefNum); break; + case ESM::REC_MISC: mMiscItems.remove(ref.mRefNum); break; + case ESM::REC_NPC_: mNpcs.remove(ref.mRefNum); break; + case ESM::REC_PROB: mProbes.remove(ref.mRefNum); break; + case ESM::REC_REPA: mRepairs.remove(ref.mRefNum); break; + case ESM::REC_STAT: mStatics.remove(ref.mRefNum); break; + case ESM::REC_WEAP: mWeapons.remove(ref.mRefNum); break; + case ESM::REC_BODY: mBodyParts.remove(ref.mRefNum); break; + default: + break; + } + } + } + switch (store.find (ref.mRefID)) { case ESM::REC_ACTI: mActivators.load(ref, deleted, store); break; @@ -639,12 +679,15 @@ namespace MWWorld case ESM::REC_WEAP: mWeapons.load(ref, deleted, store); break; case ESM::REC_BODY: mBodyParts.load(ref, deleted, store); break; - case 0: std::cerr << "Cell reference '" + ref.mRefID + "' not found!\n"; break; + case 0: std::cerr << "Cell reference '" + ref.mRefID + "' not found!\n"; return; default: std::cerr << "WARNING: Ignoring reference '" << ref.mRefID << "' of unhandled type\n"; + return; } + + refNumToID[ref.mRefNum] = ref.mRefID; } void CellStore::loadState (const ESM::CellState& state) diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index 36cc7f5eb..1aee13132 100644 --- a/apps/openmw/mwworld/cellstore.hpp +++ b/apps/openmw/mwworld/cellstore.hpp @@ -385,7 +385,7 @@ namespace MWWorld void loadRefs(); - void loadRef (ESM::CellRef& ref, bool deleted); + void loadRef (ESM::CellRef& ref, bool deleted, std::map& refNumToID); ///< Make case-adjustments to \a ref and insert it into the respective container. /// /// Invalid \a ref objects are silently dropped. diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index 4cddbcdfb..c13f10d9a 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -513,19 +513,16 @@ namespace MWWorld bool deleted = false; cell->getNextRef(esm, ref, deleted); - if (!deleted) - { - // Add data required to make reference appear in the correct cell. - // We should not need to test for duplicates, as this part of the code is pre-cell merge. - cell->mMovedRefs.push_back(cMRef); - - // But there may be duplicates here! - ESM::CellRefTracker::iterator iter = std::find(cellAlt->mLeasedRefs.begin(), cellAlt->mLeasedRefs.end(), ref.mRefNum); - if (iter == cellAlt->mLeasedRefs.end()) - cellAlt->mLeasedRefs.push_back(ref); - else - *iter = ref; - } + // Add data required to make reference appear in the correct cell. + // We should not need to test for duplicates, as this part of the code is pre-cell merge. + cell->mMovedRefs.push_back(cMRef); + + // But there may be duplicates here! + ESM::CellRefTracker::iterator iter = std::find_if(cellAlt->mLeasedRefs.begin(), cellAlt->mLeasedRefs.end(), ESM::CellRefTrackerPredicate(ref.mRefNum)); + if (iter == cellAlt->mLeasedRefs.end()) + cellAlt->mLeasedRefs.push_back(std::make_pair(ref, deleted)); + else + *iter = std::make_pair(ref, deleted); } } const ESM::Cell *Store::search(const std::string &id) const @@ -678,14 +675,17 @@ namespace MWWorld for (ESM::MovedCellRefTracker::const_iterator it = cell.mMovedRefs.begin(); it != cell.mMovedRefs.end(); ++it) { // remove reference from current leased ref tracker and add it to new cell ESM::MovedCellRefTracker::iterator itold = std::find(oldcell->mMovedRefs.begin(), oldcell->mMovedRefs.end(), it->mRefNum); - if (itold != oldcell->mMovedRefs.end()) { - ESM::MovedCellRef target0 = *itold; - ESM::Cell *wipecell = const_cast(search(target0.mTarget[0], target0.mTarget[1])); - ESM::CellRefTracker::iterator it_lease = std::find(wipecell->mLeasedRefs.begin(), wipecell->mLeasedRefs.end(), it->mRefNum); - if (it_lease != wipecell->mLeasedRefs.end()) - wipecell->mLeasedRefs.erase(it_lease); - else - std::cerr << "can't find " << it->mRefNum.mIndex << " " << it->mRefNum.mContentFile << " in leasedRefs " << std::endl; + if (itold != oldcell->mMovedRefs.end()) + { + if (it->mTarget[0] != itold->mTarget[0] || it->mTarget[1] != itold->mTarget[1]) + { + ESM::Cell *wipecell = const_cast(search(itold->mTarget[0], itold->mTarget[1])); + ESM::CellRefTracker::iterator it_lease = std::find_if(wipecell->mLeasedRefs.begin(), wipecell->mLeasedRefs.end(), ESM::CellRefTrackerPredicate(it->mRefNum)); + if (it_lease != wipecell->mLeasedRefs.end()) + wipecell->mLeasedRefs.erase(it_lease); + else + std::cerr << "can't find " << it->mRefNum.mIndex << " " << it->mRefNum.mContentFile << " in leasedRefs " << std::endl; + } *itold = *it; } else diff --git a/cmake/FindSphinx.cmake b/cmake/FindSphinx.cmake new file mode 100644 index 000000000..4607f7806 --- /dev/null +++ b/cmake/FindSphinx.cmake @@ -0,0 +1,145 @@ +# - This module looks for Sphinx +# Find the Sphinx documentation generator +# +# This modules defines +# SPHINX_EXECUTABLE +# SPHINX_FOUND + +find_program(SPHINX_EXECUTABLE + NAMES sphinx-build + PATHS + /usr/bin + /usr/local/bin + /opt/local/bin + DOC "Sphinx documentation generator" +) + +if( NOT SPHINX_EXECUTABLE ) + set(_Python_VERSIONS + 2.7 2.6 2.5 2.4 2.3 2.2 2.1 2.0 1.6 1.5 + ) + + foreach( _version ${_Python_VERSIONS} ) + set( _sphinx_NAMES sphinx-build-${_version} ) + + find_program( SPHINX_EXECUTABLE + NAMES ${_sphinx_NAMES} + PATHS + /usr/bin + /usr/local/bin + /opt/loca/bin + DOC "Sphinx documentation generator" + ) + endforeach() +endif() + +include(FindPackageHandleStandardArgs) + +find_package_handle_standard_args(Sphinx DEFAULT_MSG + SPHINX_EXECUTABLE +) + + +option( SPHINX_HTML_OUTPUT "Build a single HTML with the whole content." ON ) +option( SPHINX_DIRHTML_OUTPUT "Build HTML pages, but with a single directory per document." OFF ) +option( SPHINX_HTMLHELP_OUTPUT "Build HTML pages with additional information for building a documentation collection in htmlhelp." OFF ) +option( SPHINX_QTHELP_OUTPUT "Build HTML pages with additional information for building a documentation collection in qthelp." OFF ) +option( SPHINX_DEVHELP_OUTPUT "Build HTML pages with additional information for building a documentation collection in devhelp." OFF ) +option( SPHINX_EPUB_OUTPUT "Build HTML pages with additional information for building a documentation collection in epub." OFF ) +option( SPHINX_LATEX_OUTPUT "Build LaTeX sources that can be compiled to a PDF document using pdflatex." OFF ) +option( SPHINX_MAN_OUTPUT "Build manual pages in groff format for UNIX systems." OFF ) +option( SPHINX_TEXT_OUTPUT "Build plain text files." OFF ) + + +mark_as_advanced( + SPHINX_EXECUTABLE + SPHINX_HTML_OUTPUT + SPHINX_DIRHTML_OUTPUT + SPHINX_HTMLHELP_OUTPUT + SPHINX_QTHELP_OUTPUT + SPHINX_DEVHELP_OUTPUT + SPHINX_EPUB_OUTPUT + SPHINX_LATEX_OUTPUT + SPHINX_MAN_OUTPUT + SPHINX_TEXT_OUTPUT +) + +function( Sphinx_add_target target_name builder conf source destination ) + add_custom_target( ${target_name} ALL + COMMAND ${SPHINX_EXECUTABLE} -b ${builder} + -c ${conf} + ${source} + ${destination} + COMMENT "Generating sphinx documentation: ${builder}" + ) + + set_property( + DIRECTORY APPEND PROPERTY + ADDITIONAL_MAKE_CLEAN_FILES + ${destination} + ) +endfunction() + +# Target dependencies can be optionally listed at the end. +function( Sphinx_add_targets target_base_name conf source base_destination ) + + set( _dependencies ) + + foreach( arg IN LISTS ARGN ) + set( _dependencies ${_dependencies} ${arg} ) + endforeach() + + if( ${SPHINX_HTML_OUTPUT} ) + Sphinx_add_target( ${target_base_name}_html html ${conf} ${source} ${base_destination}/html ) + + add_dependencies( ${target_base_name}_html ${_dependencies} ) + endif() + + if( ${SPHINX_DIRHTML_OUTPUT} ) + Sphinx_add_target( ${target_base_name}_dirhtml dirhtml ${conf} ${source} ${base_destination}/dirhtml ) + + add_dependencies( ${target_base_name}_dirhtml ${_dependencies} ) + endif() + + if( ${SPHINX_QTHELP_OUTPUT} ) + Sphinx_add_target( ${target_base_name}_qthelp qthelp ${conf} ${source} ${base_destination}/qthelp ) + + add_dependencies( ${target_base_name}_qthelp ${_dependencies} ) + endif() + + if( ${SPHINX_DEVHELP_OUTPUT} ) + Sphinx_add_target( ${target_base_name}_devhelp devhelp ${conf} ${source} ${base_destination}/devhelp ) + + add_dependencies( ${target_base_name}_devhelp ${_dependencies} ) + endif() + + if( ${SPHINX_EPUB_OUTPUT} ) + Sphinx_add_target( ${target_base_name}_epub epub ${conf} ${source} ${base_destination}/epub ) + + add_dependencies( ${target_base_name}_epub ${_dependencies} ) + endif() + + if( ${SPHINX_LATEX_OUTPUT} ) + Sphinx_add_target( ${target_base_name}_latex latex ${conf} ${source} ${base_destination}/latex ) + + add_dependencies( ${target_base_name}_latex ${_dependencies} ) + endif() + + if( ${SPHINX_MAN_OUTPUT} ) + Sphinx_add_target( ${target_base_name}_man man ${conf} ${source} ${base_destination}/man ) + + add_dependencies( ${target_base_name}_man ${_dependencies} ) + endif() + + if( ${SPHINX_TEXT_OUTPUT} ) + Sphinx_add_target( ${target_base_name}_text text ${conf} ${source} ${base_destination}/text ) + + add_dependencies( ${target_base_name}_text ${_dependencies} ) + endif() + + if( ${BUILD_TESTING} ) + sphinx_add_target( ${target_base_name}_linkcheck linkcheck ${conf} ${source} ${base_destination}/linkcheck ) + + add_dependencies( ${target_base_name}_linkcheck ${_dependencies} ) + endif() +endfunction() diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index bb27457fe..4cffd5044 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -95,7 +95,7 @@ IF(NOT WIN32 AND NOT APPLE) add_definitions(-DGLOBAL_CONFIG_PATH="${GLOBAL_CONFIG_PATH}") ENDIF() add_component_dir (files - linuxpath androidpath windowspath macospath fixedpath multidircollection collections configurationmanager + linuxpath androidpath windowspath macospath fixedpath multidircollection collections configurationmanager escape lowlevelfile constrainedfilestream memorystream ) diff --git a/components/esm/loadcell.hpp b/components/esm/loadcell.hpp index 549ed7309..249d812b1 100644 --- a/components/esm/loadcell.hpp +++ b/components/esm/loadcell.hpp @@ -43,7 +43,15 @@ bool operator==(const MovedCellRef& ref, const RefNum& refNum); bool operator==(const CellRef& ref, const RefNum& refNum); typedef std::list MovedCellRefTracker; -typedef std::list CellRefTracker; +typedef std::list > CellRefTracker; + +struct CellRefTrackerPredicate +{ + RefNum mRefNum; + + CellRefTrackerPredicate(const RefNum& refNum) : mRefNum(refNum) {} + bool operator() (const std::pair& refdelPair) { return refdelPair.first == mRefNum; } +}; /* Cells hold data about objects, creatures, statics (rocks, walls, buildings) and landscape (for exterior cells). Cells frequently diff --git a/components/fallback/validate.hpp b/components/fallback/validate.hpp index d82ef5770..a1d3f9601 100644 --- a/components/fallback/validate.hpp +++ b/components/fallback/validate.hpp @@ -3,7 +3,7 @@ #include -#include +#include // Parses and validates a fallback map from boost program_options. // Note: for boost to pick up the validate function, you need to pull in the namespace e.g. @@ -47,25 +47,4 @@ namespace Fallback } } -namespace Files { - void validate(boost::any &v, const std::vector &tokens, Files::EscapeHashString * eHS, int a) - { - boost::program_options::validators::check_first_occurrence(v); - - if (v.empty()) - v = boost::any(EscapeHashString(boost::program_options::validators::get_single_string(tokens))); - } - - void validate(boost::any &v, const std::vector &tokens, EscapeStringVector *, int) - { - if (v.empty()) - v = boost::any(EscapeStringVector()); - - EscapeStringVector * eSV = boost::any_cast(&v); - - for (std::vector::const_iterator it = tokens.begin(); it != tokens.end(); ++it) - eSV->mVector.push_back(EscapeHashString(*it)); - } -} - #endif diff --git a/components/files/configurationmanager.cpp b/components/files/configurationmanager.cpp index 3a7f57949..b53e3834a 100644 --- a/components/files/configurationmanager.cpp +++ b/components/files/configurationmanager.cpp @@ -5,10 +5,13 @@ #include #include +#include + #include #include #include #include +#include /** * \namespace Files @@ -164,152 +167,6 @@ bool ConfigurationManager::loadConfig(const boost::filesystem::path& path, return false; } -const int escape_hash_filter::sEscape = '@'; -const int escape_hash_filter::sEscapeIdentifier = 'a'; -const int escape_hash_filter::sHashIdentifier = 'h'; - -escape_hash_filter::escape_hash_filter() : mNext(), mSeenNonWhitespace(false), mFinishLine(false) -{ -} - -escape_hash_filter::~escape_hash_filter() -{ -} - -template -int escape_hash_filter::get(Source & src) -{ - if (mNext.empty()) - { - int character = boost::iostreams::get(src); - bool record = true; - if (character == boost::iostreams::WOULD_BLOCK) - { - mNext.push(character); - record = false; - } - else if (character == EOF) - { - mSeenNonWhitespace = false; - mFinishLine = false; - mNext.push(character); - } - else if (character == '\n') - { - mSeenNonWhitespace = false; - mFinishLine = false; - mNext.push(character); - } - else if (mFinishLine) - { - mNext.push(character); - } - else if (character == '#') - { - if (mSeenNonWhitespace) - { - mNext.push(sEscape); - mNext.push(sHashIdentifier); - } - else - { - //it's fine being interpreted by Boost as a comment, and so is anything afterwards - mNext.push(character); - mFinishLine = true; - } - } - else if (mPrevious == sEscape) - { - mNext.push(sEscape); - mNext.push(sEscapeIdentifier); - } - else - { - mNext.push(character); - } - if (!mSeenNonWhitespace && !isspace(character)) - mSeenNonWhitespace = true; - if (record) - mPrevious = character; - } - int retval = mNext.front(); - mNext.pop(); - return retval; -} - -std::string EscapeHashString::processString(const std::string & str) -{ - std::string temp = boost::replace_all_copy(str, std::string() + (char)escape_hash_filter::sEscape + (char)escape_hash_filter::sHashIdentifier, "#"); - boost::replace_all(temp, std::string() + (char)escape_hash_filter::sEscape + (char)escape_hash_filter::sEscapeIdentifier, std::string((char) escape_hash_filter::sEscape, 1)); - return temp; -} - -EscapeHashString::EscapeHashString() : mData() -{ -} - -EscapeHashString::EscapeHashString(const std::string & str) : mData(EscapeHashString::processString(str)) -{ -} - -EscapeHashString::EscapeHashString(const std::string & str, size_t pos, size_t len) : mData(EscapeHashString::processString(str), pos, len) -{ -} - -EscapeHashString::EscapeHashString(const char * s) : mData(EscapeHashString::processString(std::string(s))) -{ -} - -EscapeHashString::EscapeHashString(const char * s, size_t n) : mData(EscapeHashString::processString(std::string(s)), 0, n) -{ -} - -EscapeHashString::EscapeHashString(size_t n, char c) : mData(n, c) -{ -} - -template -EscapeHashString::EscapeHashString(InputIterator first, InputIterator last) : mData(EscapeHashString::processString(std::string(first, last))) -{ -} - -std::string EscapeHashString::toStdString() const -{ - return std::string(mData); -} - -std::istream & operator>> (std::istream & is, EscapeHashString & eHS) -{ - std::string temp; - is >> temp; - eHS = EscapeHashString(temp); - return is; -} - -std::ostream & operator<< (std::ostream & os, const EscapeHashString & eHS) -{ - os << eHS.mData; - return os; -} - -EscapeStringVector::EscapeStringVector() : mVector() -{ -} - -EscapeStringVector::~EscapeStringVector() -{ -} - -std::vector EscapeStringVector::toStdStringVector() const -{ - std::vector temp = std::vector(); - for (std::vector::const_iterator it = mVector.begin(); it != mVector.end(); ++it) - { - temp.push_back(it->toStdString()); - } - return temp; -} - const boost::filesystem::path& ConfigurationManager::getGlobalPath() const { return mFixedPath.getGlobalConfigPath(); diff --git a/components/files/configurationmanager.hpp b/components/files/configurationmanager.hpp index 3ce995983..df131e671 100644 --- a/components/files/configurationmanager.hpp +++ b/components/files/configurationmanager.hpp @@ -2,10 +2,8 @@ #define COMPONENTS_FILES_CONFIGURATIONMANAGER_HPP #include -#include #include -#include #include #include @@ -64,66 +62,6 @@ struct ConfigurationManager bool mSilent; }; - - -/** - * \struct escape_hash_filter - */ -struct escape_hash_filter : public boost::iostreams::input_filter -{ - static const int sEscape; - static const int sHashIdentifier; - static const int sEscapeIdentifier; - - escape_hash_filter(); - virtual ~escape_hash_filter(); - - template int get(Source & src); - - private: - std::queue mNext; - int mPrevious; - - bool mSeenNonWhitespace; - bool mFinishLine; -}; - -/** - * \class EscapeHashString - */ -class EscapeHashString -{ - private: - std::string mData; - public: - static std::string processString(const std::string & str); - - EscapeHashString(); - EscapeHashString(const std::string & str); - EscapeHashString(const std::string & str, size_t pos, size_t len = std::string::npos); - EscapeHashString(const char * s); - EscapeHashString(const char * s, size_t n); - EscapeHashString(size_t n, char c); - template - EscapeHashString(InputIterator first, InputIterator last); - - std::string toStdString() const; - - friend std::ostream & operator<< (std::ostream & os, const EscapeHashString & eHS); -}; - -std::istream & operator>> (std::istream & is, EscapeHashString & eHS); - -struct EscapeStringVector -{ - std::vector mVector; - - EscapeStringVector(); - virtual ~EscapeStringVector(); - - std::vector toStdStringVector() const; -}; - } /* namespace Cfg */ #endif /* COMPONENTS_FILES_CONFIGURATIONMANAGER_HPP */ diff --git a/components/files/escape.cpp b/components/files/escape.cpp new file mode 100644 index 000000000..9a795b95f --- /dev/null +++ b/components/files/escape.cpp @@ -0,0 +1,140 @@ +#include "escape.hpp" + +#include +#include + +namespace Files +{ + const int escape_hash_filter::sEscape = '@'; + const int escape_hash_filter::sEscapeIdentifier = 'a'; + const int escape_hash_filter::sHashIdentifier = 'h'; + + escape_hash_filter::escape_hash_filter() : mNext(), mSeenNonWhitespace(false), mFinishLine(false) + { + } + + escape_hash_filter::~escape_hash_filter() + { + } + + unescape_hash_filter::unescape_hash_filter() : expectingIdentifier(false) + { + } + + unescape_hash_filter::~unescape_hash_filter() + { + } + + std::string EscapeHashString::processString(const std::string & str) + { + std::string temp = boost::replace_all_copy(str, std::string() + (char)escape_hash_filter::sEscape + (char)escape_hash_filter::sHashIdentifier, "#"); + boost::replace_all(temp, std::string() + (char)escape_hash_filter::sEscape + (char)escape_hash_filter::sEscapeIdentifier, std::string((char)escape_hash_filter::sEscape, 1)); + return temp; + } + + EscapeHashString::EscapeHashString() : mData() + { + } + + EscapeHashString::EscapeHashString(const std::string & str) : mData(EscapeHashString::processString(str)) + { + } + + EscapeHashString::EscapeHashString(const std::string & str, size_t pos, size_t len) : mData(EscapeHashString::processString(str), pos, len) + { + } + + EscapeHashString::EscapeHashString(const char * s) : mData(EscapeHashString::processString(std::string(s))) + { + } + + EscapeHashString::EscapeHashString(const char * s, size_t n) : mData(EscapeHashString::processString(std::string(s)), 0, n) + { + } + + EscapeHashString::EscapeHashString(size_t n, char c) : mData(n, c) + { + } + + template + EscapeHashString::EscapeHashString(InputIterator first, InputIterator last) : mData(EscapeHashString::processString(std::string(first, last))) + { + } + + std::string EscapeHashString::toStdString() const + { + return std::string(mData); + } + + std::istream & operator>> (std::istream & is, EscapeHashString & eHS) + { + std::string temp; + is >> temp; + eHS = EscapeHashString(temp); + return is; + } + + std::ostream & operator<< (std::ostream & os, const EscapeHashString & eHS) + { + os << eHS.mData; + return os; + } + + EscapeStringVector::EscapeStringVector() : mVector() + { + } + + EscapeStringVector::~EscapeStringVector() + { + } + + std::vector EscapeStringVector::toStdStringVector() const + { + std::vector temp = std::vector(); + for (std::vector::const_iterator it = mVector.begin(); it != mVector.end(); ++it) + { + temp.push_back(it->toStdString()); + } + return temp; + } + + // boost program options validation + + void validate(boost::any &v, const std::vector &tokens, Files::EscapeHashString * eHS, int a) + { + boost::program_options::validators::check_first_occurrence(v); + + if (v.empty()) + v = boost::any(EscapeHashString(boost::program_options::validators::get_single_string(tokens))); + } + + void validate(boost::any &v, const std::vector &tokens, EscapeStringVector *, int) + { + if (v.empty()) + v = boost::any(EscapeStringVector()); + + EscapeStringVector * eSV = boost::any_cast(&v); + + for (std::vector::const_iterator it = tokens.begin(); it != tokens.end(); ++it) + eSV->mVector.push_back(EscapeHashString(*it)); + } + + PathContainer EscapePath::toPathContainer(const EscapePathContainer & escapePathContainer) + { + PathContainer temp; + for (EscapePathContainer::const_iterator it = escapePathContainer.begin(); it != escapePathContainer.end(); ++it) + temp.push_back(it->mPath); + return temp; + } + + std::istream & operator>> (std::istream & istream, EscapePath & escapePath) + { + boost::iostreams::filtering_istream filteredStream; + filteredStream.push(unescape_hash_filter()); + filteredStream.push(istream); + + filteredStream >> escapePath.mPath; + + return istream; + } +} \ No newline at end of file diff --git a/components/files/escape.hpp b/components/files/escape.hpp new file mode 100644 index 000000000..3b016a12d --- /dev/null +++ b/components/files/escape.hpp @@ -0,0 +1,196 @@ +#ifndef COMPONENTS_FILES_ESCAPE_HPP +#define COMPONENTS_FILES_ESCAPE_HPP + +#include + +#include + +#include +#include +#include + +/** + * \namespace Files + */ +namespace Files +{ + /** + * \struct escape_hash_filter + */ + struct escape_hash_filter : public boost::iostreams::input_filter + { + static const int sEscape; + static const int sHashIdentifier; + static const int sEscapeIdentifier; + + escape_hash_filter(); + virtual ~escape_hash_filter(); + + template int get(Source & src); + + private: + std::queue mNext; + int mPrevious; + + bool mSeenNonWhitespace; + bool mFinishLine; + }; + + template + int escape_hash_filter::get(Source & src) + { + if (mNext.empty()) + { + int character = boost::iostreams::get(src); + bool record = true; + if (character == boost::iostreams::WOULD_BLOCK) + { + mNext.push(character); + record = false; + } + else if (character == EOF) + { + mSeenNonWhitespace = false; + mFinishLine = false; + mNext.push(character); + } + else if (character == '\n') + { + mSeenNonWhitespace = false; + mFinishLine = false; + mNext.push(character); + } + else if (mFinishLine) + { + mNext.push(character); + } + else if (character == '#') + { + if (mSeenNonWhitespace) + { + mNext.push(sEscape); + mNext.push(sHashIdentifier); + } + else + { + //it's fine being interpreted by Boost as a comment, and so is anything afterwards + mNext.push(character); + mFinishLine = true; + } + } + else if (mPrevious == sEscape) + { + mNext.push(sEscape); + mNext.push(sEscapeIdentifier); + } + else + { + mNext.push(character); + } + if (!mSeenNonWhitespace && !isspace(character)) + mSeenNonWhitespace = true; + if (record) + mPrevious = character; + } + int retval = mNext.front(); + mNext.pop(); + return retval; + } + + struct unescape_hash_filter : public boost::iostreams::input_filter + { + unescape_hash_filter(); + virtual ~unescape_hash_filter(); + + template int get(Source & src); + + private: + bool expectingIdentifier; + }; + + template + int unescape_hash_filter::get(Source & src) + { + int character; + if (!expectingIdentifier) + character = boost::iostreams::get(src); + else + { + character = escape_hash_filter::sEscape; + expectingIdentifier = false; + } + if (character == escape_hash_filter::sEscape) + { + int nextChar = boost::iostreams::get(src); + int intended; + if (nextChar == escape_hash_filter::sEscapeIdentifier) + intended = escape_hash_filter::sEscape; + else if (nextChar == escape_hash_filter::sHashIdentifier) + intended = '#'; + else if (nextChar == boost::iostreams::WOULD_BLOCK) + { + expectingIdentifier = true; + intended = nextChar; + } + else + intended = '?'; + return intended; + } + else + return character; + } + + /** + * \class EscapeHashString + */ + class EscapeHashString + { + private: + std::string mData; + public: + static std::string processString(const std::string & str); + + EscapeHashString(); + EscapeHashString(const std::string & str); + EscapeHashString(const std::string & str, size_t pos, size_t len = std::string::npos); + EscapeHashString(const char * s); + EscapeHashString(const char * s, size_t n); + EscapeHashString(size_t n, char c); + template + EscapeHashString(InputIterator first, InputIterator last); + + std::string toStdString() const; + + friend std::ostream & operator<< (std::ostream & os, const EscapeHashString & eHS); + }; + + std::istream & operator>> (std::istream & is, EscapeHashString & eHS); + + struct EscapeStringVector + { + std::vector mVector; + + EscapeStringVector(); + virtual ~EscapeStringVector(); + + std::vector toStdStringVector() const; + }; + + //boost program options validation + + void validate(boost::any &v, const std::vector &tokens, Files::EscapeHashString * eHS, int a); + + void validate(boost::any &v, const std::vector &tokens, EscapeStringVector *, int); + + struct EscapePath + { + boost::filesystem::path mPath; + + static PathContainer toPathContainer(const std::vector & escapePathContainer); + }; + + typedef std::vector EscapePathContainer; + + std::istream & operator>> (std::istream & istream, EscapePath & escapePath); +} /* namespace Files */ +#endif /* COMPONENTS_FILES_ESCAPE_HPP */ \ No newline at end of file diff --git a/docs/cs-manual/Makefile b/docs/cs-manual/Makefile deleted file mode 100644 index 9d62dc5ab..000000000 --- a/docs/cs-manual/Makefile +++ /dev/null @@ -1,216 +0,0 @@ -# Makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -PAPER = -BUILDDIR = build - -# User-friendly check for sphinx-build -ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) -$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) -endif - -# Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source -# the i18n builder cannot share the environment and doctrees with the others -I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source - -.PHONY: help -help: - @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " singlehtml to make a single large HTML file" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " applehelp to make an Apple Help Book" - @echo " devhelp to make HTML files and a Devhelp project" - @echo " epub to make an epub" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " latexpdf to make LaTeX files and run them through pdflatex" - @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" - @echo " text to make text files" - @echo " man to make manual pages" - @echo " texinfo to make Texinfo files" - @echo " info to make Texinfo files and run them through makeinfo" - @echo " gettext to make PO message catalogs" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " xml to make Docutils-native XML files" - @echo " pseudoxml to make pseudoxml-XML files for display purposes" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" - @echo " coverage to run coverage check of the documentation (if enabled)" - -.PHONY: clean -clean: - rm -rf $(BUILDDIR)/* - -.PHONY: html -html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." - -.PHONY: dirhtml -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." - -.PHONY: singlehtml -singlehtml: - $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml - @echo - @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." - -.PHONY: pickle -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -.PHONY: json -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json - @echo - @echo "Build finished; now you can process the JSON files." - -.PHONY: htmlhelp -htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in $(BUILDDIR)/htmlhelp." - -.PHONY: qthelp -qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp - @echo - @echo "Build finished; now you can run "qcollectiongenerator" with the" \ - ".qhcp project file in $(BUILDDIR)/qthelp, like this:" - @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/OpenMWCSManual.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/OpenMWCSManual.qhc" - -.PHONY: applehelp -applehelp: - $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp - @echo - @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." - @echo "N.B. You won't be able to view it unless you put it in" \ - "~/Library/Documentation/Help or install it in your application" \ - "bundle." - -.PHONY: devhelp -devhelp: - $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp - @echo - @echo "Build finished." - @echo "To view the help file:" - @echo "# mkdir -p $$HOME/.local/share/devhelp/OpenMWCSManual" - @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/OpenMWCSManual" - @echo "# devhelp" - -.PHONY: epub -epub: - $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub - @echo - @echo "Build finished. The epub file is in $(BUILDDIR)/epub." - -.PHONY: latex -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo - @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." - @echo "Run \`make' in that directory to run these through (pdf)latex" \ - "(use \`make latexpdf' here to do that automatically)." - -.PHONY: latexpdf -latexpdf: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through pdflatex..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -.PHONY: latexpdfja -latexpdfja: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through platex and dvipdfmx..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -.PHONY: text -text: - $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text - @echo - @echo "Build finished. The text files are in $(BUILDDIR)/text." - -.PHONY: man -man: - $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man - @echo - @echo "Build finished. The manual pages are in $(BUILDDIR)/man." - -.PHONY: texinfo -texinfo: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo - @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." - @echo "Run \`make' in that directory to run these through makeinfo" \ - "(use \`make info' here to do that automatically)." - -.PHONY: info -info: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo "Running Texinfo files through makeinfo..." - make -C $(BUILDDIR)/texinfo info - @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." - -.PHONY: gettext -gettext: - $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale - @echo - @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." - -.PHONY: changes -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes - @echo - @echo "The overview file is in $(BUILDDIR)/changes." - -.PHONY: linkcheck -linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in $(BUILDDIR)/linkcheck/output.txt." - -.PHONY: doctest -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in $(BUILDDIR)/doctest/output.txt." - -.PHONY: coverage -coverage: - $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage - @echo "Testing of coverage in the sources finished, look at the " \ - "results in $(BUILDDIR)/coverage/python.txt." - -.PHONY: xml -xml: - $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml - @echo - @echo "Build finished. The XML files are in $(BUILDDIR)/xml." - -.PHONY: pseudoxml -pseudoxml: - $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml - @echo - @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/docs/cs-manual/make.bat b/docs/cs-manual/make.bat deleted file mode 100644 index 744d60007..000000000 --- a/docs/cs-manual/make.bat +++ /dev/null @@ -1,263 +0,0 @@ -@ECHO OFF - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set BUILDDIR=build -set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source -set I18NSPHINXOPTS=%SPHINXOPTS% source -if NOT "%PAPER%" == "" ( - set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% - set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% -) - -if "%1" == "" goto help - -if "%1" == "help" ( - :help - echo.Please use `make ^` where ^ is one of - echo. html to make standalone HTML files - echo. dirhtml to make HTML files named index.html in directories - echo. singlehtml to make a single large HTML file - echo. pickle to make pickle files - echo. json to make JSON files - echo. htmlhelp to make HTML files and a HTML help project - echo. qthelp to make HTML files and a qthelp project - echo. devhelp to make HTML files and a Devhelp project - echo. epub to make an epub - echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter - echo. text to make text files - echo. man to make manual pages - echo. texinfo to make Texinfo files - echo. gettext to make PO message catalogs - echo. changes to make an overview over all changed/added/deprecated items - echo. xml to make Docutils-native XML files - echo. pseudoxml to make pseudoxml-XML files for display purposes - echo. linkcheck to check all external links for integrity - echo. doctest to run all doctests embedded in the documentation if enabled - echo. coverage to run coverage check of the documentation if enabled - goto end -) - -if "%1" == "clean" ( - for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i - del /q /s %BUILDDIR%\* - goto end -) - - -REM Check if sphinx-build is available and fallback to Python version if any -%SPHINXBUILD% 1>NUL 2>NUL -if errorlevel 9009 goto sphinx_python -goto sphinx_ok - -:sphinx_python - -set SPHINXBUILD=python -m sphinx.__init__ -%SPHINXBUILD% 2> nul -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ - exit /b 1 -) - -:sphinx_ok - - -if "%1" == "html" ( - %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/html. - goto end -) - -if "%1" == "dirhtml" ( - %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. - goto end -) - -if "%1" == "singlehtml" ( - %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. - goto end -) - -if "%1" == "pickle" ( - %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the pickle files. - goto end -) - -if "%1" == "json" ( - %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the JSON files. - goto end -) - -if "%1" == "htmlhelp" ( - %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run HTML Help Workshop with the ^ -.hhp project file in %BUILDDIR%/htmlhelp. - goto end -) - -if "%1" == "qthelp" ( - %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run "qcollectiongenerator" with the ^ -.qhcp project file in %BUILDDIR%/qthelp, like this: - echo.^> qcollectiongenerator %BUILDDIR%\qthelp\OpenMWCSManual.qhcp - echo.To view the help file: - echo.^> assistant -collectionFile %BUILDDIR%\qthelp\OpenMWCSManual.ghc - goto end -) - -if "%1" == "devhelp" ( - %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. - goto end -) - -if "%1" == "epub" ( - %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The epub file is in %BUILDDIR%/epub. - goto end -) - -if "%1" == "latex" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "latexpdf" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - cd %BUILDDIR%/latex - make all-pdf - cd %~dp0 - echo. - echo.Build finished; the PDF files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "latexpdfja" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - cd %BUILDDIR%/latex - make all-pdf-ja - cd %~dp0 - echo. - echo.Build finished; the PDF files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "text" ( - %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The text files are in %BUILDDIR%/text. - goto end -) - -if "%1" == "man" ( - %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The manual pages are in %BUILDDIR%/man. - goto end -) - -if "%1" == "texinfo" ( - %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. - goto end -) - -if "%1" == "gettext" ( - %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The message catalogs are in %BUILDDIR%/locale. - goto end -) - -if "%1" == "changes" ( - %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes - if errorlevel 1 exit /b 1 - echo. - echo.The overview file is in %BUILDDIR%/changes. - goto end -) - -if "%1" == "linkcheck" ( - %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck - if errorlevel 1 exit /b 1 - echo. - echo.Link check complete; look for any errors in the above output ^ -or in %BUILDDIR%/linkcheck/output.txt. - goto end -) - -if "%1" == "doctest" ( - %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest - if errorlevel 1 exit /b 1 - echo. - echo.Testing of doctests in the sources finished, look at the ^ -results in %BUILDDIR%/doctest/output.txt. - goto end -) - -if "%1" == "coverage" ( - %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage - if errorlevel 1 exit /b 1 - echo. - echo.Testing of coverage in the sources finished, look at the ^ -results in %BUILDDIR%/coverage/python.txt. - goto end -) - -if "%1" == "xml" ( - %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The XML files are in %BUILDDIR%/xml. - goto end -) - -if "%1" == "pseudoxml" ( - %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. - goto end -) - -:end diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 000000000..6ee8d6987 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,3 @@ +breathe +parse_cmake +sphinx diff --git a/docs/cs-manual/source/conf.py b/docs/source/conf.py similarity index 65% rename from docs/cs-manual/source/conf.py rename to docs/source/conf.py index 28931a4a0..12c6f91a7 100644 --- a/docs/cs-manual/source/conf.py +++ b/docs/source/conf.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # -# OpenMW CS Manual documentation build configuration file, created by -# sphinx-quickstart on Fri Feb 5 21:28:27 2016. +# OpenMW documentation build configuration file, created by +# sphinx-quickstart on Wed May 14 15:16:35 2014. # # This file is execfile()d with the current directory set to its # containing dir. @@ -18,7 +18,8 @@ import os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.insert(0, os.path.abspath('.')) +project_root = os.path.abspath('../../') +sys.path.insert(0, project_root) # -- General configuration ------------------------------------------------ @@ -29,14 +30,27 @@ import os # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.doctest', + 'sphinx.ext.todo', + 'sphinx.ext.coverage', + 'sphinx.ext.viewcode', + 'breathe', ] +# Where breathe can find the source files +breathe_projects_source = { + "openmw": (project_root+"/apps/openmw", ["engine.hpp", + "mwbase/dialoguemanager.hpp", "mwbase/environment.hpp", + "mwbase/inputmanager.hpp", "mwbase/journal.hpp", "mwbase/mechanicsmanager.hpp", + "mwbase/scriptmanager.hpp", "mwbase/soundmanager.hpp", "mwbase/statemanager.hpp", + "mwbase/windowmanager.hpp", "mwbase/world.hpp"]) + } + # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] -# The suffix(es) of source filenames. -# You can specify multiple suffix as a list of string: -# source_suffix = ['.rst', '.md'] +# The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. @@ -46,25 +60,25 @@ source_suffix = '.rst' master_doc = 'index' # General information about the project. -project = u'OpenMW CS Manual' -copyright = u'2016, The OpenMW Project' -author = u'HiPhish' +project = u'OpenMW' +copyright = u'2016, OpenMW Team' + # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. -version = u'0.0' # The full version, including alpha/beta/rc tags. -release = u'0.0' + +from parse_cmake import parsing +cmake_raw = open(project_root+'/CMakeLists.txt', 'r').read() +cmake_data = parsing.parse(cmake_raw) +release = version = int(cmake_data[24][1][1].contents), int(cmake_data[25][1][1].contents), int(cmake_data[26][1][1].contents) # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -# -# This is also used if you do content translation via gettext catalogs. -# Usually you set "language" from the command line for these cases. -language = None +#language = cpp # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: @@ -100,15 +114,13 @@ pygments_style = 'sphinx' # If true, keep warnings as "system message" paragraphs in the built documents. #keep_warnings = False -# If true, `todo` and `todoList` produce output, else they produce nothing. -todo_include_todos = False - +primary_domain = 'c' # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = 'alabaster' +html_theme = 'default' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the @@ -137,7 +149,9 @@ html_theme = 'alabaster' # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ['_static', + 'openmw-cs/_static' + ] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied @@ -185,22 +199,9 @@ html_static_path = ['_static'] # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None -# Language to be used for generating the HTML full-text search index. -# Sphinx supports the following languages: -# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' -# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' -#html_search_language = 'en' - -# A dictionary with options for the search language support, empty by default. -# Now only 'ja' uses this config value -#html_search_options = {'type': 'default'} - -# The name of a javascript file (relative to the configuration directory) that -# implements a search results scorer. If empty, the default will be used. -#html_search_scorer = 'scorer.js' - # Output file base name for HTML help builder. -htmlhelp_basename = 'OpenMWCSManualdoc' +htmlhelp_basename = 'OpenMWdoc' + # -- Options for LaTeX output --------------------------------------------- @@ -213,17 +214,14 @@ latex_elements = { # Additional stuff for the LaTeX preamble. #'preamble': '', - -# Latex figure (float) alignment -#'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'OpenMWCSManual.tex', u'OpenMW CS Manual Documentation', - u'HiPhish', 'manual'), + ('index', 'OpenMW.tex', u'OpenMW Documentation', + u'Bret Curtis', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of @@ -252,8 +250,8 @@ latex_documents = [ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - (master_doc, 'openmwcsmanual', u'OpenMW CS Manual Documentation', - [author], 1) + ('index', 'openmw', u'OpenMW Documentation', + [u'Bret Curtis'], 1) ] # If true, show URL addresses after external links. @@ -266,9 +264,9 @@ man_pages = [ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - (master_doc, 'OpenMWCSManual', u'OpenMW CS Manual Documentation', - author, 'OpenMWCSManual', 'One line description of project.', - 'Miscellaneous'), + ('index', 'OpenMW', u'OpenMW Documentation', + u'Bret Curtis', 'OpenMW', 'One line description of project.', + 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. @@ -282,77 +280,3 @@ texinfo_documents = [ # If true, do not generate a @detailmenu in the "Top" node's menu. #texinfo_no_detailmenu = False - - -# -- Options for Epub output ---------------------------------------------- - -# Bibliographic Dublin Core info. -epub_title = project -epub_author = author -epub_publisher = author -epub_copyright = copyright - -# The basename for the epub file. It defaults to the project name. -#epub_basename = project - -# The HTML theme for the epub output. Since the default themes are not -# optimized for small screen space, using the same theme for HTML and epub -# output is usually not wise. This defaults to 'epub', a theme designed to save -# visual space. -#epub_theme = 'epub' - -# The language of the text. It defaults to the language option -# or 'en' if the language is not set. -#epub_language = '' - -# The scheme of the identifier. Typical schemes are ISBN or URL. -#epub_scheme = '' - -# The unique identifier of the text. This can be a ISBN number -# or the project homepage. -#epub_identifier = '' - -# A unique identification for the text. -#epub_uid = '' - -# A tuple containing the cover image and cover page html template filenames. -#epub_cover = () - -# A sequence of (type, uri, title) tuples for the guide element of content.opf. -#epub_guide = () - -# HTML files that should be inserted before the pages created by sphinx. -# The format is a list of tuples containing the path and title. -#epub_pre_files = [] - -# HTML files that should be inserted after the pages created by sphinx. -# The format is a list of tuples containing the path and title. -#epub_post_files = [] - -# A list of files that should not be packed into the epub file. -epub_exclude_files = ['search.html'] - -# The depth of the table of contents in toc.ncx. -#epub_tocdepth = 3 - -# Allow duplicate toc entries. -#epub_tocdup = True - -# Choose between 'default' and 'includehidden'. -#epub_tocscope = 'default' - -# Fix unsupported image types using the Pillow. -#epub_fix_images = False - -# Scale large images. -#epub_max_image_width = 0 - -# How to display URL addresses: 'footnote', 'no', or 'inline'. -#epub_show_urls = 'inline' - -# If false, no index is generated. -#epub_use_index = True - - -# Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = {'https://docs.python.org/': None} diff --git a/docs/source/index.rst b/docs/source/index.rst new file mode 100644 index 000000000..32844146c --- /dev/null +++ b/docs/source/index.rst @@ -0,0 +1,20 @@ + +Welcome to OpenMW's documentation! +===================================== + +Components +---------- + +.. toctree:: + :maxdepth: 2 + + openmw/index + openmw-cs/index + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`search` + diff --git a/docs/cs-manual/source/_static/images/chapter-1/add-record.png b/docs/source/openmw-cs/_static/images/chapter-1/add-record.png similarity index 100% rename from docs/cs-manual/source/_static/images/chapter-1/add-record.png rename to docs/source/openmw-cs/_static/images/chapter-1/add-record.png diff --git a/docs/cs-manual/source/_static/images/chapter-1/edit-record.png b/docs/source/openmw-cs/_static/images/chapter-1/edit-record.png similarity index 100% rename from docs/cs-manual/source/_static/images/chapter-1/edit-record.png rename to docs/source/openmw-cs/_static/images/chapter-1/edit-record.png diff --git a/docs/cs-manual/source/_static/images/chapter-1/new-project.png b/docs/source/openmw-cs/_static/images/chapter-1/new-project.png similarity index 100% rename from docs/cs-manual/source/_static/images/chapter-1/new-project.png rename to docs/source/openmw-cs/_static/images/chapter-1/new-project.png diff --git a/docs/cs-manual/source/_static/images/chapter-1/objects.png b/docs/source/openmw-cs/_static/images/chapter-1/objects.png similarity index 100% rename from docs/cs-manual/source/_static/images/chapter-1/objects.png rename to docs/source/openmw-cs/_static/images/chapter-1/objects.png diff --git a/docs/cs-manual/source/_static/images/chapter-1/opening-dialogue.png b/docs/source/openmw-cs/_static/images/chapter-1/opening-dialogue.png similarity index 100% rename from docs/cs-manual/source/_static/images/chapter-1/opening-dialogue.png rename to docs/source/openmw-cs/_static/images/chapter-1/opening-dialogue.png diff --git a/docs/cs-manual/source/files-and-directories.rst b/docs/source/openmw-cs/files-and-directories.rst similarity index 100% rename from docs/cs-manual/source/files-and-directories.rst rename to docs/source/openmw-cs/files-and-directories.rst diff --git a/docs/cs-manual/source/foreword.rst b/docs/source/openmw-cs/foreword.rst similarity index 100% rename from docs/cs-manual/source/foreword.rst rename to docs/source/openmw-cs/foreword.rst diff --git a/docs/cs-manual/source/index.rst b/docs/source/openmw-cs/index.rst similarity index 78% rename from docs/cs-manual/source/index.rst rename to docs/source/openmw-cs/index.rst index ce50b8c95..dcd28081a 100644 --- a/docs/cs-manual/source/index.rst +++ b/docs/source/openmw-cs/index.rst @@ -1,8 +1,3 @@ -.. OpenMW CS Manual documentation master file, created by - sphinx-quickstart on Fri Feb 5 21:28:27 2016. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - ##################### OpenMW CS user manual ##################### diff --git a/docs/cs-manual/source/starting-dialog.rst b/docs/source/openmw-cs/starting-dialog.rst similarity index 100% rename from docs/cs-manual/source/starting-dialog.rst rename to docs/source/openmw-cs/starting-dialog.rst diff --git a/docs/cs-manual/source/tour.rst b/docs/source/openmw-cs/tour.rst similarity index 97% rename from docs/cs-manual/source/tour.rst rename to docs/source/openmw-cs/tour.rst index 0e92fe6fa..3ddeac4fa 100644 --- a/docs/cs-manual/source/tour.rst +++ b/docs/source/openmw-cs/tour.rst @@ -24,7 +24,7 @@ We will start by launching OpenMW CS, the location of the program depends on your operating system. You will be presented with a dialogue with three options: create a new game, create a new addon, edit a content file. -.. figure:: ./_static/images/chapter-1/opening-dialogue.png +.. figure:: _static/images/chapter-1/opening-dialogue.png :alt: Opening dialogue with three option and setting button (the wrench) The first option is for creating an entirely new game, that's not what we want. @@ -37,7 +37,7 @@ optionally a number of other addons we want to depend on. The name of the project is arbitrary, it will be used to identify the addon later in the OpenMW launcher. -.. figure:: ./_static/images/chapter-1/new-project.png +.. figure:: _static/images/chapter-1/new-project.png :alt: Creation dialogue for a new project, pick content modules and name Choose Morrowind as your content file and enter `Ring of Night Vision` as the @@ -47,7 +47,7 @@ to, but for this mod the base game is enough. Once the addon has been created you will be presented with a table. If you see a blank window rather than a table choose *World* → *Objects* from the menu. -.. figure:: ./_static/images/chapter-1/objects.png +.. figure:: _static/images/chapter-1/objects.png :alt: The table showing all objet records in the game. Let's talk about the interface for a second. Every window in OpenMW CS has @@ -83,7 +83,7 @@ We need to enter an *ID* (short for *identifier*) and pick the type. The identifier is a unique name by which the ring can later be identified; I have chosen `ring_night_vision`. For the type choose *Clothing*. -.. figure:: ./_static/images/chapter-1/add-record.png +.. figure:: _static/images/chapter-1/add-record.png :alt: Enter the ID and type of the new ring The table should jump right to our newly created record, if not read further @@ -101,7 +101,7 @@ instead of using the context menu; the default is double-clicking while holding down the shift key. -.. figure:: ./_static/images/chapter-1/edit-record.png +.. figure:: _static/images/chapter-1/edit-record.png :alt: Edit the properties of the record in a separate panel You can set the name, weight and coin value as you like, I chose `Ring of Night diff --git a/docs/source/openmw/index.rst b/docs/source/openmw/index.rst new file mode 100644 index 000000000..ce4e42f1f --- /dev/null +++ b/docs/source/openmw/index.rst @@ -0,0 +1,17 @@ +########################### +OpenMW Source Documentation +########################### + +.. toctree:: + :maxdepth: 2 + + mwbase + +.. autodoxygenfile:: engine.hpp + :project: openmw + +Indices and tables +================== + +* :ref:`genindex` + * :ref:`search` \ No newline at end of file diff --git a/docs/source/openmw/mwbase.rst b/docs/source/openmw/mwbase.rst new file mode 100644 index 000000000..182ed66f6 --- /dev/null +++ b/docs/source/openmw/mwbase.rst @@ -0,0 +1,34 @@ +######## +./mwbase +######## + +.. autodoxygenfile:: mwbase/dialoguemanager.hpp + :project: openmw + +.. autodoxygenfile:: mwbase/environment.hpp + :project: openmw + +.. autodoxygenfile:: mwbase/inputmanager.hpp + :project: openmw + +.. autodoxygenfile:: mwbase/journal.hpp + :project: openmw + +.. autodoxygenfile:: mwbase/mechanicsmanager.hpp + :project: openmw + +.. autodoxygenfile:: mwbase/scriptmanager.hpp + :project: openmw + +.. autodoxygenfile:: mwbase/soundmanager.hpp + :project: openmw + +.. autodoxygenfile:: mwbase/statemanager.hpp + :project: openmw + +.. autodoxygenfile:: mwbase/windowmanager.hpp + :project: openmw + +.. autodoxygenfile:: mwbase/world.hpp + :project: openmw +