Showing posts with label linux. Show all posts
Showing posts with label linux. Show all posts

Wednesday, March 18, 2009

QTimer and no monotonic clock support

I found myself dealing with qt-embedded and QTimers again. There's a board whose software configuration doesn't support monotonic clocks so changing the date back in time causes running QTimer's to cease activity.

I chose to patch qt-embedded 4.5.0. The fix is simple, I just had replace this function in src/corelib/kernel/qeventdispatcher_unix.cpp



void QTimerInfoList::registerTimer(int timerId, int interval, QObject *object)
{
QTimerInfo *t = new QTimerInfo;
t->id = timerId;
t->interval.tv_sec = interval / 1000;
t->interval.tv_usec = (interval % 1000) * 1000;
t->timeout = updateCurrentTime() + t->interval;
t->obj = object;
t->inTimerEvent = false;

timerInsert(t);
}

by this one:



void QTimerInfoList::registerTimer(int timerId, int interval, QObject *object)
{
/** add this 2 lines */
updateCurrentTime();
repairTimersIfNeeded();

QTimerInfo *t = new QTimerInfo;
t->id = timerId;
t->interval.tv_sec = interval / 1000;
t->interval.tv_usec = (interval % 1000) * 1000;
t->timeout = updateCurrentTime() + t->interval;
t->obj = object;
t->inTimerEvent = false;

timerInsert(t);
}

After that qt-embedded should be recompiled (a full recompilation isn't needed, the only file that needs to be compiled again is the one modified, and then a full relink and install; make will do the job).

What happens is that everytime a timer is registered, Qt will update the current time and try to fix the timers if it's needed. This will make newly registered timers work after the date is changed backwards, but old timers won't run properly. I created a class which tracks the QTimers present in the application so that it is able to stop and start them again. This is not the best solution but I had to do a quick fix on this and it works.

Sunday, January 4, 2009

Qt-Embedded and busy-waiting

Qt-embedded is astonishing. I'm currently developing a product which uses embedded linux, color 800x480 LCD and touchscreen. Qt-embedded does really nice, faster than gtk on DirectFB and much better-looking.

However, qt won't show any notification to the user when it is busy, be it refreshing an window, loading a frame, etc. This behaviour is somewhat expected, but on a product I'm developing the user needs to be notified (somehow) that the application is doing alright but is busy, so the [in]famous sand clock jumps in.

Since it's a touchscreen device the mouse cursor was disabled when compiling Qt, and in the end it was better since it looks like only B/W cursors are supported on embedded linux (or at least in this platform?).

I thought of a trick by using QTimer, and later I found out some posts when googling. Here is what I finally did:



#ifndef _busy_notify_h_
#define _busy_notify_h_

#include <QTimer>
#include <QApplication>

/**
* Class to show a bitmap inside a widget when
* the event loop is busy.
*/


class BusyNotify : QObject
{
Q_OBJECT

public:
// Call to initialise an instance
static void init();

BusyNotify();
/**
* Show the busy cursor,
* it will disappear once the event loop is
* done and the timer times out */
void showBusyCursor();

private:
bool waiting;
int screenWidth, screenHeight; //cached

public slots:
void TimerTimeout();
};

// This should be the one used
extern BusyNotify *BusyNotifier;

#endif



#include "BusyNotify.h"

#include <QPixmap>
#include <QPainter>
#include <QDesktopWidget>
#include <QLabel>

BusyNotify *BusyNotifier;

static QPixmap *waitPixmap;
static QTimer *timer;
static QWidget *widget;

void BusyNotify::init()
{
BusyNotifier = new BusyNotify();

//load sand clock image
waitPixmap = new QPixmap( "icons/png/sand_clock.png" );

timer = new QTimer();
timer->setInterval( 100 ); //this could be less..
timer->setSingleShot(false);

Qt::WindowFlags flags = Qt::Dialog;
//flags |= Qt::FramelessWindowHint;
flags |= Qt::CustomizeWindowHint | Qt::WindowTitleHint;
flags |= Qt::WindowStaysOnTopHint;

widget = new QWidget( NULL, flags );
widget->setWindowTitle("Wait...");

QLabel *lbl = new QLabel( widget );
lbl->setText("");
lbl->setPixmap( *waitPixmap );
lbl->setAlignment( Qt::AlignCenter );

widget->resize( waitPixmap->width() + 2, waitPixmap->height() + 2 );
widget->move( screenWidth/2 - widget->width()/2, screenHeight/2 - widget->height()/2 );

connect( timer, SIGNAL(timeout()), BusyNotifier, SLOT(TimerTimeout()) );
}

BusyNotify::BusyNotify() : QObject()
{
waiting = false;
screenWidth = QApplication::desktop()->screenGeometry(0).width();
screenHeight = QApplication::desktop()->screenGeometry(0).height();
}

void BusyNotify::showBusyCursor()
{
if ( !waiting )
{
widget->setVisible(true);

// this will force the widget to show up
// as fast as possible
QApplication::processEvents();

waiting = true;
timer->start();
}
}

void BusyNotify::TimerTimeout()
{
widget->setVisible(false);
timer->stop();
waiting = false;
}


After defining the source and header files above one just needs to call BusyNotify::init() once, at program startup, and then BusyNotifier->showBusyCursor() any time the sand clock is needed. 'showBusyCursor()' comes from an old version based on the mouse busy cursor.

How it works

This class works fine when processing or loading (widgets, etc.) is done in the main loop which is also Qt's event processor. When calling showBusyCursor() Qt will be forced to show the sand clock. Then processing can be done and the QTimer will timeout whenever Qt is able to process events once again (since the 100mseg delay is small enough), so the sand clock will remain on the screen as long as needed. Of course, if processing is done in another thread this won't work, but that's not what it is intended for.