Diffstat (limited to 'libopie2/opieui/big-screen/omodalhelper.h') (more/less context) (ignore whitespace changes)
-rw-r--r-- | libopie2/opieui/big-screen/omodalhelper.h | 774 |
1 files changed, 774 insertions, 0 deletions
diff --git a/libopie2/opieui/big-screen/omodalhelper.h b/libopie2/opieui/big-screen/omodalhelper.h new file mode 100644 index 0000000..096cec4 --- a/dev/null +++ b/libopie2/opieui/big-screen/omodalhelper.h @@ -0,0 +1,774 @@ +/* + =. This file is part of the OPIE Project + .=l. Copyright (c) 2003 hOlgAr <zecke@handhelds.org> + .>+-= + _;:, .> :=|. This library is free software; you can +.> <`_, > . <= redistribute it and/or modify it under +:`=1 )Y*s>-.-- : the terms of the GNU Library General Public +.="- .-=="i, .._ License as published by the Free Software + - . .-<_> .<> Foundation; either version 2 of the License, + ._= =} : or (at your option) any later version. + .%`+i> _;_. + .i_,=:_. -<s. This library is distributed in the hope that + + . -:. = it will be useful, but WITHOUT ANY WARRANTY; + : .. .:, . . . without even the implied warranty of + =_ + =;=|` MERCHANTABILITY or FITNESS FOR A + _.=:. : :=>`: PARTICULAR PURPOSE. See the GNU +..}^=.= = ; Library General Public License for more +++= -. .` .: details. + : = ...= . :.=- + -. .:....=;==+<; You should have received a copy of the GNU + -_. . . )=. = Library General Public License along with + -- :-=` this library; see the file COPYING.LIB. + If not, write to the Free Software Foundation, + Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + +*/ + +#ifndef OMODALHELPER_H +#define OMODALHELPER_H + +/* QT*/ +#include <qdialog.h> +#include <qwidget.h> +#include <qvaluelist.h> +#include <qmap.h> +#include <qvariant.h> + +typedef int TransactionID; + +class QDialog; + +namespace Opie +{ + +class OModalHelperControler; +class OModalHelperSignal; + +struct OModalHelperBase +{ + virtual void done( int status, TransactionID ) = 0; + virtual void next( TransactionID ) = 0; + virtual void prev( TransactionID ) = 0; +}; + +/** + * Modality sucks! ;) But it is easy to work with + * do exec() on a dialog and you know everything is funky. + * You only need to have one Dialog loaded and so on. + * This class helps you to work like with modality and help + * you to keep things in sync + * It's a template class but it sends signals once one Item is ready + * the signals contains the status and id of the item and then you + * need fetch it. + * Handled Records will stay available until the first call to retrieve + * either the record via the TransactionID or via the QValueList<Record>. Note + * that most functions do not take handled records into account. + * Also if you edit an record external you can tell this class and it'll + * call the merge() function of your widget to maybe merge in these changes. + * It also supports multiple modes. Either it can create new dialogs + * for each item or it can queue them depending on your usage. But it is + * so smart that if only one item is shown that the queue bar is not shown + * See the example for simple usage. + * + * @short helps to life without modaility + * @author hOlgAr + * @version 0.01 + */ +template<class Dialog, class Record, typename Id = int> +class OModalHelper : private OModalHelperBase +{ + friend class OModalHelperSignal; + friend class OModalHelperControler; +public: + typedef QValueList<Record> RecordList; + typedef QMap<Id, Record> IdMap; + typedef QMap<TransactionID, Id> TransactionMap; + typedef QMap<QDialog*, TransactionID> DialogMap + enum Mode { Queue, New }; + OModalHelper(enum Mode mode, QObject* parnet ); + + bool handles( Id id)const; + TransactionID transactionID( Id id)const; + + void suspend( bool = true ); + + void cancel(); + void cancel( TransactionID ); + + void connectDone( QObject* rec, const char* slot ); + void connectAccepted( QObject* rec, const char* slot ); + void connectRejected( QObject* rec, const char* slot ); + + TransactionID handle( Id id, const Record& rec = Record() ); + + void edited( Id, int what, const QVariant& data ); + + Record record( TransactionID )const; + RecordList recordsDone()const; +private: + virtual void done( int, TransactionID ); + virtual void next( TransactionID ); + virtual void prev( TransactionID ); + + Record nextRecord( TransactionID &, int & )const; + Record prevRecord( TransactionID &, int & )const; + int pos( TransactionID )const; + Dialog* newDialogRecord( const Record& ); + +private: + OModalHelperDialog *queuedDialog()const; // generate or recycle + OModalHelperDialog *m_dialog; + OModalHelperSignal *m_signal; // our signal + OModalHelperControler *m_controler; + IdMap m_ids; // maps ids (uids) to a record + IdMap m_doneIds; + TransactionMap m_transactions; // activate transactions + TransactionMap m_done; // done and waiting for getting picked + DialogMap m_editing; // only used for New Mode + enum Mode m_mode; // the mode we're in +bool m_disabled :1; +}; + + + +/* ### FIXME use namespace with Qt3 */ + +/* + * A note on flow. The Signal is used for QT Signals when + * a record is done. + * There is either one controler and this controler slot will + * be connected to a dialog signal. + * In Queue we get the next and prev signals and call the Helper. + * this then changes the Record of the dialog and sets the transactionId + * of the controler. + * For the new mode + * + */ + +class OModalHelperSignal : public QObject +{ + Q_OBJECT +public: + OModalHelperSignal(OModalHelperBase* base, QObject* parent); + ~OModalHelperSignal(); + +signals: + done( int status, TransactionID transaction ); + accepted( TransactionID transaction ); + rejected( TransactionID transaction ); + +private: + OModalHelperBase* m_base; +}; + + +class OModalHelperControler : public QObject +{ + Q_OBJECT +public: + OModalHelperControler( OModalHelperBase* , QObject* parent); + virtual TransactionID transactionID()const; + void setTransactionID( TransactionID id ); + QDialog* dialog()const; + +public slots: + virtual void done(int result ); + virtual void next(); + virtual void prev(); +private: + QDialog *m_dia; + TransactionID m_id; + OModalHelperBase *m_base; +} + +struct OModalQueueBar; +class OModalQueuedDialog : public QDialog +{ + Q_OBJECT +public: + OModalQueuedDialog(QDialog *mainWidget); + ~OModalQueuedDialog(); + + QDialog* centerDialog()const; + + void setQueueBarEnabled( bool = true ); + void setRecord( int record, int count ); + +signals: + void next(); + void prev(); + +private: + OModalQueueBar *m_bar; + QDialog *m_center; +}; + + +/* + * Tcpp Template Implementation + */ + +/** + * This is the simple Template c'tor. It takes the mode + * this helper should operate in and the parent object. + * This helper will be deleted when the parent gets deleted + * or you delete it yourself. + * + * @param mode The mode this dialog should be in + * @param parent The parent QObject of this helper. + */ +template<class Dialog, class Record, typename Id> +OModalHelper<Dialog, Record, Id>::OModalHelper( enum Mode mode, QObject* parent ) +{ + m_disabled = false; + m_mode = mode; + m_signal = new OModalHelperSignal( this, parent ); + m_controler = new OModalHelperControler( this, m_signal ); +} + + +/** + * This functions looks for your record and sees if it is + * handled with this helper. Note that done records + * will not be returned. + * + * @return true if the record is currenlty edited otherwise false + * + * @param Id The id which might be handled + */ +template<class Dialog, class Record, typename Id> +bool OModalHelper<Dialog, Record, Id>::handles( Id id )const +{ + if ( m_transactions.isEmpty() ) + return false; + + TransactionMap::ConstIterator it = m_transactions.begin(); + for ( ; it != m_transactions.end(); ++it ) + if ( it.data() == id ) + return true; + + return false; +} + + +/** + * just like handles( Id ) but returns the TransactionId + */ +template<class Dialog, class Record, typename Id> +TransactionID OModalHelper<Dialog, Record, Id>::transactionID( Id id)const +{ + if ( m_transactions.isEmpty() || !m_ids.contains( id ) ) + return 0; + + TransactionMap::ConstIterator it = m_transactions.begin(); + for ( ; it != m_transactions.end(); ++it ) + if ( it.data() == id ) + return it.key(); + + return 0; +} + +/** + * If you're requested to flush your data and you do not want + * to call cancel with this method you can disable and enabled + * all dialogs. + * The state gets saved so if you want to handle a new record the dialog + * will be disabled as well. + * + * @param sus If true setDisabled(TRUE) will be called otherwise FALSE + */ +template<class Dialog, class Record, typename Id> +void OModalHelper<Dialog, Record, Id>::suspend(bool sus) +{ + m_disabled = sus; + if (m_mode == New ) + for (DialogMap::Iterator it = m_editing.begin(); it != m_editing.end(); ++it ) + it.key()->setDisabled( sus ); + else if (m_dialog ) + queuedDialog()->setDisabled( sus ); +} + +/** + * Cancel will cancel all current operations and clear the list + * of done operations as well. + * This also clears all done operations you did not popped + */ +template<class Dialog, class Record, typename Id> +void OModalHelper<Dialog, Record, Id>::cancel() +{ + m_ids.clear(); + m_doneIds.clear(); + m_done.clear(); + m_transactions.clear(); + + /* we also need to remove the QDialogs */ + /* and hide the queue dialog if present */ + if (m_mode == New && !m_editing.isEmpty() ) + { + for (DialogMap::Iterator it = m_editing.begin(); it != m_editing.end(); ++it ) + delete it.key(); + + m_editing.clear(); + } + else if (m_dialog ) + queuedDialog()->setRecord( 0, 0 ); + + m_controler->setTransactionID( 0 ); +} + + +/** + * This cancels editing of the record behind the Transaction Number + * Note that if editing is already done it will also be removed from this list + */ +template<class Dialog, class Record, typename Id> +void OModalHelper::cancel( TransactionID tid ) +{ + /* wrong tid */ + if (!m_transactions.contains( tid ) && !m_done.contains( tid) ) + return; + + if (m_mode == New ) + /* reverse map eek */ + for (DialogMap::Iterator it = m_editing.begin(); it != m_editing.end(); ++it ) + if ( it.data() == tid ) + { + it.key()->hide(); + delete it.key(); + it = m_editing.remove( it ); + break; + } + + /* now remove from the various maps done and currently editing map*/ + if (m_transactions.contains( tid ) ) + m_ids.remove( m_transactions[tid] ); + if (m_done.contains( tid ) ) + m_doneIds.remove( m_done[tid ] ); + m_done.remove( tid ); + m_transactions.remove( tid ); + + next( 0 ); +} + +/** + * Connect to the done Signal. SIGNAL( done(int, TransactionID ) ) + * This signal gets emitted whenever a Record was accepted or rejected + * + * @param rec The object where the slot belongs to + * @param slot The slot which should be called. See the needed parameter above + */ +template<class Dialog, class Record, typename Id> +void OModalHelper<Dialog, Record, Id>::connectDone( QObject* rec, const char* slot ) +{ + QObject::connect(m_signal, SIGNAL(done(int, TransactionID) ), + rec, slot ); +} + +/** + * Connect to the accepted Signal. SIGNAL( accepted(TransactionID ) ) + * This signal gets emitted whenever a Record was accepted + * + * @param rec The object where the slot belongs to + * @param slot The slot which should be called. See the needed parameter above + */ +template<class Dialog, class Record, typename Id> +void OModalHelper<Dialog, Record, Id>::connectAccepted( QObject* rec, const char* slot ) +{ + QObject::connect(m_signal, SIGNAL(accepted(TransactionID) ), + rec, slot ); +} + +/** + * Same as the accepted method but this one gets emitted if the dialog + * got rejected. + * SIGNAL( rejected(TransactionID) ) + * + * @param rec The QObject of the slot + * @param slot The slot make sure the signature is correct + */ +template<class Dialog, class Record, typename Id> +void OModalHelper<Dialog, Record, Id>::connectRejected( QObject* rec, const char* slot ) +{ + QObject::connect(m_signal, SIGNAL(rejected(TransactionID) ), + rec, slot ); +} + +/** + * Tell the helper to handle a record. If the record is currently handled + * it will be made active. + * Already handled record which are waiting getting popped are not taken into account + * Otherwise this helpers make the record editable. + * The record supplied needs to have a valid copy operator and constructor. + * In the case where the record is already present the parameter gets discarded. + * If you want the new record to be taken you need to cancel the Transaction first + * + * @param id The Identification of the Record. For PIM it would uid() + * @param rec The record we want to be edited + * + * @returns This functions returns the TransactionId assigned to the record + * + */ +template<class Dialog, class Record, typename Id> +TransactionID OModalHelper<Dialog, Record, Id>::handle( Id id, const Record& rec ) +{ + static TransactionID t_id = 0; + /* + *this method consists out of two parts divided each into New and Queued Mode. + * Either we have the dialog already, in this case we need to highlight the widget + * Or we need to add it. + */ + TransactionID tid = 0; + /* we already have the record lets see if it was done or not */ + if ( !(tid = transactionID( id ) ) ) + { + if (m_mode == New ) + { + /* lets find the dialog and show it need to reverse map*/ + for (DialogMap::Iterator it = m_editing.begin(); it != m_editing.end(); ++it ) + if ( it.data() == tid ) + it.key()->show(); + } + else if (m_controler->transactionID() != tid ) + { + int po = pos( tid ); + m_controler->setTransactionID( tid ); + static_cast<Dialog*>( queuedDialog()->centerDialog() )->setRecord( m_ids[ m_transactions[tid] ] ); + queuedDialog()->setRecord( po, m_transactions.count() ); + } + } + else + { + tid = ++t_id; + m_transactions.insert( tid, id ); + m_ids.insert( id, rec ); + + if (m_mode == New ) + m_editing.insert( newDialogRecord( rec ), tid ); + else + { + m_controler->setTransactionID( tid ); + static_cast<Dialog*>( queuedDialog()->centerDialog() )->setRecord( rec ); + queuedDialog()->setRecord( m_transactions.count(), m_transactions.count() ); + } + } + return tid; +} + +/** + * The goal of this helper is to help you to create non blocking + * GUIs. In the example of the todolist you can have the edit dialog + * but still change the priority or completion inline even if you currently + * edit the record. + * Your Dialog needs to have a Method setData(int,const QVariant& ) which will be called + * in these cases. + * If you edit anything while a record is edited call this function to merge the + * change in. Note if the record is not handled here we will ignore the request + * + */ +template<class Dialog, class Record, typename Id> +void OModalHelper<Dialog, Record, Id>::edited( Id id, int what, const QVariant& data ) +{ + int tid; + if (!( tid = transactionID( id ) ) ) + return; + + if (m_mode == New ) + { + for (DialogMap::Iterator it= m_editing.begin(); it != m_editing.end(); ++it ) + if ( it.data() == tid ) + it.key()->setData( what, data ); + } + else + { + int po = pos( tid ); + Dialog* dia = static_cast<Dialog*>( queuedDialog()->centerDialog() ); + dia->setRecord( m_ids[id] ); + dia->setData( what, data ); + queuedDialog()->setRecord( pos, m_transactions.count() ); + } +} + +/** + * This functions either returns the unedited record the done record + * or a new empty Record using Record(). + * If a done record is retrieved all traces are removed inside this class. This + * is what was called popping a record. This means when you call this function + * with the same TransactionID an Empty record is retrieved. + * + */ +template<class Dialog, class Record, typename Id> +Record OModalHelper<Dialog, Record, Id>::record( TransactionID tid)const +{ + if (m_transactions.contains( tid ) ) + return m_ids[ m_transactions[tid] ]; + else if (m_done.contains( tid ) ) + { + Record rec = m_doneIds[ m_done[ tid] ]; + m_doneIds.remove( m_done[ tid ] ); + m_done.remove( tid ); + return rec; + } + else + return Record(); +} + +/** + * Returns all done Records and removes all references to them internally. A 2nd call to this will + * only contain done record that where edited past the point + */ +template<class Dialog, class Record, typename Id> +OModalHelper<Dialog,Record,Id>::RecordList OModalHelper<Dialog, Record, Id>::recordsDone()const +{ + RecordList list; + + for (IdMap::ConstIterator it = m_doneIds.begin(); it != m_doneIds.end(); ++it ) + list.append( it.data() ); + + /* clean up */ + m_done.clear(); + m_doneIds.clear(); + + return list; +} + + +/** + * @internal + */ +template<class Dialog, class Record, typename Id> +void OModalHelper<Dialog, Record, Id>::done( int status, TransactionID tid) +{ + /* If we're in New mode the transaction Id does not count */ + Record rec; + + if (m_mode == New ) + { + Dialog *dia = static_cast<Dialog*>( m_controler->dialog() ); + m_controler->setTransactionID( 0 ); // set the internal dialog to 0l again + tid = m_editing[ dia ]; + m_editing.remove( dia ); + rec = dia->record(); + delete dia; + } + else + rec = queuedDialog()->record(); + + Id id = m_transactions[ tid ]; + if (result == QDialog::Accept ) + { + m_doneIds.insert( is, rec ); + m_done.insert( tid, id ); + } + + m_transactions.remove( tid ); + m_ids.remove( id ); + + + if (status == QDialog::Accept ) + emit m_signal->accepted( tid ); + else + emit m_signal->rejected( tid ); + + emit m_signal->done( result, tid ); + + next( 0 ); +} + +/** + * @internal + */ +template<class Dialog, class Record, typename Id> +void OModalHelper<Dialog, Record, Id>::next( TransactionID tid) +{ + if (m_mode == New ) + return; + + if (! (m_transactions.count() ) ) + { + m_controler->setTransactionID( 0 ); + queuedDialog()->setRecord( 0, 0 ); + return; + } + + int next; + Record rec; + + /* save the maybe edited record before switching */ + Dialog *dia = static_cast<Dialog*>( queuedDialog()->centerDialog() ); + rec = dia->record(); + m_ids.replace( m_transactions[tid], rec ); + + rec = nextRecord( tid, next ); + queuedDialog()->setRecord( next, m_transactions.count() ); + dia->setRecord( rec ); + + m_controler->setTransactionID( tid ); // was changed during the next call +} + +/** + * @internal + */ +/* + * code duplication should create a template fcuntion + * which takes a pointer to a function ( next, prev ) function + */ +template<class Dialog, class Record, typename Id> +void OModalHelper<Dialog, Record, Id>::prev( TransactionID tid ) +{ + if (m_mode == New ) + return; + + if (! (m_transactions.count()) ) + { + m_controler->setTransactionID( 0 ); + queuedDialog()->setRecord( 0, 0 ); + return; + } + + int prev; + Record rec; + + /* save the maybe edited record before switching */ + Dialog *dia = static_cast<Dialog*>( queuedDialog()->centerDialog() ); + rec = dia->record(); + m_ids.replace( m_transactions[tid], rec ); + + rec = prevRecord( tid, prev ); + queuedDialog()->setRecord( prev, m_transactions.count() ); + dia->setRecord( rec ); + + m_controler->setTransactionID( tid ); // was changed during the next call +} + +/** + * @internal + */ +template<class Dialog, class Record, typename Id> +Record OModalHelper<Dialog, Record, Id>::nextRecord( TransactionID &tid, int &po ) +{ + /* if tid is == 0 we will take the first one */ + /* pos starts at 1 here */ + /* we know we're only called if there are records */ + Record rec; + TransactionMap::Iterator it; + if (!tid ) + { + po = 1; + TransactionMap::Iterator it = m_transactions.begin(); + } + else + { + po = pos( tid ); + /* if it is the last take the first as next */ + if ( po == m_transactions.count() ) + { + po = 1; + it = m_transactions.begin(); + } + else + { + /* we know we're not the last and there is one after us */ + it = m_transactions.find( tid ); + ++it; ++po; + } + } + + tid = it.key(); + rec = m_ids[ tid ]; + return rec; +} + +/** + * @internal + */ +template<class Dialog, class Record, typename Id> +Record OModalHelper<Dialog, Record, Id>::prevRecord( TransactionID& tid, int& pos ) +{ + /* if tid is == 0 we will take the first one */ + /* pos starts at 1 here */ + /* we know we're only called if there are records */ + Record rec; + TransactionMap::Iterator it; + if (!tid ) + { + po = 1; + TransactionMap::Iterator it = m_transactions.begin(); + } + else + { + po = pos( tid ); + /* if it is the last take the first as next */ + if ( po == 1 ) + { + po = m_transactions.count(); + it = m_transactions.end(); + --it; + } + else + { + /* we know we're not the first and there is one before us */ + it = m_transactions.find( tid ); + --it; --po; + } + } + + tid = it.key(); + rec = m_ids[ tid ]; + return rec; +} + +/** + * @internal + */ +template<class Dialog, class Record, typename Id> +int OModalHelper<Dialog, Record, Id>::pos( TransactionID id)const +{ + int i = 1; + for ( TransactionMap::ConstIterator it = m_transactions.begin(); it != m_transactions.end(); ++it, i++ ) + if ( it.key() == id ) + return i; + + + return 0; +} + +/** + * @internal + */ +template<class Dialog, class Record, typename Id> +Dialog* OModalHelper<Dialog, Record, Id>::newDialogRecord( const Record& rec ) +{ + Dialog* dia = new Dialog; + dia->setRecord( rec ); + dia->setDisabled( m_disabled ); + + QObject::connect(dia, SIGNAL(done(int) ), + m_controler, SLOT(done(int) ) ); + + /* FIXME big screen QPEApplication needs fixed*/ + dia->show(); +} + +template<class Record, class Dialog, typename Id> +OModalHelperDialog* OModalHelper<Record, Dialog, Id>::queuedDialog()const +{ + if (!m_dialog ) + { + m_dialog = new OModalHelperDialog; + m_dialog->setEnabled( m_disabled ); + + QObject::connect(m_dialog, SIGNAL(done(int) ), + m_controler, SLOT(done(int) ) ); + QObject::connect(m_dialog, SIGNAL(next() ), + m_controler, SLOT(next() ) ); + QObject::connect(m_dialog, SIGNAL(prev() ), + m_controler, SLOT(prev() ) ); + } + return m_dialog; +} + +}; + +#endif |