diff options
Diffstat (limited to 'src/include/interfaces.h')
-rw-r--r-- | src/include/interfaces.h | 714 |
1 files changed, 714 insertions, 0 deletions
diff --git a/src/include/interfaces.h b/src/include/interfaces.h new file mode 100644 index 0000000..0ed4a59 --- /dev/null +++ b/src/include/interfaces.h @@ -0,0 +1,714 @@ +/*************************************************************************** + interfaces.h - description + ------------------- + begin : Fre Feb 28 2003 + copyright : (C) 2003 by Martin Witte + email : witte@kawo1.rwth-aachen.de + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef KRADIO_INTERFACES_H +#define KRADIO_INTERFACES_H + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <tqptrlist.h> +#include <tqmap.h> +#include <kdebug.h> +#include <typeinfo> + +/* +///////////////////////////////////////////////////////////////////////////// + + Interfaces - Our Concept + + Without connection management an interface can be defined easily as empty + abstract C++-Class. But that's not what we want. + + Our interfaces also provide connection management. Thus each interface has + exactly one matching counterpart, the complementary interface (cmplIF). + Therefore connecting two objects that have matching interfaces can be + automated. + + Our interfaces have to be able to support the following "functions": + + - send and receive messages (e.g. notifications, commands, ...) to + all connected interfaces. These functions do not need a return value, + but in some cases the sender might want to know if anyone has received + his message. Thus a boolean return value should indicate if the message + was handled or ignored. + + - query for information on connected interfaces / answer queries. These + functions usually have a return value. A query is only executed on the + "current" or - if not selected - the first or only connection. + +///////////////////////////////////////////////////////////////////////////// + + Why are we not using QT signal/slots? + + First the idea of using qt for connecting interfaces is very nice, as the + signal/slot model is well known and hopefully properly implemented. + + But there are some problems: + + - Signals/slots do not support return values, except "call by reference". + To provide queries or a delivery feedback for messages, wrapper functions + would have been necessary. + + - TQt does not support multiple inheritance of TQObjects. Thus even signals + have to be declared abstract by the interface though the (later) + implementation is already known. + + Those functions have to be declared as signals in the interface + implementation (derived from TQObject) though the implementation does not + want to worry about these signals. + + - TQt does connect functions (signals/slots) and not interfaces. These + functions have to be connected separately. By that it is possible to + forget to connect signals/slots of that interfaces. + + - Aggregation of multiple interface implementations (each one is an TQObject) + is not possible because qt does not allow multiple inheritance of TQObjects + +///////////////////////////////////////////////////////////////////////////// + + What about our own solution? + + Well, it eliminates at least the qt-problems explained above. But first we + need a common mechanism to manage interface connections. This functionality + can be provided by a common base class "InterfaceBase". It stores all + connected interfaces in a list of InterfaceBase pointers, e.g. TQPtrList. + + With this approach we would have some problems: + + - When calling a function of a connected interface a slow dynamic_cast + is necessary to upcast the stored InterfaceBase pointer to the + apropriate type. + + - Multiple inheritance of InterfaceBase must not be virtual. Otherwise + interface connection management is mixed between interfaces. + (well, virtual inheritance is usually no real issue, but worth a hint;-) + + To avoid these problems, InterfaceBase is a template with two parameters, + thisIF (IF = interface) and cmplIF (complementary IF). With that + information the base class for an interface is capable to handle + connections with the correct type information. Additionally some pseudo + types are declared (thisInterface, cmplInterface, IFList, IFIterator) to + make easy-to-use macros for messages and queries possible. + +///////////////////////////////////////////////////////////////////////////// + + How do I use it ? - Declarations + + First you have to declare the two matching interface-classes as unkown + classes, because both their names are used in the class declarations. + Afterwards you can declare both classes as class derived from + InterfaceBase. + + class Interface; + class ComplementaryInterface; + + class Interface : public InterfaceBase<Interface, ComplementaryInterface> + { + ... + }; + + class ComplementaryInterface : public InterfaceBase<ComplementaryInterface, Interface> + { + ... + }; + + With macro abbreviation: + + INTERFACE(Interface, ComplementaryInterface) + { + }; + + INTERFACE(ComplementaryInterface, Interface) + { + }; + + + In order to receive/send Messages or query/answer queries we have to declare + special methods: + + - sending Messages + + Declare a virtual constant method with return value "int" and the desired + parameters. The return value will indicate how many receivers have handled + the message: + + virtual bool SendingMessages(int any_or_non_param) const; + + Abbreviation by macros: + + IF_SENDER( SendingMessages(int any_or_non_param) ) + + + - receiving Messages + + Declare an abstract Method with return value "bool", and the desired + paramters. The return value indicates wether the message was handled or not: + + virtual bool ReceivingMessages(int any_or_non_param) = 0; + + Abbreviation by macros: + + IF_RECEIVER( ReceivingMessages(int any_or_non_param) ) + + + The method has to be implemented by a derived class. The current item of the + receivers conntions list is set to the sender. + + + - querying queries + + Declare a virtual constant method with the desired return value and + parameters: + + virtual int QueryingQueries(int another_param) const; + + Abbreviation by macros: + + IF_QUERY( int QueryingQueries(int another_param) ) + + + - answering queries + + Declare an abstract Method with return value void, and the desired + paramters: + + virtual void AnsweringQueries(int another_param) = 0; + + Abbreviation by macros: + + IF_ANSWER( AnsweringQueries(int another_param) ) + + The method has to be implemented by a derived class. The current item of the + receivers conntions list is set to the sender. + + + At last a note on maxConnections. This member is set on initialization by + the constructor and thus can be set in a derived class in it's own + constructor. Negative values are interpreted as "unlimited". + + +///////////////////////////////////////////////////////////////////////////// + + How do I use it ? - Implementations + + Because we do not have a MOC as TQt does, we have to implement our sending + or querying methods by hand. But this minor disadvantage should be + considered as less important than the fact, that this implementation is + done where it belongs to. Especially because there are easy to use macros + to do this: + + int ComplementaryInterface::SendingMessages(int any_or_non_param) const + { + IF_SEND_MESSAGE( ReceivingMessages(any_or_non_param) ) + // macro includes "return #receivers" + } + + int ComplementaryInterface::QueryingQueries(int another_param) const + { + IF_SEND_QUERY( AnsweringQuery(another_param), (int)"default return value" ) + } + + + Even shorter: + + IF_IMPL_SENDER( ComplementaryInterface::QueryingQueries(int param), + AnsweringQueries(param) + ) + + IF_IMPL_QUERY( int ComplementaryInterface::SendingMessages(int param), + ReceivingMessages(param), + (int)"default return value" + ) + +///////////////////////////////////////////////////////////////////////////// + + How do I use it ? - Disconnect/Connect notifications + + + Usually the virtual methods notifyDisconnect(ed) or notifyConnect(ed) + will be called within connect/disconnect methods. + + As constructors and destructors are not able to call virtual methods + of derived classes, there are two possible problems: + + * Constructors: Calling a connect method in a constructor will not result + in a connect notification of any derived class. Thus do not use connect + calls in contructors if any derived class hast to receive all + connect/disconnect notifications. + + * Destructors: If connections are still present if the interface destructor + is called, it will only call its own empty noticedisconnect method. That + shouldn't be a big problem as the derived class is already gone and + doesn't have any interest in this notification any more. But it might be + possible that the connected object wants to call a function of the just + destroyed derived class. That is not possible. Dynamic casts to the + derived class will return NULL. Do not try to call methods of this class + by use of cached pointers. + + + +///////////////////////////////////////////////////////////////////////////// + + Extending and Aggregating Interfaces + + Our interfaces must be extended by aggregation. The reason is that + otherwise we would have the same problems as with a common base class + for connection management. Each interface extensions is an normal + interface on its own. + + Example: + + class I_AM_FM_Radio : public IRadioBase, + public IRadioFrequencyExtension, + public IRadioSeekExtension + { + ... + }; + + To guarantee, that connection management continues to work, we have to overwrite + the connect and disconnect methods: + + virtual bool I_AM_FM_Radio::connect (Interface *i) { + IRadioBase::connect(i); + IFrequencyExtension::connect(i); + ISeekExtension::connect(i); + } + + virtual bool I_AM_FM_Radio::disconnect (Interface *i) { + IRadioBase::disconnect(i); + IFrequencyExtension::disconnect(i); + ISeekExtension::disconnect(i); + } + +*/ + + +///////////////////////////////////////////////////////////////////////////// + +// a polymorphic and *virtual* base class so that we can make use of +// dynamic_casts in connect/disconnect and to be able to merge +// connect/disconnect methods to one single function in case of multiple +// inheritance + +class Interface +{ +public: + Interface () {} + virtual ~Interface() {} + + virtual bool connectI (Interface *) { return false; } + virtual bool disconnectI(Interface *) { return false; } + + // "Interface &"-Versions for convienience, not virtual, only "Interface*" + // versions have to / may be overwritten in case of multiple inheritance + bool connectI (Interface &i) { return connectI (&i); } + bool disconnectI(Interface &i) { return disconnectI (&i); } +}; + +///////////////////////////////////////////////////////////////////////////// + +template <class thisIF, class cmplIF> +class InterfaceBase : virtual public Interface +{ +private: + typedef InterfaceBase<thisIF, cmplIF> thisClass; + typedef InterfaceBase<cmplIF, thisIF> cmplClass; + +// friend class cmplClass; // necessary for connects (to keep number of different connect functions low) + +public: + + typedef thisIF thisInterface; + typedef cmplIF cmplInterface; + + typedef TQPtrList<cmplIF> IFList; + typedef TQPtrListIterator<cmplIF> IFIterator; + + typedef thisClass BaseClass; + +public : + InterfaceBase (int maxIConnections = -1); + virtual ~InterfaceBase (); + + // duplicate connects will add no more entries to connection list + virtual bool connectI(Interface *i); + virtual bool disconnectI(Interface *i); + +protected: + virtual void disconnectAllI(); + + +public: + + // It might be compfortable to derived Interfaces to get an argument + // of the Interface class, but that part of the object might + // already be destroyed. Thus it is necessary to evaluate the additional + // pointer_valid argument. A null pointer is not transmitted, as the + // pointer value might be needed to clean up some references in derived + // classes + virtual void noticeConnectI (cmplInterface *, bool /*pointer_valid*/) {} + virtual void noticeConnectedI (cmplInterface *, bool /*pointer_valid*/) {} + virtual void noticeDisconnectI (cmplInterface *, bool /*pointer_valid*/); + virtual void noticeDisconnectedI(cmplInterface *, bool /*pointer_valid*/) {} + + virtual bool isIConnectionFree() const; + virtual unsigned connectedI() const { return iConnections.count(); } + + thisIF *initThisInterfacePointer(); + thisIF *getThisInterfacePointer() const { return me; } + bool isThisInterfacePointerValid() const { return me_valid; } + bool hasConnectionTo(cmplInterface *other) const { return iConnections.containsRef(other); } + void appendConnectionTo(cmplInterface *other) { iConnections.append(other); } + void removeConnectionTo(cmplInterface *other) { iConnections.removeRef(other); } + +protected : + + IFList iConnections; + int maxIConnections; + + // functions for individually selectable callbacks +protected: + bool addListener (const cmplInterface *i, TQPtrList<cmplInterface> &list); + void removeListener(const cmplInterface *i, TQPtrList<cmplInterface> &list); + void removeListener(const cmplInterface *i); + + TQMap<const cmplInterface *, TQPtrList<TQPtrList<cmplInterface> > > m_FineListeners; + +private: + thisInterface *me; + bool me_valid; +}; + + +// macros for interface declaration + +#define INTERFACE(IF, cmplIF) \ + class IF; \ + class cmplIF; \ + class IF : public InterfaceBase<IF, cmplIF> \ + +#define IF_CON_DESTRUCTOR(IF, n) \ + IF() : BaseClass((n)) {} \ + virtual ~IF() { } + +// macros to make sending messages or queries easier + + +// debug util +#ifdef DEBUG + #include <iostream> + using namespace std; + #define IF_QUERY_DEBUG \ + if (iConnections.count() > 1) { \ + kdDebug() << "class " << typeid(this).name() << ": using IF_QUERY with #connections > 1\n"; \ + } +#else + #define IF_QUERY_DEBUG +#endif + + + +// messages + +#define SENDERS protected +#define RECEIVERS public + +#define IF_SENDER(decl) \ + virtual int decl const; + +#define IF_SEND_MESSAGE(call) \ + int ____n = 0; \ + for (IFIterator i(iConnections); i.current(); ++i) { \ + if (i.current()->call ) ++____n; \ + } \ + return ____n; + +#define IF_IMPL_SENDER(decl, call) \ + int decl const \ + { \ + IF_SEND_MESSAGE(call) \ + } + +#define IF_RECEIVER(decl) \ + virtual bool decl = 0; + +#define IF_RECEIVER_EMPTY(decl) \ + virtual bool decl { return false; } + +// queries + +#define ANSWERS public +#define QUERIES protected + +#define IF_QUERY(decl) \ + virtual decl const; + +#define IF_SEND_QUERY(call, default) \ + cmplInterface *o = IFIterator(iConnections).current(); \ + if (o) { \ + IF_QUERY_DEBUG \ + return o->call; \ + } else { \ + return default; \ + } \ + +#define IF_IMPL_QUERY(decl, call, default) \ + decl const { \ + IF_SEND_QUERY(call, default) \ + } + +#define IF_ANSWER(decl) \ + virtual decl = 0; + + + + +///////////////////////////////////////////////////////////////////////////// +// MACROS for individually selectable callbacks +///////////////////////////////////////////////////////////////////////////// + + +#define IF_SENDER_FINE(name, param) \ +protected: \ + int name param const; \ +public: \ + bool register4_##name (cmplInterface *); \ + void unregister4_##name(cmplInterface *); \ +private: \ + TQPtrList<cmplInterface> m_Listeners_##name;\ + + +#define IF_SEND_MESSAGE_FINE(name, params, call) \ + int ____n = 0; \ + for (TQPtrListIterator<cmplInterface> ____it(m_Listeners_##name); ____it.current(); ++____it) { \ + if (____it.current()->call ) ++____n; \ + } \ + return ____n; + +#define IF_IMPL_SENDER_FINE(class, name, param, call) \ + int class::name param const { \ + IF_SEND_MESSAGE_FINE(name, param, call) \ + } \ + \ + bool class::register4_##name(cmplInterface *i) { \ + return addListener(i, m_Listeners_##name); \ + } \ + void class::unregister4_##name(cmplInterface *i) { \ + m_Listeners_##name.remove(i); \ + } + + +///////////////////////////////////////////////////////////////////////////// + + +template <class thisIF, class cmplIF> +InterfaceBase<thisIF, cmplIF>::InterfaceBase(int _maxIConnections) + : maxIConnections(_maxIConnections), + me(NULL), + me_valid(false) +{ +} + + +template <class thisIF, class cmplIF> +InterfaceBase<thisIF, cmplIF>::~InterfaceBase() +{ + me_valid = false; + // In this state the derived interfaces may already be destroyed + // so that dereferencing cached upcasted me-pointers in noticeDisconnect(ed) + // will fail. + // Thus we must ensure that disconnectAll() is called in the (upper) thisIF + // destructor, not here (see macro IF_CON_DESTRUCTOR). + // If this has not taken place (i.e. the programmer forgot to do so) + // we can only warn, clear our list now and hope that nothing + // more bad will happen + + if (iConnections.count() > 0) { + thisClass::disconnectAllI(); + } +} + + +template <class thisIF, class cmplIF> +bool InterfaceBase<thisIF, cmplIF>::isIConnectionFree () const +{ + int m = maxIConnections; + return (m < 0) || (iConnections.count() < (unsigned) m); +} + +template <class thisIF, class cmplIF> +thisIF *InterfaceBase<thisIF, cmplIF>::initThisInterfacePointer() +{ + if (!me) me = dynamic_cast<thisIF*>(this); + me_valid = me != NULL; + return me; +} + +template <class thisIF, class cmplIF> +bool InterfaceBase<thisIF, cmplIF>::connectI (Interface *__i) +{ + // cache upcasted pointer, especially important for disconnects + // where already destructed derived parts cannot be reached with dynamic casts + initThisInterfacePointer(); + + // same with the other interface + cmplClass *_i = dynamic_cast<cmplClass*>(__i); + if (!_i) { + return false; + } + + cmplIF *i = _i->initThisInterfacePointer(); + + if (i && me) { + bool i_connected = iConnections.containsRef(i); + bool me_connected = i->hasConnectionTo(me); + + if (i_connected || me_connected) { + return true; + } else if (isIConnectionFree() && i->isIConnectionFree()) { + + noticeConnectI(i, i != NULL); + _i->noticeConnectI(me, me != NULL); + + if (!i_connected) + appendConnectionTo(i); + if (!me_connected) + _i->appendConnectionTo(me); + + noticeConnectedI(i, i != NULL); + _i->noticeConnectedI(me, me != NULL); + + return true; + } else { + return false; + } + } + return false; +} + + + +template <class thisIF, class cmplIF> +bool InterfaceBase<thisIF, cmplIF>::disconnectI (Interface *__i) +{ + cmplClass *_i = dynamic_cast<cmplClass*>(__i); + + // use cache to find pointer in connections list + cmplIF *i = _i ? _i->getThisInterfacePointer() : NULL; + + // The cached me pointer might already point to an destroyed + // object. We must use it only for identifying the entry in + // connections list + + if (i && _i) { + if (me_valid) + noticeDisconnectI(i, _i->isThisInterfacePointerValid()); + } + + if (me && _i) { + if (_i->isThisInterfacePointerValid()) + _i->noticeDisconnectI(me, me_valid); + } + + if (i && hasConnectionTo(i)) { + removeListener(i); + removeConnectionTo(i); + } + + if (me && i && i->hasConnectionTo(me)) + i->removeConnectionTo(me); + + if (me_valid && i && _i) + noticeDisconnectedI(i, _i->isThisInterfacePointerValid()); + if (_i && _i->isThisInterfacePointerValid() && me) + _i->noticeDisconnectedI(me, me_valid); + + return true; +} + + +template <class thisIF, class cmplIF> +void InterfaceBase<thisIF, cmplIF>::noticeDisconnectI(cmplInterface *i, bool /*pointer_valid*/) +{ + removeListener(i); +} + + +template <class thisIF, class cmplIF> +void InterfaceBase<thisIF, cmplIF>::disconnectAllI() +{ + IFList tmp = iConnections; + for (IFIterator it(tmp); it.current(); ++it) { + /* Do not call virtual methods if I'm in the contstructor! + Actually this should be ensured by the compiler generated + code and virtual method tables, but unfortunately some compilers + seem to ignore this in some situations. + */ + if (me_valid) + disconnectI(it.current()); + else + thisClass::disconnectI(it.current()); + } +} + + + + +template <class thisIF, class cmplIF> +bool InterfaceBase<thisIF, cmplIF>::addListener(const cmplInterface *i, TQPtrList<cmplInterface> &list) +{ + if (iConnections.containsRef(i) && !list.contains(i)) { + list.append(i); + m_FineListeners[i].append(&list); + return true; + } else { + return false; + } +} + + +template <class thisIF, class cmplIF> +void InterfaceBase<thisIF, cmplIF>::removeListener(const cmplInterface *i, TQPtrList<cmplInterface> &list) +{ + list.remove(i); + if (m_FineListeners.contains(i)) + m_FineListeners[i].remove(&list); +} + + +template <class thisIF, class cmplIF> +void InterfaceBase<thisIF, cmplIF>::removeListener(const cmplInterface *i) +{ + if (m_FineListeners.contains(i)) { + TQPtrList<TQPtrList<cmplInterface> > &list = m_FineListeners[i]; + TQPtrListIterator<TQPtrList<cmplInterface> > it(list); + for (; it.current(); ++it) { + (*it)->remove(i); + } + } + m_FineListeners.remove(i); +} + + + + + + + +#endif |