summaryrefslogtreecommitdiff
path: root/libopie2/opieui/big-screen/omodalhelper.h
Side-by-side diff
Diffstat (limited to 'libopie2/opieui/big-screen/omodalhelper.h') (more/less context) (ignore whitespace changes)
-rw-r--r--libopie2/opieui/big-screen/omodalhelper.h774
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