Trolltech Home | Qt-jambi-interest Home | Recent Threads | All Threads | Author | Date
All threads index page 1

Qt-jambi-interest Archive, August 2006
Qt signal/slot model to java objects observer pattern


Message 1 in thread

Hello,

I tried to bring Qt a bit closer to Java's Objects rather than Qt's QObjects
by implementing the observer pattern on top of Qt's signals and slots model
(basically another observer pattern). The goal is to hide the Qt's
"triggered.connect" like methods to an "addListener" method.

How it works:
AbstractAction subclasses QAction and provides two public methods:
addListener and removeListener, that register and unregister objects
implementing the ActionListener interface. Behind the scenes AbstractAction
uses:
    triggered.connect(this, "fireActionTriggered()");
to get notified locally whenever the action gets triggered and then creates
a new Thread which, triggers the registered listeners.

Another method of implementing this would have been to let Qt do all the
triggering by making the addListener method wrap the
    triggered.connect(newListener, "actionTriggered()");
as long as newListener extends ActionListener (say with an instaceof check)
to make sure that newListener actually has an actionTriggered method. The
problem with this approach is that the result is not flexible enough since
triggered.connect requires that newListener extends QObject so there goes
our interface and there is no way of doing something like:
    class ActionHelpAbout extends AbstractAction implements ActionListener
and all the work has to be through anonymous or inner classes.
(Counterpoint: you could argue that there is no point in doing all that
since it's almost the same as doing triggered.connect everywhere so why
bother with addListener)

The first attempt actually works but when using the AboutImageViewer
(borrowed from the ImageViewer sample) I'm getting lots of :
    QFont: It is not safe to use text and fonts outside the gui thread
    QObject::startTimer: timers cannot be started from another thread

So I'm wondering:
Does it have to do with the known issue in Qt 4.1.3 as listed in
KNOWN_ISSUES file: "QObject construction is currently mutex locked..." that
"...has been fixed for Qt 4.2..."
If not so:
Does this mean that the GUI has to be in one Thread only?


Regards,
Fotios Avgeris

Code to reproduce the behaviour:
Compile and run the following code in a single class file named AppTest.java
making sure that Jambi's samples are in the classpath to get the
Ui_AboutImageViewer class. From the menu select "Help->About..." and either
resize the dialog or use the scroll bar or roll over text.

package qtest;

import java.util.Vector;
import com.trolltech.qt.gui.*;
import com.trolltech.demos.imageviewer.Ui_AboutImageViewer;

public class AppTest extends QMainWindow {

 public AppTest() throws Exception {
  QMenuBar qMenuBar = new QMenuBar(this);
  setMenuBar(qMenuBar);

  QMenu qMenuFile = new QMenu("&File", this);
  qMenuBar.addAction(qMenuFile.menuAction());
  qMenuFile.addAction(new ActionFileExit(this));

  QMenu qHelpFile = new QMenu("&Help", this);
  qMenuBar.addAction(qHelpFile.menuAction());
  qHelpFile.addAction(new ActionHelpAbout(this));
 }

 public static void main(String[] args) {
  QApplication.initialize(args);
  AppTest appTest;
  try {
   appTest = new AppTest();
   appTest.show();
   QApplication.exec();
  } catch (Exception e) {
   e.printStackTrace();
  }
 }

}

class ActionHelpAbout extends AbstractAction implements ActionListener {

 public ActionHelpAbout(QWidget parent) {
  super(parent);
  setText("A&bout...");
  addListener(this);
 }

 public void actionTriggered() {
  QDialog d = new QDialog((QWidget)parent());
  Ui_AboutImageViewer ui = new Ui_AboutImageViewer();
  ui.setupUi(d);

  QPalette pal = ui.textEdit.palette();
  pal.setBrush(QPalette.Base, d.palette().window());
  ui.textEdit.setPalette(pal);

  d.exec();
  d.dispose(); // No strictly needed, but it frees up memory faster.
 }

}

class ActionFileExit extends AbstractAction implements ActionListener {

 public ActionFileExit(QWidget parent) {
  super(parent);
  setText("E&xit");
  addListener(this);
 }

 public void actionTriggered() {
  ((QMainWindow)parent()).close();
 }

}

interface ActionListener {
 public abstract void actionTriggered();
}

abstract class AbstractAction extends QAction {

 private transient Vector<ActionListener> listeners;

 public AbstractAction(QWidget parent) {
  super(parent);
  listeners = new Vector<ActionListener>(5);
  triggered.connect(this, "fireActionTriggered()");
 }

 public synchronized void addListener(ActionListener l) {
  if (l == null)
   throw new NullPointerException();
  if (!this.listeners.contains(l))
   listeners.addElement(l);
 }

 public synchronized void removeListener(ActionListener l) {
  listeners.removeElement(l);
 }

 // this could be private and still work but the compiler generates an
 // "... never used locally" warning. Not a problem really...
 protected void fireActionTriggered() {
  if (listeners.size() < 1) return;

  synchronized (this) {
   final Object[] arrLocal = listeners.toArray();

   new Thread(new Runnable() {
    public void run() {
     for (int i = arrLocal.length - 1; i >= 0; i--) {
      ((ActionListener) arrLocal[i])
        .actionTriggered();
     }
    }
   }).start();
  }
 }

// the fireActionTriggered method without creating a Thread
protected void fireActionTriggeredFromCurThread() {
  if (listeners.size() < 1) return;

  Object[] arrLocal = listeners.toArray();
  for (int i = arrLocal.length - 1; i >= 0; i--)
   ((ActionListener) arrLocal[i]).actionTriggered();
 }

}




Message 2 in thread

Hello, Fotios!

Fotios Avgeris wrote:

>The first attempt actually works but when using the AboutImageViewer
>(borrowed from the ImageViewer sample) I'm getting lots of :
>    QFont: It is not safe to use text and fonts outside the gui thread
>    QObject::startTimer: timers cannot be started from another thread
>
>So I'm wondering:
>Does it have to do with the known issue in Qt 4.1.3 as listed in
>KNOWN_ISSUES file: "QObject construction is currently mutex locked..." that
>"...has been fixed for Qt 4.2..."
>If not so:
>Does this mean that the GUI has to be in one Thread only?
>  
>
Yes, this is correct. All GUI processing goes in the main thread of your 
application in Qt Jambi. There's more about Qt and threads on the 
following page:

    http://doc.trolltech.com/4.1/threads.html

Also, as the timer mechanism in Qt Jambi uses the thread of the QObject 
to determine where to post the QTimerEvent when the timer fires, so 
startTimer has to be called from the same thread that the QObject lives in.

More about timers here:

    http://doc.trolltech.com/4.1/timers.html


Hope that helped! :)

-- Eskil


Message 3 in thread

Its generally a bad idea to move an action to a different thread when 
handling it since the user of this action will get surprising results if 
his code gets called in a new thread.


On Thursday 10 August 2006 16:30, Fotios Avgeris wrote:
>  protected void fireActionTriggered() {
>   if (listeners.size() < 1) return;
>
>   synchronized (this) {
>    final Object[] arrLocal = listeners.toArray();
>
>    new Thread(new Runnable() {
>     public void run() {
>      for (int i = arrLocal.length - 1; i >= 0; i--) {
>       ((ActionListener) arrLocal[i])
>         .actionTriggered();
>      }
>     }
>    }).start();
>   }
>  }

-- 
 [ signature omitted ] 

Attachment: pgp8zkeZxOTQp.pgp
Description: PGP signature