A context menu may be extremely helpfull for the user. On standard desktops, the contextmenu is mostly reached by pressing the right mouse button. A menu pops up, which allows the specially arranged (context) actions.
As a PDA does not have "real" mouse funtionality, the right mouse button has to be emulated. We do that by press and hold the stylus on the touch screen.
This section describes the necessary program code to get that working. We use the CheckList sources as an example at the moment.
This section shows the example code to build up the menu. Here it is done fixed inside the class constructor.
The popup menu is created containing the menu texts and the connected slot. Beside that a timer is defined and connected to the slot, which pops up the menu.
#include <qpopup.h>
CheckTreeEdit::CheckTreeEdit(QWidget *parent, QCheckData *d) :
TreeDlg(parent), treeUtil(), ScQtUtil()
{
...
actionPopup = new QPopupMenu;
actionPopup->insertItem("&Append", this, SLOT(slotNew()) );
actionPopup->insertItem("&Change", this, SLOT(slotChange()) );
actionPopup->insertItem("&Show", this, SLOT(slotShow()) );
actionPopup->insertItem("&Delete", this, SLOT(slotDelete()) );
actionPopup->hide();
connect( &menuTimer, SIGNAL(timeout()), SLOT( slotShowActionMenu() ) );
...
}
As soon as the mouse is pressed, the menuTimer is started (sindle shot). Here we use 500 msecs for the timeout, which is long enough to differ a "click" and a "hold".
Note that the slot used here is connected in the dialog class TreeDlg, which is created using QT-Designer.
void CheckTreeEdit::slotMouseButtonPressed( int button, QListViewItem *qlv, const QPoint &a, int col )
{
...
if (( col == NAMECOL )&&( button == LeftButton ))
menuTimer.start( 500, true );
...
}
If the user wanted to "click" instead of hold, the program has to stop the timer (if running) to avoid the popup. This is managed by the signal clicked() here. As soon, as this signal occurs, the menutimer is stopped by calling slotCancelMenuTimer();
void CheckTreeEdit::slotClicked( QListViewItem *qlv, const QPoint &a, int col )
{
...
slotCancelMenuTimer();
...
}
void CheckTreeEdit::slotCancelMenuTimer( )
{
if ( menuTimer.isActive() )
menuTimer.stop( );
}
This section describes the usage of the central opie alarmserver. This utility allows the system to wake up the handheld and start the program, which defined the related alarm.
This works using a QCop message (only available on NON-X systems). The Program has to register a so called QCopChannel at the opie system. Once registered, it may add / delete alarms using QDatiTime format. If an alarm occurs, the kernel wakes the handheld up (if necessary), opie starts the related program and send a QCop message, which results in a QT-Signal inside the program.
This signal has to be connected to a slot, which controls the rest.
The following example is part of my project "qchecklist". The code examples are stripped down to its explanation needs.
The first part is to register your program. This should be done inside the MainWindow constructor. If compiled with Q_WS_QWS and !QT_NO_COP, the codelines inside the constructor are compiled.
The first one creates a QCop channel whith the parameter "QPE/Application/qchecklist".
The part "QPE/Application/" is mandatory, while the last part of the string is normally
yopur programs name. This info is used to start your appliation later. The system recognizes,
that an application called qchecklist should be started, when an alarm was set
for the application (using the same string).
Beside that you have to connect a SIGNAL coming from QPEApplication to a self defined
SLOT using exactly the described parameters.
Heres the example code.
#ifdef QWS_OPIE
#include <qcopchannel_qws.h>
#endif
QCheckApp::QCheckApp( QWidget *parent, const char *name, WFlags f )
: QMainWindow( parent, name, f ), uvFileIconProvider (), uvToolIconProvider()
{
...
#if defined(Q_WS_QWS)
#if !defined(QT_NO_COP)
QCopChannel *m_channel = new QCopChannel( "QPE/Application/qchecklist", this );
connect( qApp, SIGNAL(appMessage(const QCString&, const QByteArray&)), this,
SLOT(slotAppMessage(const QCString&, const QByteArray&)) );
#endif
#endif
...
}
Dont forget to delete the QCopChannel when deleting your application.
QCheckApp::~QCheckApp()
{
#if defined(Q_WS_QWS)
#if !defined(QT_NO_COP)
delete ( m_channel );
#endif
#endif
}
To add / delete an alarm, you can use the static method AlarmServer::addAlarm and AlarmServer::deleteAlarm. Note that an old (expired) alarm will automatically be deleted from the alarmserver list, but a change in timing will have the effect, that your program maybe awoken twice.
For this always delete the old Alarm, when the data was changed inside the dialog.
The example method does it storing the old QDateTime in a temp variable before calling the dialog. If the dialog returns with the alarmflag off, for safety reasons, the corresponding alarm is deleted ( is maybe an old entry and the alarm was just switched off). If the alarmflag is on, and the actual elements alarmtime is different from the temporary stored one, the potentially old alarm is deleted and the new one added.
The method addAlarm (and as well deleteAlarm) gets 4 parameters.
The method deleteAlarm uses tha parameters to search the alarm to be deleted. All parameters have to be equal, if the alarm should be deleted.
Heres the example code.
#ifdef QWS_OPIE
#include <qpe/alarmserver.h>
#endif
void QCheckView::slotCheckChange()
{
...
tdattim = ul->alarmtime;
#ifdef QWS_OPIE
QPEApplication::execDialog( chkEdit );
#else
chkEdit->exec();
#endif
chkEdit->hide( );
if ( chkEdit->result() == QDialog::Accepted )
{
#if defined(Q_WS_QWS)
#if !defined(QT_NO_COP)
if ( ul->alarmflag )
{
if ( ul->alarmtime != tdattim )
{
AlarmServer::deleteAlarm( tdattim, "QPE/Application/qchecklist", "checkAlarm(QDateTime,int)", 0 );
AlarmServer::addAlarm( ul->alarmtime, "QPE/Application/qchecklist", "checkAlarm(QDateTime,int)", 0 );
}
}
else
{
AlarmServer::deleteAlarm( ul->alarmtime, "QPE/Application/qchecklist", "checkAlarm(QDateTime,int)", 0 );
}
#endif
#endif
...
}
}
The slot called by alarm has two Parameters filled with different data.
The first one returns the message sting, that was used during addAlarm. The second one returns the data related. In case of "QDateTime,int", that is always the QDateTime stamp which fired the alarm and the integer value defined during addAlarm (4th parameter).
Inside the slot you should check, whether the message was the intended one by comparing the msg transfered with the message string used during the addAlarm call. If correct, you can read out the transfered data using the stream method show in the example.
This slot also checks, if the view widget is available and load all data (grantLoad()) before
showing the element, which defined the alarm.
Heres the example code.
void QCheckApp::slotAppMessage( const QCString& msg, const QByteArray& data )
{
QDataStream stream( data, IO_ReadOnly );
if ( msg == "checkAlarm(QDateTime,int)" )
{
QDateTime qdt;
int a;
stream >> qdt >> a;
if ( view )
{
grantLoad(); // Grants, that data is loaded when shown
view->showCheck( qdt, a );
}
}
}
Once woken up, the program wants to show the correct data (which caused the wakeup) on top. For this the example implements a method, which searches the correct entry out of its data list using the QDateTime stamp returned by the alarmserver.
In my case, if found an entry, tha slot slotCheckChange() is called, which will exec the change dialog containing the data of the found list element.
Heres the example code.
void QCheckView::showCheck( const QDateTime &dt, int a )
{
QCheckDataCheck *ul;
ul = qdata->getEntryByDateTime( qdata->checkList, dt );
if ( !ul )
return;
slotCheckChange();
}
You will find out, that your application is started, but terminates immediately, if the top dialog is closed. The reason for this are programs, that should do it that way, when the message is placed.
To avoid that insert the static method QPEApplication::setKeepRunning() can be called either in the main function or the MainWidget.
Heres the example code.
#ifdef QWS_OPIE
#include <qpe/qpeapplication.h>
#endif
int main(int argc, char *argv[])
{
...
#ifdef QWS_OPIE
QPEApplication::setKeepRunning();
#endif
...
}
The syslog facility can be used to debug a running opie program not started from the console,
if the program uses the syslog facility (see man syslog for syslog details).
Normally the syslog daemon on the handheld is started with that parameter -C, which causes
the daemon to write the messages into a shared memory region (maybe accessible via special programs). If
you want the daemon to write to /var/log/messages (the "normal" behaviour), you have to modify
its start script /etc/init.d/syslog and restart the syslog daemon calling
/etc/init.d/syslog stop; /etc/init.d/syslog stop.
The next paragraph shows the syslog scripts header with the line SYSLOG_ARGS="-C" active and
SYSLOG_ARGS="" inactive. Uncomment the inactive one and comment the active one to get the
messages file written.
#! /bin/sh
#
# syslog init.d script for busybox syslogd/klogd
# Written by Robert Griebl <sandman@handhelds.org>
#
# log to remote host
# SYSLOG_ARGS="-R remote.host"
# log to 16K shm circular buffer
SYSLOG_ARGS="-C"
# log to /var/log/messages
# SYSLOG_ARGS=""