#ifndef CONTROLPROXY_H #define CONTROLPROXY_H #include #include #include #include "control/control.h" #include "preferences/usersettings.h" // This class is the successor of ControlObjectThread. It should be used for // new code to avoid unnecessary locking during send if no slot is connected. // Do not (re-)connect slots during runtime, since this locks the mutex in // QMetaObject::activate(). // Be sure that the ControlProxy is created and deleted from the same // thread, otherwise a pending signal may lead to a segfault (Bug #1406124). // Parent it to the the creating object to achieve this. class ControlProxy : public QObject { Q_OBJECT public: ControlProxy(QObject* pParent = NULL); ControlProxy(const QString& g, const QString& i, QObject* pParent = NULL); ControlProxy(const char* g, const char* i, QObject* pParent = NULL); ControlProxy(const ConfigKey& key, QObject* pParent = NULL); virtual ~ControlProxy(); void initialize(const ConfigKey& key, bool warn = true); const ConfigKey& getKey() const { return m_key; } template bool connectValueChanged(Receiver receiver, Slot func, Qt::ConnectionType requestedConnectionType = Qt::AutoConnection) { if (!m_pControl) { return false; } // We connect to the // ControlObjectPrivate only once and in a way that // the requested ConnectionType is working as desired. // We try to avoid direct connections if not requested // since you cannot safely delete an object with a pending // direct connection. This fixes bug Bug #1406124 // requested: Auto -> COP = Auto / SCO = Auto // requested: Direct -> COP = Direct / SCO = Direct // requested: Queued -> COP = Queued / SCO = Auto // requested: BlockingQueued -> Assert(false) auto copSlot = &ControlProxy::slotValueChangedAuto; Qt::ConnectionType copConnection; Qt::ConnectionType scoConnection; switch(requestedConnectionType) { case Qt::AutoConnection: copConnection = Qt::AutoConnection; scoConnection = Qt::AutoConnection; break; case Qt::DirectConnection: copSlot = &ControlProxy::slotValueChangedDirect; copConnection = Qt::DirectConnection; scoConnection = Qt::DirectConnection; break; case Qt::QueuedConnection: copSlot = &ControlProxy::slotValueChangedQueued; copConnection = Qt::QueuedConnection; scoConnection = Qt::AutoConnection; break; case Qt::BlockingQueuedConnection: // We must not block the signal source by a blocking connection DEBUG_ASSERT(false); return false; default: DEBUG_ASSERT(false); return false; } if (!connect(this, &ControlProxy::valueChanged, receiver, func, scoConnection)) { return false; } // Connect to ControlObjectPrivate only if required. Do not allow // duplicate connections. // use only explicit direct connection if requested // the caller must not delete this until the all signals are // processed to avoid segfaults connect(m_pControl.data(), &ControlDoublePrivate::valueChanged, this, copSlot, static_cast(copConnection | Qt::UniqueConnection)); return true; } // TODO: get the compiler to accept parent() for the receiver //template //bool connectValueChanged(Slot func, Qt::ConnectionType type = Qt::AutoConnection) { // DEBUG_ASSERT(parent() != nullptr); // return connectValueChanged(parent(), func, type); //} // Called from update(); virtual void emitValueChanged() { emit(valueChanged(get())); } inline bool valid() const { return m_pControl != NULL; } // Returns the value of the object. Thread safe, non-blocking. inline double get() const { return m_pControl ? m_pControl->get() : 0.0; } // Returns the bool interpretation of the value inline bool toBool() const { return get() > 0.0; } // Returns the parameterized value of the object. Thread safe, non-blocking. inline double getParameter() const { return m_pControl ? m_pControl->getParameter() : 0.0; } // Returns the parameterized value of the object. Thread safe, non-blocking. inline double getParameterForValue(double value) const { return m_pControl ? m_pControl->getParameterForValue(value) : 0.0; } // Returns the normalized parameter of the object. Thread safe, non-blocking. inline double getDefault() const { return m_pControl ? m_pControl->defaultValue() : 0.0; } public slots: // Set the control to a new value. Non-blocking. inline void slotSet(double v) { set(v); } // Sets the control value to v. Thread safe, non-blocking. void set(double v) { if (m_pControl) { m_pControl->set(v, this); } } // Sets the control parameterized value to v. Thread safe, non-blocking. void setParameter(double v) { if (m_pControl) { m_pControl->setParameter(v, this); } } // Resets the control to its default value. Thread safe, non-blocking. void reset() { if (m_pControl) { // NOTE(rryan): This is important. The originator of this action does // not know the resulting value so it makes sense that we should emit a // general valueChanged() signal even though the change originated from // us. For this reason, we provide NULL here so that the change is // not filtered in valueChanged() m_pControl->reset(); } } signals: // This signal must not connected by connect(). Use connectValueChanged() // instead. It will connect to the base ControlDoublePrivate as well. void valueChanged(double); protected slots: // Receives the value from the master control by a unique direct connection void slotValueChangedDirect(double v, QObject* pSetter) { if (pSetter != this) { // This is base implementation of this function without scaling emit(valueChanged(v)); } } // Receives the value from the master control by a unique auto connection void slotValueChangedAuto(double v, QObject* pSetter) { if (pSetter != this) { // This is base implementation of this function without scaling emit(valueChanged(v)); } } // Receives the value from the master control by a unique Queued connection void slotValueChangedQueued(double v, QObject* pSetter) { if (pSetter != this) { // This is base implementation of this function without scaling emit(valueChanged(v)); } } protected: ConfigKey m_key; // Pointer to connected control. QSharedPointer m_pControl; }; #endif // CONTROLPROXY_H