forked from mirror/openmw-tes3mp
Redesigned shortcut handler to be capable of dealing with child and
parent widgets. This should be the final design change. Also, some various bug fixes.
This commit is contained in:
parent
acdb636935
commit
f251c3867d
16 changed files with 285 additions and 161 deletions
|
@ -1,15 +1,15 @@
|
|||
#include "shortcut.hpp"
|
||||
|
||||
#include <QKeyEvent>
|
||||
#include <QMouseEvent>
|
||||
#include <QShortcut>
|
||||
#include <cassert>
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
#include "state.hpp"
|
||||
#include "shortcutmanager.hpp"
|
||||
|
||||
namespace CSMPrefs
|
||||
{
|
||||
Shortcut::Shortcut(const std::string& name, QObject* parent)
|
||||
Shortcut::Shortcut(const std::string& name, QWidget* parent)
|
||||
: QObject(parent)
|
||||
, mEnabled(true)
|
||||
, mName(name)
|
||||
|
@ -20,13 +20,15 @@ namespace CSMPrefs
|
|||
, mActivationStatus(AS_Inactive)
|
||||
, mModifierStatus(false)
|
||||
{
|
||||
assert (parent);
|
||||
|
||||
State::get().getShortcutManager().addShortcut(this);
|
||||
ShortcutManager::SequenceData data = State::get().getShortcutManager().getSequence(name);
|
||||
setSequence(data.first);
|
||||
setModifier(data.second);
|
||||
}
|
||||
|
||||
Shortcut::Shortcut(const std::string& name, SecondaryMode secMode, QObject* parent)
|
||||
Shortcut::Shortcut(const std::string& name, SecondaryMode secMode, QWidget* parent)
|
||||
: QObject(parent)
|
||||
, mEnabled(true)
|
||||
, mName(name)
|
||||
|
@ -37,6 +39,8 @@ namespace CSMPrefs
|
|||
, mActivationStatus(AS_Inactive)
|
||||
, mModifierStatus(false)
|
||||
{
|
||||
assert (parent);
|
||||
|
||||
State::get().getShortcutManager().addShortcut(this);
|
||||
ShortcutManager::SequenceData data = State::get().getShortcutManager().getSequence(name);
|
||||
setSequence(data.first);
|
||||
|
|
|
@ -7,9 +7,7 @@
|
|||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
class QKeyEvent;
|
||||
class QMouseEvent;
|
||||
class QShortcut;
|
||||
class QWidget;
|
||||
|
||||
namespace CSMPrefs
|
||||
{
|
||||
|
@ -34,8 +32,8 @@ namespace CSMPrefs
|
|||
SM_Ignore ///< The secondary signal will not ever be emitted
|
||||
};
|
||||
|
||||
Shortcut(const std::string& name, QObject* parent);
|
||||
Shortcut(const std::string& name, SecondaryMode secMode, QObject* parent);
|
||||
Shortcut(const std::string& name, QWidget* parent);
|
||||
Shortcut(const std::string& name, SecondaryMode secMode, QWidget* parent);
|
||||
|
||||
~Shortcut();
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#include "shortcuteventhandler.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <cassert>
|
||||
|
||||
#include <QEvent>
|
||||
#include <QKeyEvent>
|
||||
|
@ -19,54 +19,88 @@ namespace CSMPrefs
|
|||
|
||||
void ShortcutEventHandler::addShortcut(Shortcut* shortcut)
|
||||
{
|
||||
mShortcuts.push_back(shortcut);
|
||||
// Enforced by shortcut class
|
||||
QWidget* widget = static_cast<QWidget*>(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)
|
||||
{
|
||||
std::remove(mShortcuts.begin(), mShortcuts.end(), shortcut);
|
||||
// Enforced by shortcut class
|
||||
QWidget* widget = static_cast<QWidget*>(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<QWidget*>(watched);
|
||||
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
|
||||
unsigned int mod = (unsigned int) keyEvent->modifiers();
|
||||
unsigned int key = (unsigned int) keyEvent->key();
|
||||
|
||||
if (!keyEvent->isAutoRepeat())
|
||||
return activate(mod, key);
|
||||
return activate(widget, mod, key);
|
||||
}
|
||||
else if (event->type() == QEvent::KeyRelease)
|
||||
{
|
||||
QWidget* widget = static_cast<QWidget*>(watched);
|
||||
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
|
||||
unsigned int mod = (unsigned int) keyEvent->modifiers();
|
||||
unsigned int key = (unsigned int) keyEvent->key();
|
||||
|
||||
if (!keyEvent->isAutoRepeat())
|
||||
return deactivate(mod, key);
|
||||
return deactivate(widget, mod, key);
|
||||
}
|
||||
else if (event->type() == QEvent::MouseButtonPress)
|
||||
{
|
||||
QWidget* widget = static_cast<QWidget*>(watched);
|
||||
QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
|
||||
unsigned int mod = (unsigned int) mouseEvent->modifiers();
|
||||
unsigned int button = (unsigned int) mouseEvent->button();
|
||||
|
||||
return activate(mod, button);
|
||||
return activate(widget, mod, button);
|
||||
}
|
||||
else if (event->type() == QEvent::MouseButtonRelease)
|
||||
{
|
||||
QWidget* widget = static_cast<QWidget*>(watched);
|
||||
QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
|
||||
unsigned int mod = (unsigned int) mouseEvent->modifiers();
|
||||
unsigned int button = (unsigned int) mouseEvent->button();
|
||||
|
||||
return deactivate(mod, button);
|
||||
return deactivate(widget, mod, button);
|
||||
}
|
||||
else if (event->type() == QEvent::FocusOut)
|
||||
{
|
||||
QWidget* widget = static_cast<QWidget*>(watched);
|
||||
ShortcutMap::iterator shortcutListIt = mWidgetShortcuts.find(widget);
|
||||
|
||||
// Deactivate in case events are missed
|
||||
for (std::vector<Shortcut*>::iterator it = mShortcuts.begin(); it != mShortcuts.end(); ++it)
|
||||
for (ShortcutList::iterator it = shortcutListIt->second.begin(); it != shortcutListIt->second.end(); ++it)
|
||||
{
|
||||
Shortcut* shortcut = *it;
|
||||
|
||||
|
@ -85,41 +119,76 @@ namespace CSMPrefs
|
|||
}
|
||||
}
|
||||
}
|
||||
else if (event->type() == QEvent::FocusIn)
|
||||
{
|
||||
QWidget* widget = static_cast<QWidget*>(watched);
|
||||
updateParent(widget);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ShortcutEventHandler::activate(unsigned int mod, unsigned int button)
|
||||
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));
|
||||
break;
|
||||
}
|
||||
|
||||
// Check next
|
||||
parent = parent->parentWidget();
|
||||
}
|
||||
}
|
||||
|
||||
bool ShortcutEventHandler::activate(QWidget* widget, unsigned int mod, unsigned int button)
|
||||
{
|
||||
std::vector<std::pair<MatchResult, Shortcut*> > potentials;
|
||||
bool used = false;
|
||||
|
||||
// Find potential activations
|
||||
for (std::vector<Shortcut*>::iterator it = mShortcuts.begin(); it != mShortcuts.end(); ++it)
|
||||
while (widget)
|
||||
{
|
||||
Shortcut* shortcut = *it;
|
||||
ShortcutMap::iterator shortcutListIt = mWidgetShortcuts.find(widget);
|
||||
assert(shortcutListIt != mWidgetShortcuts.end());
|
||||
|
||||
if (!shortcut->isEnabled())
|
||||
continue;
|
||||
|
||||
int pos = shortcut->getPosition();
|
||||
int lastPos = shortcut->getLastPosition();
|
||||
MatchResult result = match(mod, button, shortcut->getSequence()[pos]);
|
||||
|
||||
if (result == Matches_WithMod || result == Matches_NoMod)
|
||||
// Find potential activations
|
||||
for (ShortcutList::iterator it = shortcutListIt->second.begin(); it != shortcutListIt->second.end(); ++it)
|
||||
{
|
||||
if (pos < lastPos && (result == Matches_WithMod || pos > 0))
|
||||
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)
|
||||
{
|
||||
shortcut->setPosition(pos+1);
|
||||
}
|
||||
else if (pos == lastPos)
|
||||
{
|
||||
potentials.push_back(std::make_pair(result, shortcut));
|
||||
if (pos < lastPos && (result == Matches_WithMod || pos > 0))
|
||||
{
|
||||
shortcut->setPosition(pos+1);
|
||||
}
|
||||
else if (pos == lastPos)
|
||||
{
|
||||
potentials.push_back(std::make_pair(result, shortcut));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (checkModifier(mod, button, shortcut, true))
|
||||
used = true;
|
||||
// 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.
|
||||
|
@ -147,39 +216,49 @@ namespace CSMPrefs
|
|||
return used;
|
||||
}
|
||||
|
||||
bool ShortcutEventHandler::deactivate(unsigned int mod, unsigned int button)
|
||||
bool ShortcutEventHandler::deactivate(QWidget* widget, unsigned int mod, unsigned int button)
|
||||
{
|
||||
const int KeyMask = 0x01FFFFFF;
|
||||
|
||||
bool used = false;
|
||||
|
||||
for (std::vector<Shortcut*>::iterator it = mShortcuts.begin(); it != mShortcuts.end(); ++it)
|
||||
while (widget)
|
||||
{
|
||||
Shortcut* shortcut = *it;
|
||||
ShortcutMap::iterator shortcutListIt = mWidgetShortcuts.find(widget);
|
||||
assert(shortcutListIt != mWidgetShortcuts.end());
|
||||
|
||||
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)
|
||||
for (ShortcutList::iterator it = shortcutListIt->second.begin(); it != shortcutListIt->second.end(); ++it)
|
||||
{
|
||||
shortcut->setPosition(0);
|
||||
Shortcut* shortcut = *it;
|
||||
|
||||
if (shortcut->getActivationStatus() == Shortcut::AS_Regular)
|
||||
{
|
||||
shortcut->setActivationStatus(Shortcut::AS_Inactive);
|
||||
shortcut->signalActivated(false);
|
||||
if (checkModifier(mod, button, shortcut, false))
|
||||
used = true;
|
||||
}
|
||||
else if (shortcut->getActivationStatus() == Shortcut::AS_Secondary)
|
||||
|
||||
int pos = shortcut->getPosition();
|
||||
MatchResult result = match(0, button, shortcut->getSequence()[pos] & KeyMask);
|
||||
|
||||
if (result != Matches_Not)
|
||||
{
|
||||
shortcut->setActivationStatus(Shortcut::AS_Inactive);
|
||||
shortcut->signalSecondary(false);
|
||||
used = true;
|
||||
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;
|
||||
|
@ -187,7 +266,8 @@ namespace CSMPrefs
|
|||
|
||||
bool ShortcutEventHandler::checkModifier(unsigned int mod, unsigned int button, Shortcut* shortcut, bool activate)
|
||||
{
|
||||
if (!shortcut->isEnabled() || !shortcut->getModifier() || shortcut->getSecondaryMode() == Shortcut::SM_Ignore)
|
||||
if (!shortcut->isEnabled() || !shortcut->getModifier() || shortcut->getSecondaryMode() == Shortcut::SM_Ignore ||
|
||||
shortcut->getModifierStatus() == activate)
|
||||
return false;
|
||||
|
||||
MatchResult result = match(mod, button, shortcut->getModifier());
|
||||
|
@ -208,8 +288,6 @@ namespace CSMPrefs
|
|||
{
|
||||
shortcut->signalSecondary(false);
|
||||
}
|
||||
|
||||
used = true;
|
||||
}
|
||||
else if (!activate && shortcut->getActivationStatus() == Shortcut::AS_Secondary)
|
||||
{
|
||||
|
@ -243,9 +321,17 @@ namespace CSMPrefs
|
|||
bool ShortcutEventHandler::sort(const std::pair<MatchResult, Shortcut*>& left,
|
||||
const std::pair<MatchResult, Shortcut*>& right)
|
||||
{
|
||||
if (left.first == Matches_WithMod && left.first != right.first)
|
||||
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<QWidget*>(sender());
|
||||
|
||||
mWidgetShortcuts.erase(widget);
|
||||
mChildParentRelations.erase(widget);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#ifndef CSM_PREFS_SHORTCUT_EVENT_HANDLER_H
|
||||
#define CSM_PREFS_SHORTCUT_EVENT_HANDLER_H
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
#include <QObject>
|
||||
|
@ -19,7 +20,7 @@ namespace CSMPrefs
|
|||
|
||||
public:
|
||||
|
||||
ShortcutEventHandler(QObject* parent=0);
|
||||
ShortcutEventHandler(QObject* parent);
|
||||
|
||||
void addShortcut(Shortcut* shortcut);
|
||||
void removeShortcut(Shortcut* shortcut);
|
||||
|
@ -30,6 +31,11 @@ namespace CSMPrefs
|
|||
|
||||
private:
|
||||
|
||||
typedef std::vector<Shortcut*> ShortcutList;
|
||||
// Child, Parent
|
||||
typedef std::map<QWidget*, QWidget*> WidgetMap;
|
||||
typedef std::map<QWidget*, ShortcutList> ShortcutMap;
|
||||
|
||||
enum MatchResult
|
||||
{
|
||||
Matches_WithMod,
|
||||
|
@ -37,9 +43,11 @@ namespace CSMPrefs
|
|||
Matches_Not
|
||||
};
|
||||
|
||||
bool activate(unsigned int mod, unsigned int button);
|
||||
void updateParent(QWidget* widget);
|
||||
|
||||
bool deactivate(unsigned int mod, unsigned int button);
|
||||
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);
|
||||
|
||||
|
@ -49,7 +57,12 @@ namespace CSMPrefs
|
|||
static bool sort(const std::pair<MatchResult, Shortcut*>& left,
|
||||
const std::pair<MatchResult, Shortcut*>& right);
|
||||
|
||||
std::vector<Shortcut*> mShortcuts;
|
||||
WidgetMap mChildParentRelations;
|
||||
ShortcutMap mWidgetShortcuts;
|
||||
|
||||
private slots:
|
||||
|
||||
void widgetDestroyed();
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -3,17 +3,25 @@
|
|||
#include <sstream>
|
||||
#include <iostream>
|
||||
|
||||
#include <QApplication>
|
||||
#include <QMetaEnum>
|
||||
#include <QRegExp>
|
||||
#include <QStringList>
|
||||
|
||||
#include "shortcut.hpp"
|
||||
#include "shortcuteventhandler.hpp"
|
||||
|
||||
namespace CSMPrefs
|
||||
{
|
||||
ShortcutManager::ShortcutManager()
|
||||
{
|
||||
mEventHandler = new ShortcutEventHandler(this);
|
||||
}
|
||||
|
||||
void ShortcutManager::addShortcut(Shortcut* shortcut)
|
||||
{
|
||||
mShortcuts.insert(std::make_pair(shortcut->getName(), shortcut));
|
||||
mEventHandler->addShortcut(shortcut);
|
||||
}
|
||||
|
||||
void ShortcutManager::removeShortcut(Shortcut* shortcut)
|
||||
|
@ -31,6 +39,8 @@ namespace CSMPrefs
|
|||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
mEventHandler->removeShortcut(shortcut);
|
||||
}
|
||||
|
||||
ShortcutManager::SequenceData ShortcutManager::getSequence(const std::string& name) const
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
namespace CSMPrefs
|
||||
{
|
||||
class Shortcut;
|
||||
class ShortcutEventHandler;
|
||||
|
||||
/// Class used to track and update shortcuts/sequences
|
||||
class ShortcutManager : public QObject
|
||||
|
@ -20,6 +21,9 @@ namespace CSMPrefs
|
|||
/// Key Sequence, Modifier (for secondary signal)
|
||||
typedef std::pair<QKeySequence, int> SequenceData;
|
||||
|
||||
|
||||
ShortcutManager();
|
||||
|
||||
/// The shortcut class will do this automatically
|
||||
void addShortcut(Shortcut* shortcut);
|
||||
|
||||
|
@ -40,6 +44,8 @@ namespace CSMPrefs
|
|||
|
||||
ShortcutMap mShortcuts;
|
||||
SequenceMap mSequences;
|
||||
|
||||
ShortcutEventHandler* mEventHandler;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -229,6 +229,10 @@ void CSMPrefs::State::declare()
|
|||
addValues (insertOutsideVisibleCell);
|
||||
|
||||
declareCategory ("Key Bindings");
|
||||
|
||||
declareShortcut ("document-save", "Save", QKeySequence(Qt::ControlModifier | Qt::Key_S));
|
||||
|
||||
declareSeparator ();
|
||||
declareShortcut ("free-forward", "Free camera forward", QKeySequence(Qt::Key_W), Qt::Key_Shift);
|
||||
declareShortcut ("free-backward", "Free camera backward", QKeySequence(Qt::Key_S));
|
||||
declareShortcut ("free-left", "Free camera left", QKeySequence(Qt::Key_A));
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
#include "../../model/doc/document.hpp"
|
||||
#include "../../model/prefs/state.hpp"
|
||||
#include "../../model/prefs/shortcut.hpp"
|
||||
|
||||
#include "../../model/world/idtable.hpp"
|
||||
|
||||
|
@ -62,6 +63,9 @@ void CSVDoc::View::setupFileMenu()
|
|||
connect (mSave, SIGNAL (triggered()), this, SLOT (save()));
|
||||
file->addAction (mSave);
|
||||
|
||||
CSMPrefs::Shortcut* saveShortcut = new CSMPrefs::Shortcut("document-save", this);
|
||||
connect (saveShortcut, SIGNAL(activated()), this, SLOT(save()));
|
||||
|
||||
mVerify = new QAction (tr ("&Verify"), this);
|
||||
connect (mVerify, SIGNAL (triggered()), this, SLOT (verify()));
|
||||
file->addAction (mVerify);
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
#include <cmath>
|
||||
|
||||
#include <QKeyEvent>
|
||||
#include <QWidget>
|
||||
|
||||
#include <osg/BoundingBox>
|
||||
#include <osg/Camera>
|
||||
|
@ -15,7 +15,6 @@
|
|||
#include <osgUtil/LineSegmentIntersector>
|
||||
|
||||
#include "../../model/prefs/shortcut.hpp"
|
||||
#include "../../model/prefs/shortcuteventhandler.hpp"
|
||||
|
||||
#include "scenewidget.hpp"
|
||||
|
||||
|
@ -79,27 +78,17 @@ namespace CSVRender
|
|||
|
||||
void CameraController::setCamera(osg::Camera* camera)
|
||||
{
|
||||
bool wasActive = mActive;
|
||||
|
||||
mCamera = camera;
|
||||
mActive = (mCamera != NULL);
|
||||
|
||||
if (mActive)
|
||||
if (mActive != wasActive)
|
||||
{
|
||||
onActivate();
|
||||
|
||||
QList<CSMPrefs::Shortcut*> shortcuts = findChildren<CSMPrefs::Shortcut*>();
|
||||
|
||||
for (QList<CSMPrefs::Shortcut*>::iterator it = shortcuts.begin(); it != shortcuts.end(); ++it)
|
||||
for (std::vector<CSMPrefs::Shortcut*>::iterator it = mShortcuts.begin(); it != mShortcuts.end(); ++it)
|
||||
{
|
||||
(*it)->enable(true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
QList<CSMPrefs::Shortcut*> shortcuts = findChildren<CSMPrefs::Shortcut*>();
|
||||
|
||||
for (QList<CSMPrefs::Shortcut*>::iterator it = shortcuts.begin(); it != shortcuts.end(); ++it)
|
||||
{
|
||||
(*it)->enable(false);
|
||||
CSMPrefs::Shortcut* shortcut = *it;
|
||||
shortcut->enable(mActive);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -160,14 +149,21 @@ namespace CSVRender
|
|||
getCamera()->setViewMatrixAsLookAt(eye, center, up);
|
||||
}
|
||||
|
||||
void CameraController::addShortcut(CSMPrefs::Shortcut* shortcut)
|
||||
{
|
||||
mShortcuts.push_back(shortcut);
|
||||
}
|
||||
|
||||
/*
|
||||
Free Camera Controller
|
||||
*/
|
||||
|
||||
FreeCameraController::FreeCameraController(CSMPrefs::ShortcutEventHandler* handler, QObject* parent)
|
||||
: CameraController(parent)
|
||||
FreeCameraController::FreeCameraController(QWidget* widget)
|
||||
: CameraController(widget)
|
||||
, mLockUpright(false)
|
||||
, mModified(false)
|
||||
, mNaviPrimary(false)
|
||||
, mNaviSecondary(false)
|
||||
, mFast(false)
|
||||
, mFastAlternate(false)
|
||||
, mLeft(false)
|
||||
|
@ -181,52 +177,61 @@ namespace CSVRender
|
|||
, mRotSpeed(osg::PI / 2)
|
||||
, mSpeedMult(8)
|
||||
{
|
||||
CSMPrefs::Shortcut* naviPrimaryShortcut = new CSMPrefs::Shortcut("scene-navi-primary", this);
|
||||
CSMPrefs::Shortcut* naviPrimaryShortcut = new CSMPrefs::Shortcut("scene-navi-primary", widget);
|
||||
naviPrimaryShortcut->enable(false);
|
||||
handler->addShortcut(naviPrimaryShortcut);
|
||||
connect(naviPrimaryShortcut, SIGNAL(activated(bool)), this, SLOT(naviPrimary(bool)));
|
||||
|
||||
CSMPrefs::Shortcut* naviSecondaryShortcut = new CSMPrefs::Shortcut("scene-navi-secondary", this);
|
||||
addShortcut(naviPrimaryShortcut);
|
||||
|
||||
CSMPrefs::Shortcut* naviSecondaryShortcut = new CSMPrefs::Shortcut("scene-navi-secondary", widget);
|
||||
naviSecondaryShortcut->enable(false);
|
||||
handler->addShortcut(naviSecondaryShortcut);
|
||||
connect(naviSecondaryShortcut, SIGNAL(activated(bool)), this, SLOT(naviSecondary(bool)));
|
||||
|
||||
addShortcut(naviSecondaryShortcut);
|
||||
|
||||
CSMPrefs::Shortcut* forwardShortcut = new CSMPrefs::Shortcut("free-forward", CSMPrefs::Shortcut::SM_Detach,
|
||||
this);
|
||||
widget);
|
||||
forwardShortcut->enable(false);
|
||||
handler->addShortcut(forwardShortcut);
|
||||
connect(forwardShortcut, SIGNAL(activated(bool)), this, SLOT(forward(bool)));
|
||||
connect(forwardShortcut, SIGNAL(secondary(bool)), this, SLOT(alternateFast(bool)));
|
||||
|
||||
CSMPrefs::Shortcut* leftShortcut = new CSMPrefs::Shortcut("free-left", this);
|
||||
addShortcut(forwardShortcut);
|
||||
|
||||
CSMPrefs::Shortcut* leftShortcut = new CSMPrefs::Shortcut("free-left", widget);
|
||||
leftShortcut->enable(false);
|
||||
handler->addShortcut(leftShortcut);
|
||||
connect(leftShortcut, SIGNAL(activated(bool)), this, SLOT(left(bool)));
|
||||
|
||||
CSMPrefs::Shortcut* backShortcut = new CSMPrefs::Shortcut("free-backward", this);
|
||||
addShortcut(leftShortcut);
|
||||
|
||||
CSMPrefs::Shortcut* backShortcut = new CSMPrefs::Shortcut("free-backward", widget);
|
||||
backShortcut->enable(false);
|
||||
handler->addShortcut(backShortcut);
|
||||
connect(backShortcut, SIGNAL(activated(bool)), this, SLOT(backward(bool)));
|
||||
|
||||
CSMPrefs::Shortcut* rightShortcut = new CSMPrefs::Shortcut("free-right", this);
|
||||
addShortcut(backShortcut);
|
||||
|
||||
CSMPrefs::Shortcut* rightShortcut = new CSMPrefs::Shortcut("free-right", widget);
|
||||
rightShortcut->enable(false);
|
||||
handler->addShortcut(rightShortcut);
|
||||
connect(rightShortcut, SIGNAL(activated(bool)), this, SLOT(right(bool)));
|
||||
|
||||
CSMPrefs::Shortcut* rollLeftShortcut = new CSMPrefs::Shortcut("free-roll-left", this);
|
||||
addShortcut(rightShortcut);
|
||||
|
||||
CSMPrefs::Shortcut* rollLeftShortcut = new CSMPrefs::Shortcut("free-roll-left", widget);
|
||||
rollLeftShortcut->enable(false);
|
||||
handler->addShortcut(rollLeftShortcut);
|
||||
connect(rollLeftShortcut, SIGNAL(activated(bool)), this, SLOT(rollLeft(bool)));
|
||||
|
||||
CSMPrefs::Shortcut* rollRightShortcut = new CSMPrefs::Shortcut("free-roll-right", this);
|
||||
addShortcut(rollLeftShortcut);
|
||||
|
||||
CSMPrefs::Shortcut* rollRightShortcut = new CSMPrefs::Shortcut("free-roll-right", widget);
|
||||
rollRightShortcut->enable(false);
|
||||
handler->addShortcut(rollRightShortcut);
|
||||
connect(rollRightShortcut, SIGNAL(activated(bool)), this, SLOT(rollRight(bool)));
|
||||
|
||||
CSMPrefs::Shortcut* speedModeShortcut = new CSMPrefs::Shortcut("free-speed-mode", this);
|
||||
addShortcut(rollRightShortcut);
|
||||
|
||||
CSMPrefs::Shortcut* speedModeShortcut = new CSMPrefs::Shortcut("free-speed-mode", widget);
|
||||
speedModeShortcut->enable(false);
|
||||
handler->addShortcut(speedModeShortcut);
|
||||
connect(speedModeShortcut, SIGNAL(activated()), this, SLOT(swapSpeedMode()));
|
||||
|
||||
addShortcut(speedModeShortcut);
|
||||
}
|
||||
|
||||
double FreeCameraController::getLinearSpeed() const
|
||||
|
@ -440,9 +445,11 @@ namespace CSVRender
|
|||
Orbit Camera Controller
|
||||
*/
|
||||
|
||||
OrbitCameraController::OrbitCameraController(CSMPrefs::ShortcutEventHandler* handler, QObject* parent)
|
||||
: CameraController(parent)
|
||||
OrbitCameraController::OrbitCameraController(QWidget* widget)
|
||||
: CameraController(widget)
|
||||
, mInitialized(false)
|
||||
, mNaviPrimary(false)
|
||||
, mNaviSecondary(false)
|
||||
, mFast(false)
|
||||
, mFastAlternate(false)
|
||||
, mLeft(false)
|
||||
|
@ -457,51 +464,60 @@ namespace CSVRender
|
|||
, mOrbitSpeed(osg::PI / 4)
|
||||
, mOrbitSpeedMult(4)
|
||||
{
|
||||
CSMPrefs::Shortcut* naviPrimaryShortcut = new CSMPrefs::Shortcut("scene-navi-primary", this);
|
||||
CSMPrefs::Shortcut* naviPrimaryShortcut = new CSMPrefs::Shortcut("scene-navi-primary", widget);
|
||||
naviPrimaryShortcut->enable(false);
|
||||
handler->addShortcut(naviPrimaryShortcut);
|
||||
connect(naviPrimaryShortcut, SIGNAL(activated(bool)), this, SLOT(naviPrimary(bool)));
|
||||
|
||||
CSMPrefs::Shortcut* naviSecondaryShortcut = new CSMPrefs::Shortcut("scene-navi-secondary", this);
|
||||
addShortcut(naviPrimaryShortcut);
|
||||
|
||||
CSMPrefs::Shortcut* naviSecondaryShortcut = new CSMPrefs::Shortcut("scene-navi-secondary", widget);
|
||||
naviSecondaryShortcut->enable(false);
|
||||
handler->addShortcut(naviSecondaryShortcut);
|
||||
connect(naviSecondaryShortcut, SIGNAL(activated(bool)), this, SLOT(naviSecondary(bool)));
|
||||
|
||||
CSMPrefs::Shortcut* upShortcut = new CSMPrefs::Shortcut("orbit-up", CSMPrefs::Shortcut::SM_Detach, this);
|
||||
addShortcut(naviSecondaryShortcut);
|
||||
|
||||
CSMPrefs::Shortcut* upShortcut = new CSMPrefs::Shortcut("orbit-up", CSMPrefs::Shortcut::SM_Detach, widget);
|
||||
upShortcut->enable(false);
|
||||
handler->addShortcut(upShortcut);
|
||||
connect(upShortcut, SIGNAL(activated(bool)), this, SLOT(up(bool)));
|
||||
connect(upShortcut, SIGNAL(secondary(bool)), this, SLOT(alternateFast(bool)));
|
||||
|
||||
CSMPrefs::Shortcut* leftShortcut = new CSMPrefs::Shortcut("orbit-left", this);
|
||||
addShortcut(upShortcut);
|
||||
|
||||
CSMPrefs::Shortcut* leftShortcut = new CSMPrefs::Shortcut("orbit-left", widget);
|
||||
leftShortcut->enable(false);
|
||||
handler->addShortcut(leftShortcut);
|
||||
connect(leftShortcut, SIGNAL(activated(bool)), this, SLOT(left(bool)));
|
||||
|
||||
CSMPrefs::Shortcut* downShortcut = new CSMPrefs::Shortcut("orbit-down", this);
|
||||
addShortcut(leftShortcut);
|
||||
|
||||
CSMPrefs::Shortcut* downShortcut = new CSMPrefs::Shortcut("orbit-down", widget);
|
||||
downShortcut->enable(false);
|
||||
handler->addShortcut(downShortcut);
|
||||
connect(downShortcut, SIGNAL(activated(bool)), this, SLOT(down(bool)));
|
||||
|
||||
CSMPrefs::Shortcut* rightShortcut = new CSMPrefs::Shortcut("orbit-right", this);
|
||||
addShortcut(downShortcut);
|
||||
|
||||
CSMPrefs::Shortcut* rightShortcut = new CSMPrefs::Shortcut("orbit-right", widget);
|
||||
rightShortcut->enable(false);
|
||||
handler->addShortcut(rightShortcut);
|
||||
connect(rightShortcut, SIGNAL(activated(bool)), this, SLOT(right(bool)));
|
||||
|
||||
CSMPrefs::Shortcut* rollLeftShortcut = new CSMPrefs::Shortcut("orbit-roll-left", this);
|
||||
addShortcut(rightShortcut);
|
||||
|
||||
CSMPrefs::Shortcut* rollLeftShortcut = new CSMPrefs::Shortcut("orbit-roll-left", widget);
|
||||
rollLeftShortcut->enable(false);
|
||||
handler->addShortcut(rollLeftShortcut);
|
||||
connect(rollLeftShortcut, SIGNAL(activated(bool)), this, SLOT(rollLeft(bool)));
|
||||
|
||||
CSMPrefs::Shortcut* rollRightShortcut = new CSMPrefs::Shortcut("orbit-roll-right", this);
|
||||
addShortcut(rollLeftShortcut);
|
||||
|
||||
CSMPrefs::Shortcut* rollRightShortcut = new CSMPrefs::Shortcut("orbit-roll-right", widget);
|
||||
rollRightShortcut->enable(false);
|
||||
handler->addShortcut(rollRightShortcut);
|
||||
connect(rollRightShortcut, SIGNAL(activated(bool)), this, SLOT(rollRight(bool)));
|
||||
|
||||
CSMPrefs::Shortcut* speedModeShortcut = new CSMPrefs::Shortcut("orbit-speed-mode", this);
|
||||
addShortcut(rollRightShortcut);
|
||||
|
||||
CSMPrefs::Shortcut* speedModeShortcut = new CSMPrefs::Shortcut("orbit-speed-mode", widget);
|
||||
speedModeShortcut->enable(false);
|
||||
handler->addShortcut(speedModeShortcut);
|
||||
connect(speedModeShortcut, SIGNAL(activated()), this, SLOT(swapSpeedMode()));
|
||||
|
||||
addShortcut(speedModeShortcut);
|
||||
}
|
||||
|
||||
osg::Vec3d OrbitCameraController::getCenter() const
|
||||
|
|
|
@ -2,14 +2,13 @@
|
|||
#define OPENCS_VIEW_CAMERACONTROLLER_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include <osg/ref_ptr>
|
||||
#include <osg/Vec3d>
|
||||
|
||||
class QKeyEvent;
|
||||
|
||||
namespace osg
|
||||
{
|
||||
class Camera;
|
||||
|
@ -18,7 +17,7 @@ namespace osg
|
|||
|
||||
namespace CSMPrefs
|
||||
{
|
||||
class ShortcutEventHandler;
|
||||
class Shortcut;
|
||||
}
|
||||
|
||||
namespace CSVRender
|
||||
|
@ -66,6 +65,8 @@ namespace CSVRender
|
|||
|
||||
virtual void onActivate(){}
|
||||
|
||||
void addShortcut(CSMPrefs::Shortcut* shortcut);
|
||||
|
||||
private:
|
||||
|
||||
bool mActive, mInverted;
|
||||
|
@ -74,6 +75,8 @@ namespace CSVRender
|
|||
double mWheelMoveMult;
|
||||
|
||||
osg::Camera* mCamera;
|
||||
|
||||
std::vector<CSMPrefs::Shortcut*> mShortcuts;
|
||||
};
|
||||
|
||||
class FreeCameraController : public CameraController
|
||||
|
@ -82,7 +85,7 @@ namespace CSVRender
|
|||
|
||||
public:
|
||||
|
||||
FreeCameraController(CSMPrefs::ShortcutEventHandler* handler, QObject* parent=0);
|
||||
FreeCameraController(QWidget* parent);
|
||||
|
||||
double getLinearSpeed() const;
|
||||
double getRotationalSpeed() const;
|
||||
|
@ -139,7 +142,7 @@ namespace CSVRender
|
|||
|
||||
public:
|
||||
|
||||
OrbitCameraController(CSMPrefs::ShortcutEventHandler* handler, QObject* parent=0);
|
||||
OrbitCameraController(QWidget* parent);
|
||||
|
||||
osg::Vec3d getCenter() const;
|
||||
double getOrbitSpeed() const;
|
||||
|
|
|
@ -9,22 +9,19 @@
|
|||
|
||||
namespace CSVRender
|
||||
{
|
||||
OrbitCameraMode::OrbitCameraMode(WorldspaceWidget* worldspaceWidget, CSMPrefs::ShortcutEventHandler* handler,
|
||||
const QIcon& icon, const QString& tooltip, QWidget* parent)
|
||||
OrbitCameraMode::OrbitCameraMode(WorldspaceWidget* worldspaceWidget, const QIcon& icon, const QString& tooltip,
|
||||
QWidget* parent)
|
||||
: ModeButton(icon, tooltip, parent)
|
||||
, mWorldspaceWidget(worldspaceWidget)
|
||||
, mShortcutHandler(handler)
|
||||
, mCenterOnSelection(0)
|
||||
{
|
||||
mCenterShortcut = new CSMPrefs::Shortcut("orbit-center-selection", this);
|
||||
mCenterShortcut.reset(new CSMPrefs::Shortcut("orbit-center-selection", worldspaceWidget));
|
||||
mCenterShortcut->enable(false);
|
||||
mShortcutHandler->addShortcut(mCenterShortcut);
|
||||
connect(mCenterShortcut, SIGNAL(activated()), this, SLOT(centerSelection()));
|
||||
connect(mCenterShortcut.get(), SIGNAL(activated()), this, SLOT(centerSelection()));
|
||||
}
|
||||
|
||||
OrbitCameraMode::~OrbitCameraMode()
|
||||
{
|
||||
mShortcutHandler->removeShortcut(mCenterShortcut);
|
||||
}
|
||||
|
||||
void OrbitCameraMode::activate(CSVWidget::SceneToolbar* toolbar)
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
#ifndef CSV_RENDER_ORBITCAMERAPICKMODE_H
|
||||
#define CSV_RENDER_ORBITCAMERAPICKMODE_H
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "../widget/modebutton.hpp"
|
||||
|
||||
namespace CSMPrefs
|
||||
{
|
||||
class Shortcut;
|
||||
class ShortcutEventHandler;
|
||||
}
|
||||
|
||||
namespace CSVRender
|
||||
|
@ -19,8 +20,8 @@ namespace CSVRender
|
|||
|
||||
public:
|
||||
|
||||
OrbitCameraMode(WorldspaceWidget* worldspaceWidget, CSMPrefs::ShortcutEventHandler* shortcutHandler,
|
||||
const QIcon& icon, const QString& tooltip = "", QWidget* parent = 0);
|
||||
OrbitCameraMode(WorldspaceWidget* worldspaceWidget, const QIcon& icon, const QString& tooltip = "",
|
||||
QWidget* parent = 0);
|
||||
~OrbitCameraMode();
|
||||
|
||||
virtual void activate(CSVWidget::SceneToolbar* toolbar);
|
||||
|
@ -30,9 +31,8 @@ namespace CSVRender
|
|||
private:
|
||||
|
||||
WorldspaceWidget* mWorldspaceWidget;
|
||||
CSMPrefs::ShortcutEventHandler* mShortcutHandler;
|
||||
QAction* mCenterOnSelection;
|
||||
CSMPrefs::Shortcut* mCenterShortcut;
|
||||
std::auto_ptr<CSMPrefs::Shortcut> mCenterShortcut;
|
||||
|
||||
private slots:
|
||||
|
||||
|
|
|
@ -171,11 +171,8 @@ SceneWidget::SceneWidget(boost::shared_ptr<Resource::ResourceSystem> resourceSys
|
|||
, mPrevMouseY(0)
|
||||
, mCamPositionSet(false)
|
||||
{
|
||||
mShortcutHandler = new CSMPrefs::ShortcutEventHandler(this);
|
||||
installEventFilter(mShortcutHandler);
|
||||
|
||||
mFreeCamControl = new FreeCameraController(mShortcutHandler, this);
|
||||
mOrbitCamControl = new OrbitCameraController(mShortcutHandler, this);
|
||||
mFreeCamControl = new FreeCameraController(this);
|
||||
mOrbitCamControl = new OrbitCameraController(this);
|
||||
mCurrentCamControl = mFreeCamControl;
|
||||
|
||||
mOrbitCamControl->setPickingMask(Mask_Reference | Mask_Terrain);
|
||||
|
@ -205,18 +202,14 @@ SceneWidget::SceneWidget(boost::shared_ptr<Resource::ResourceSystem> resourceSys
|
|||
|
||||
// Shortcuts
|
||||
CSMPrefs::Shortcut* focusToolbarShortcut = new CSMPrefs::Shortcut("scene-focus-toolbar", this);
|
||||
mShortcutHandler->addShortcut(focusToolbarShortcut);
|
||||
connect(focusToolbarShortcut, SIGNAL(activated()), this, SIGNAL(focusToolbarRequest()));
|
||||
|
||||
CSMPrefs::Shortcut* renderStatsShortcut = new CSMPrefs::Shortcut("scene-render-stats", this);
|
||||
mShortcutHandler->addShortcut(renderStatsShortcut);
|
||||
connect(renderStatsShortcut, SIGNAL(activated()), this, SLOT(toggleRenderStats()));
|
||||
}
|
||||
|
||||
SceneWidget::~SceneWidget()
|
||||
{
|
||||
removeEventFilter(mShortcutHandler);
|
||||
|
||||
// Since we're holding on to the scene templates past the existance of this graphics context, we'll need to manually release the created objects
|
||||
mResourceSystem->getSceneManager()->releaseGLObjects(mView->getCamera()->getGraphicsContext()->getState());
|
||||
}
|
||||
|
|
|
@ -37,8 +37,6 @@ namespace CSVWidget
|
|||
namespace CSMPrefs
|
||||
{
|
||||
class Setting;
|
||||
class Shortcut;
|
||||
class ShortcutEventHandler;
|
||||
}
|
||||
|
||||
namespace CSVRender
|
||||
|
@ -116,8 +114,6 @@ namespace CSVRender
|
|||
OrbitCameraController* mOrbitCamControl;
|
||||
CameraController* mCurrentCamControl;
|
||||
|
||||
CSMPrefs::ShortcutEventHandler *mShortcutHandler;
|
||||
|
||||
private:
|
||||
bool mCamPositionSet;
|
||||
|
||||
|
|
|
@ -98,23 +98,18 @@ CSVRender::WorldspaceWidget::WorldspaceWidget (CSMDoc::Document& document, QWidg
|
|||
|
||||
// Shortcuts
|
||||
CSMPrefs::Shortcut* primaryEditShortcut = new CSMPrefs::Shortcut("scene-edit-primary", this);
|
||||
mShortcutHandler->addShortcut(primaryEditShortcut);
|
||||
connect(primaryEditShortcut, SIGNAL(activated(bool)), this, SLOT(primaryEdit(bool)));
|
||||
|
||||
CSMPrefs::Shortcut* secondaryEditShortcut = new CSMPrefs::Shortcut("scene-edit-secondary", this);
|
||||
mShortcutHandler->addShortcut(secondaryEditShortcut);
|
||||
connect(secondaryEditShortcut, SIGNAL(activated(bool)), this, SLOT(secondaryEdit(bool)));
|
||||
|
||||
CSMPrefs::Shortcut* primarySelectShortcut = new CSMPrefs::Shortcut("scene-select-primary", this);
|
||||
mShortcutHandler->addShortcut(primarySelectShortcut);
|
||||
connect(primarySelectShortcut, SIGNAL(activated(bool)), this, SLOT(primarySelect(bool)));
|
||||
|
||||
CSMPrefs::Shortcut* secondarySelectShortcut = new CSMPrefs::Shortcut("scene-select-secondary", this);
|
||||
mShortcutHandler->addShortcut(secondarySelectShortcut);
|
||||
connect(secondarySelectShortcut, SIGNAL(activated(bool)), this, SLOT(secondarySelect(bool)));
|
||||
|
||||
CSMPrefs::Shortcut* abortShortcut = new CSMPrefs::Shortcut("scene-edit-abort", this);
|
||||
mShortcutHandler->addShortcut(abortShortcut);
|
||||
connect(abortShortcut, SIGNAL(activated()), this, SLOT(abortDrag()));
|
||||
}
|
||||
|
||||
|
@ -184,7 +179,7 @@ CSVWidget::SceneToolMode *CSVRender::WorldspaceWidget::makeNavigationSelector (
|
|||
"<li>Hold shift to speed up movement</li>"
|
||||
"</ul>");
|
||||
tool->addButton(
|
||||
new CSVRender::OrbitCameraMode(this, mShortcutHandler, QIcon(":scenetoolbar/orbiting-camera"),
|
||||
new CSVRender::OrbitCameraMode(this, QIcon(":scenetoolbar/orbiting-camera"),
|
||||
"Orbiting Camera"
|
||||
"<ul><li>Always facing the centre point</li>"
|
||||
"<li>Rotate around the centre point via WASD or by moving the mouse while holding the left button</li>"
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
namespace CSMPrefs
|
||||
{
|
||||
class Setting;
|
||||
class Shortcut;
|
||||
}
|
||||
|
||||
namespace CSMWorld
|
||||
|
|
Loading…
Reference in a new issue