1
/***************************************************************************
2
interfaces.h - description
4
begin : Fre Feb 28 2003
5
copyright : (C) 2003 by Martin Witte
6
email : witte@kawo1.rwth-aachen.de
7
***************************************************************************/
9
/***************************************************************************
11
* This program is free software; you can redistribute it and/or modify *
12
* it under the terms of the GNU General Public License as published by *
13
* the Free Software Foundation; either version 2 of the License, or *
14
* (at your option) any later version. *
16
***************************************************************************/
18
#ifndef KRADIO_INTERFACES_H
19
#define KRADIO_INTERFACES_H
31
/////////////////////////////////////////////////////////////////////////////
33
Interfaces - Our Concept
35
Without connection management an interface can be defined easily as empty
36
abstract C++-Class. But that's not what we want.
38
Our interfaces also provide connection management. Thus each interface has
39
exactly one matching counterpart, the complementary interface (cmplIF).
40
Therefore connecting two objects that have matching interfaces can be
43
Our interfaces have to be able to support the following "functions":
45
- send and receive messages (e.g. notifications, commands, ...) to
46
all connected interfaces. These functions do not need a return value,
47
but in some cases the sender might want to know if anyone has received
48
his message. Thus a boolean return value should indicate if the message
49
was handled or ignored.
51
- query for information on connected interfaces / answer queries. These
52
functions usually have a return value. A query is only executed on the
53
"current" or - if not selected - the first or only connection.
55
/////////////////////////////////////////////////////////////////////////////
57
Why are we not using QT signal/slots?
59
First the idea of using qt for connecting interfaces is very nice, as the
60
signal/slot model is well known and hopefully properly implemented.
62
But there are some problems:
64
- Signals/slots do not support return values, except "call by reference".
65
To provide queries or a delivery feedback for messages, wrapper functions
66
would have been necessary.
68
- Qt does not support multiple inheritance of QObjects. Thus even signals
69
have to be declared abstract by the interface though the (later)
70
implementation is already known.
72
Those functions have to be declared as signals in the interface
73
implementation (derived from QObject) though the implementation does not
74
want to worry about these signals.
76
- Qt does connect functions (signals/slots) and not interfaces. These
77
functions have to be connected separately. By that it is possible to
78
forget to connect signals/slots of that interfaces.
80
- Aggregation of multiple interface implementations (each one is an QObject)
81
is not possible because qt does not allow multiple inheritance of QObjects
83
/////////////////////////////////////////////////////////////////////////////
85
What about our own solution?
87
Well, it eliminates at least the qt-problems explained above. But first we
88
need a common mechanism to manage interface connections. This functionality
89
can be provided by a common base class "InterfaceBase". It stores all
90
connected interfaces in a list of InterfaceBase pointers, e.g. QPtrList.
92
With this approach we would have some problems:
94
- When calling a function of a connected interface a slow dynamic_cast
95
is necessary to upcast the stored InterfaceBase pointer to the
98
- Multiple inheritance of InterfaceBase must not be virtual. Otherwise
99
interface connection management is mixed between interfaces.
100
(well, virtual inheritance is usually no real issue, but worth a hint;-)
102
To avoid these problems, InterfaceBase is a template with two parameters,
103
thisIF (IF = interface) and cmplIF (complementary IF). With that
104
information the base class for an interface is capable to handle
105
connections with the correct type information. Additionally some pseudo
106
types are declared (thisInterface, cmplInterface, IFList, IFIterator) to
107
make easy-to-use macros for messages and queries possible.
109
/////////////////////////////////////////////////////////////////////////////
111
How do I use it ? - Declarations
113
First you have to declare the two matching interface-classes as unkown
114
classes, because both their names are used in the class declarations.
115
Afterwards you can declare both classes as class derived from
119
class ComplementaryInterface;
121
class Interface : public InterfaceBase<Interface, ComplementaryInterface>
126
class ComplementaryInterface : public InterfaceBase<ComplementaryInterface, Interface>
131
With macro abbreviation:
133
INTERFACE(Interface, ComplementaryInterface)
137
INTERFACE(ComplementaryInterface, Interface)
142
In order to receive/send Messages or query/answer queries we have to declare
147
Declare a virtual constant method with return value "int" and the desired
148
parameters. The return value will indicate how many receivers have handled
151
virtual bool SendingMessages(int any_or_non_param) const;
153
Abbreviation by macros:
155
IF_SENDER( SendingMessages(int any_or_non_param) )
160
Declare an abstract Method with return value "bool", and the desired
161
paramters. The return value indicates wether the message was handled or not:
163
virtual bool ReceivingMessages(int any_or_non_param) = 0;
165
Abbreviation by macros:
167
IF_RECEIVER( ReceivingMessages(int any_or_non_param) )
170
The method has to be implemented by a derived class. The current item of the
171
receivers conntions list is set to the sender.
176
Declare a virtual constant method with the desired return value and
179
virtual int QueryingQueries(int another_param) const;
181
Abbreviation by macros:
183
IF_QUERY( int QueryingQueries(int another_param) )
188
Declare an abstract Method with return value void, and the desired
191
virtual void AnsweringQueries(int another_param) = 0;
193
Abbreviation by macros:
195
IF_ANSWER( AnsweringQueries(int another_param) )
197
The method has to be implemented by a derived class. The current item of the
198
receivers conntions list is set to the sender.
201
At last a note on maxConnections. This member is set on initialization by
202
the constructor and thus can be set in a derived class in it's own
203
constructor. Negative values are interpreted as "unlimited".
206
/////////////////////////////////////////////////////////////////////////////
208
How do I use it ? - Implementations
210
Because we do not have a MOC as Qt does, we have to implement our sending
211
or querying methods by hand. But this minor disadvantage should be
212
considered as less important than the fact, that this implementation is
213
done where it belongs to. Especially because there are easy to use macros
216
int ComplementaryInterface::SendingMessages(int any_or_non_param) const
218
IF_SEND_MESSAGE( ReceivingMessages(any_or_non_param) )
219
// macro includes "return #receivers"
222
int ComplementaryInterface::QueryingQueries(int another_param) const
224
IF_SEND_QUERY( AnsweringQuery(another_param), (int)"default return value" )
230
IF_IMPL_SENDER( ComplementaryInterface::QueryingQueries(int param),
231
AnsweringQueries(param)
234
IF_IMPL_QUERY( int ComplementaryInterface::SendingMessages(int param),
235
ReceivingMessages(param),
236
(int)"default return value"
239
/////////////////////////////////////////////////////////////////////////////
241
How do I use it ? - Disconnect/Connect notifications
244
Usually the virtual methods notifyDisconnect(ed) or notifyConnect(ed)
245
will be called within connect/disconnect methods.
247
As constructors and destructors are not able to call virtual methods
248
of derived classes, there are two possible problems:
250
* Constructors: Calling a connect method in a constructor will not result
251
in a connect notification of any derived class. Thus do not use connect
252
calls in contructors if any derived class hast to receive all
253
connect/disconnect notifications.
255
* Destructors: If connections are still present if the interface destructor
256
is called, it will only call its own empty noticedisconnect method. That
257
shouldn't be a big problem as the derived class is already gone and
258
doesn't have any interest in this notification any more. But it might be
259
possible that the connected object wants to call a function of the just
260
destroyed derived class. That is not possible. Dynamic casts to the
261
derived class will return NULL. Do not try to call methods of this class
262
by use of cached pointers.
266
/////////////////////////////////////////////////////////////////////////////
268
Extending and Aggregating Interfaces
270
Our interfaces must be extended by aggregation. The reason is that
271
otherwise we would have the same problems as with a common base class
272
for connection management. Each interface extensions is an normal
273
interface on its own.
277
class I_AM_FM_Radio : public IRadioBase,
278
public IRadioFrequencyExtension,
279
public IRadioSeekExtension
284
To guarantee, that connection management continues to work, we have to overwrite
285
the connect and disconnect methods:
287
virtual bool I_AM_FM_Radio::connect (Interface *i) {
288
IRadioBase::connect(i);
289
IFrequencyExtension::connect(i);
290
ISeekExtension::connect(i);
293
virtual bool I_AM_FM_Radio::disconnect (Interface *i) {
294
IRadioBase::disconnect(i);
295
IFrequencyExtension::disconnect(i);
296
ISeekExtension::disconnect(i);
302
/////////////////////////////////////////////////////////////////////////////
304
// a polymorphic and *virtual* base class so that we can make use of
305
// dynamic_casts in connect/disconnect and to be able to merge
306
// connect/disconnect methods to one single function in case of multiple
313
virtual ~Interface() {}
315
virtual bool connectI (Interface *) { return false; }
316
virtual bool disconnectI(Interface *) { return false; }
318
// "Interface &"-Versions for convienience, not virtual, only "Interface*"
319
// versions have to / may be overwritten in case of multiple inheritance
320
bool connectI (Interface &i) { return connectI (&i); }
321
bool disconnectI(Interface &i) { return disconnectI (&i); }
324
/////////////////////////////////////////////////////////////////////////////
326
template <class thisIF, class cmplIF>
327
class InterfaceBase : virtual public Interface
330
typedef InterfaceBase<thisIF, cmplIF> thisClass;
331
typedef InterfaceBase<cmplIF, thisIF> cmplClass;
333
// friend class cmplClass; // necessary for connects (to keep number of different connect functions low)
337
typedef thisIF thisInterface;
338
typedef cmplIF cmplInterface;
340
typedef QPtrList<cmplIF> IFList;
341
typedef QPtrListIterator<cmplIF> IFIterator;
343
typedef thisClass BaseClass;
346
InterfaceBase (int maxIConnections = -1);
347
virtual ~InterfaceBase ();
349
// duplicate connects will add no more entries to connection list
350
virtual bool connectI(Interface *i);
351
virtual bool disconnectI(Interface *i);
354
virtual void disconnectAllI();
359
// It might be compfortable to derived Interfaces to get an argument
360
// of the Interface class, but that part of the object might
361
// already be destroyed. Thus it is necessary to evaluate the additional
362
// pointer_valid argument. A null pointer is not transmitted, as the
363
// pointer value might be needed to clean up some references in derived
365
virtual void noticeConnectI (cmplInterface *, bool /*pointer_valid*/) {}
366
virtual void noticeConnectedI (cmplInterface *, bool /*pointer_valid*/) {}
367
virtual void noticeDisconnectI (cmplInterface *, bool /*pointer_valid*/);
368
virtual void noticeDisconnectedI(cmplInterface *, bool /*pointer_valid*/) {}
370
virtual bool isIConnectionFree() const;
371
virtual unsigned connectedI() const { return iConnections.count(); }
373
thisIF *initThisInterfacePointer();
374
thisIF *getThisInterfacePointer() const { return me; }
375
bool isThisInterfacePointerValid() const { return me_valid; }
376
bool hasConnectionTo(cmplInterface *other) const { return iConnections.containsRef(other); }
377
void appendConnectionTo(cmplInterface *other) { iConnections.append(other); }
378
void removeConnectionTo(cmplInterface *other) { iConnections.removeRef(other); }
385
// functions for individually selectable callbacks
387
bool addListener (const cmplInterface *i, QPtrList<cmplInterface> &list);
388
void removeListener(const cmplInterface *i, QPtrList<cmplInterface> &list);
389
void removeListener(const cmplInterface *i);
391
QMap<const cmplInterface *, QPtrList<QPtrList<cmplInterface> > > m_FineListeners;
399
// macros for interface declaration
401
#define INTERFACE(IF, cmplIF) \
404
class IF : public InterfaceBase<IF, cmplIF> \
406
#define IF_CON_DESTRUCTOR(IF, n) \
407
IF() : BaseClass((n)) {} \
410
// macros to make sending messages or queries easier
417
#define IF_QUERY_DEBUG \
418
if (iConnections.count() > 1) { \
419
kdDebug() << "class " << typeid(this).name() << ": using IF_QUERY with #connections > 1\n"; \
422
#define IF_QUERY_DEBUG
429
#define SENDERS protected
430
#define RECEIVERS public
432
#define IF_SENDER(decl) \
433
virtual int decl const;
435
#define IF_SEND_MESSAGE(call) \
437
for (IFIterator i(iConnections); i.current(); ++i) { \
438
if (i.current()->call ) ++____n; \
442
#define IF_IMPL_SENDER(decl, call) \
445
IF_SEND_MESSAGE(call) \
448
#define IF_RECEIVER(decl) \
449
virtual bool decl = 0;
451
#define IF_RECEIVER_EMPTY(decl) \
452
virtual bool decl { return false; }
456
#define ANSWERS public
457
#define QUERIES protected
459
#define IF_QUERY(decl) \
462
#define IF_SEND_QUERY(call, default) \
463
cmplInterface *o = IFIterator(iConnections).current(); \
471
#define IF_IMPL_QUERY(decl, call, default) \
473
IF_SEND_QUERY(call, default) \
476
#define IF_ANSWER(decl) \
482
/////////////////////////////////////////////////////////////////////////////
483
// MACROS for individually selectable callbacks
484
/////////////////////////////////////////////////////////////////////////////
487
#define IF_SENDER_FINE(name, param) \
489
int name param const; \
491
bool register4_##name (cmplInterface *); \
492
void unregister4_##name(cmplInterface *); \
494
QPtrList<cmplInterface> m_Listeners_##name;\
497
#define IF_SEND_MESSAGE_FINE(name, params, call) \
499
for (QPtrListIterator<cmplInterface> ____it(m_Listeners_##name); ____it.current(); ++____it) { \
500
if (____it.current()->call ) ++____n; \
504
#define IF_IMPL_SENDER_FINE(class, name, param, call) \
505
int class::name param const { \
506
IF_SEND_MESSAGE_FINE(name, param, call) \
509
bool class::register4_##name(cmplInterface *i) { \
510
return addListener(i, m_Listeners_##name); \
512
void class::unregister4_##name(cmplInterface *i) { \
513
m_Listeners_##name.remove(i); \
517
/////////////////////////////////////////////////////////////////////////////
520
template <class thisIF, class cmplIF>
521
InterfaceBase<thisIF, cmplIF>::InterfaceBase(int _maxIConnections)
522
: maxIConnections(_maxIConnections),
529
template <class thisIF, class cmplIF>
530
InterfaceBase<thisIF, cmplIF>::~InterfaceBase()
533
// In this state the derived interfaces may already be destroyed
534
// so that dereferencing cached upcasted me-pointers in noticeDisconnect(ed)
536
// Thus we must ensure that disconnectAll() is called in the (upper) thisIF
537
// destructor, not here (see macro IF_CON_DESTRUCTOR).
538
// If this has not taken place (i.e. the programmer forgot to do so)
539
// we can only warn, clear our list now and hope that nothing
540
// more bad will happen
542
if (iConnections.count() > 0) {
543
thisClass::disconnectAllI();
548
template <class thisIF, class cmplIF>
549
bool InterfaceBase<thisIF, cmplIF>::isIConnectionFree () const
551
int m = maxIConnections;
552
return (m < 0) || (iConnections.count() < (unsigned) m);
555
template <class thisIF, class cmplIF>
556
thisIF *InterfaceBase<thisIF, cmplIF>::initThisInterfacePointer()
558
if (!me) me = dynamic_cast<thisIF*>(this);
559
me_valid = me != NULL;
563
template <class thisIF, class cmplIF>
564
bool InterfaceBase<thisIF, cmplIF>::connectI (Interface *__i)
566
// cache upcasted pointer, especially important for disconnects
567
// where already destructed derived parts cannot be reached with dynamic casts
568
initThisInterfacePointer();
570
// same with the other interface
571
cmplClass *_i = dynamic_cast<cmplClass*>(__i);
576
cmplIF *i = _i->initThisInterfacePointer();
579
bool i_connected = iConnections.containsRef(i);
580
bool me_connected = i->hasConnectionTo(me);
582
if (i_connected || me_connected) {
584
} else if (isIConnectionFree() && i->isIConnectionFree()) {
586
noticeConnectI(i, i != NULL);
587
_i->noticeConnectI(me, me != NULL);
590
appendConnectionTo(i);
592
_i->appendConnectionTo(me);
594
noticeConnectedI(i, i != NULL);
595
_i->noticeConnectedI(me, me != NULL);
607
template <class thisIF, class cmplIF>
608
bool InterfaceBase<thisIF, cmplIF>::disconnectI (Interface *__i)
610
cmplClass *_i = dynamic_cast<cmplClass*>(__i);
612
// use cache to find pointer in connections list
613
cmplIF *i = _i ? _i->getThisInterfacePointer() : NULL;
615
// The cached me pointer might already point to an destroyed
616
// object. We must use it only for identifying the entry in
621
noticeDisconnectI(i, _i->isThisInterfacePointerValid());
625
if (_i->isThisInterfacePointerValid())
626
_i->noticeDisconnectI(me, me_valid);
629
if (i && hasConnectionTo(i)) {
631
removeConnectionTo(i);
634
if (me && i && i->hasConnectionTo(me))
635
i->removeConnectionTo(me);
637
if (me_valid && i && _i)
638
noticeDisconnectedI(i, _i->isThisInterfacePointerValid());
639
if (_i && _i->isThisInterfacePointerValid() && me)
640
_i->noticeDisconnectedI(me, me_valid);
646
template <class thisIF, class cmplIF>
647
void InterfaceBase<thisIF, cmplIF>::noticeDisconnectI(cmplInterface *i, bool /*pointer_valid*/)
653
template <class thisIF, class cmplIF>
654
void InterfaceBase<thisIF, cmplIF>::disconnectAllI()
656
IFList tmp = iConnections;
657
for (IFIterator it(tmp); it.current(); ++it) {
658
/* Do not call virtual methods if I'm in the contstructor!
659
Actually this should be ensured by the compiler generated
660
code and virtual method tables, but unfortunately some compilers
661
seem to ignore this in some situations.
664
disconnectI(it.current());
666
thisClass::disconnectI(it.current());
673
template <class thisIF, class cmplIF>
674
bool InterfaceBase<thisIF, cmplIF>::addListener(const cmplInterface *i, QPtrList<cmplInterface> &list)
676
if (iConnections.containsRef(i) && !list.contains(i)) {
678
m_FineListeners[i].append(&list);
686
template <class thisIF, class cmplIF>
687
void InterfaceBase<thisIF, cmplIF>::removeListener(const cmplInterface *i, QPtrList<cmplInterface> &list)
690
if (m_FineListeners.contains(i))
691
m_FineListeners[i].remove(&list);
695
template <class thisIF, class cmplIF>
696
void InterfaceBase<thisIF, cmplIF>::removeListener(const cmplInterface *i)
698
if (m_FineListeners.contains(i)) {
699
QPtrList<QPtrList<cmplInterface> > &list = m_FineListeners[i];
700
QPtrListIterator<QPtrList<cmplInterface> > it(list);
701
for (; it.current(); ++it) {
705
m_FineListeners.remove(i);