tags:

views:

2733

answers:

2

I'm building a Qt client for the open-source client/server 4X strategy game Thousand Parsec. This a Google Summer of Code project. I'm however stuck at a dead end. Basically, the client interfaces with the server via a C++ protocol layer that facilitates client/server communication. The protocol's documentation is available here.

Now my problem is that the protocol requires you to create a subclass of the virtual EventLoop class (link) in your client. There is an example SimpleEventLoop used for console clients on the same link. I'm having difficulty figuring out how I can design my own event loop subclass that handles the protocol's events while hooking into the Qt application at the same time. My research has lead me to believe QAbstractEventDispatcher is the Qt class I want to use but the documentation seems pretty slim and I'm not exactly sure how I would go about doing this.

Does anyone else have experience linking external event loops with a Qt application? I've also found this example on the Qt page but it wasn't very helpful - or at least I didn't really understand it.

Thanks!

+8  A: 

I haven't done too much Qt development recently, but if I remember correctly, you can call QApplication::processEvents() within your own event loop (instead of starting the Qt main loop through QApplication::exec())

Edit: I have used the opportunity of a slow Sunday morning to test-drive / learn something about PyQt (Python bindings for Qt) and cobbled together a proof-of-concept code below. Replacing the call to QApplication::exec() with a custom event loop based on QApplication::processEvents() seems to work.

I have also quickly looked at simpleeventloop.cpp and tpclient-cpptext main.cpp. From the looks of it, it shoud be fine to just add QApplication::processEvents() somewhere in the main loop of SimpleEventLoop::runEventLoop(). To add it to the main loop, I would probably replace the tv interval for the select() function in lines 106 through 117 with

tv.tv_sec = 0;
tv.tv_usec = 10000;   // run processEvents() every 0.01 seconds
app->processEvents();

and change the signature in line 89 to void SimpleEventLoop::runEventLoop(QApplication *app). It should than be fine to add your usual Qt stuff to your implementation of the client (your replacement of tpclient-cpptext main.cpp)

Looks like a hack, though. I would probably start with something like this to get started. I think that your idea of wrapping TPSocket and the timer within Qt's respective concepts in order to forward them with the QAbstractEventDispatcher to the QEventLoop is the better long-term solution. It should then be sufficient that your runEventLoop() simply calls QApplication::exec(). But I have never used QAbstractEventDispatcher before, so take my comments for what they are.

import sys
import time

from PyQt4 import QtGui
from PyQt4 import QtCore

# Global variable used as a quick and dirty way to notify my
# main event loop that the MainWindow has been exited
APP_RUNNING = False

class SampleMainWindow(QtGui.QMainWindow):
    def __init__(self, parent=None):
        QtGui.QMainWindow.__init__(self)
        global APP_RUNNING
        APP_RUNNING = True

        # main window
        self.setGeometry(300, 300, 250, 150)
        self.setWindowTitle('Test')
        self.statusBar().showMessage('Ready')

        # exit action (assumes that the exit icon from
        # http://upload.wikimedia.org/wikipedia/commons/b/bc/Exit.png
        # is saved as Exit.png in the same folder as this file)
        exitAction = QtGui.QAction(QtGui.QIcon('Exit.png')
                                   ,'Exit'
                                   ,self)
        exitAction.setShortcut('Ctrl+Q')
        exitAction.setStatusTip('Exit application')
        self.connect(exitAction
                     ,QtCore.SIGNAL('triggered()')
                     ,QtCore.SLOT('close()'))

        # main menu
        menubar = self.menuBar()
        fileMenu = menubar.addMenu('&File')
        fileMenu.addAction(exitAction)

        # toolbar
        self.toolbar = self.addToolBar('Exit')
        self.toolbar.addAction(exitAction)

        # text editor
        textEdit = QtGui.QTextEdit()
        self.setCentralWidget(textEdit)

        #tool tip
        textEdit.setToolTip('Enter some text')
        QtGui.QToolTip.setFont(QtGui.QFont('English', 12))

    def closeEvent(self, event):
        reply = QtGui.QMessageBox.question(self
                                           ,'Message'
                                           ,"Are you sure?"
                                           ,QtGui.QMessageBox.Yes
                                           ,QtGui.QMessageBox.No)

        if reply == QtGui.QMessageBox.Yes:
            event.accept()
            global APP_RUNNING
            APP_RUNNING = False
        else:
            event.ignore()

# main program
app = QtGui.QApplication(sys.argv)
testWindow = SampleMainWindow()
testWindow.show()
# run custom event loop instead of app.exec_()
while APP_RUNNING:
    app.processEvents()
    # sleep to prevent that my "great" event loop eats 100% cpu
    time.sleep(0.01)
stephan
While my goal is to use the AbstractEventDispatcher to properly integrate the eventloop, I'm just too much of a beginner at Qt to pull it off quick. I'm trying to do this on the side while just getting a working solution in place.I'm trying to hack SimpleEventLoop class so it can return its "running" status which can then be checked from the main app with a while statement as similar to your example. I'll let you know how it works. The thing I'm worried about is if and how the Boost signal callbacks will be handled properly from inside the Qt application.
Gimpyfuzznut
@Gimpyfuzznut: added a sample hack for simpleeventloop.cpp. Your suggested hack is most likely more complicated, because the current implementation of SimpleEventLoop::runEventLoop() implements a standard event loop (see e.g. http://stackoverflow.com/questions/658403/how-would-you-implement-a-basic-event-loop/658495#658495 for an explanation), and therefore never returns unless a signal or timer calls SimpleEventLoop::endEventLoop() as part of the running event loop.
stephan
+3  A: 

I would probably code the event loops to be separate threads. You can handle the events from the library in a class, and have it generate signals which will then be handled by the main Qt eventloop whenever you want (call QApplication::processEvents() if needed in long operations). The only trick to this is making sure that your external event loop is a Q_OBJECT so that it knows how to emit the signals that you care about.

There are other thread issues, such as never (ever) painting in a thread which is not the main QT thread.

jamuraa