4.4.5. UI Implementation (cut_rad_hello/src/cut_rad_hello/applicationwindow.py)
4.4.5.1. Imports
Lines 3 and 10 change. We have added QProcess to handle the start of the hellocii application, and QEventLoop for special circumstance on closing of the application.
On line 10 we add the StdCmdsFacade
class we have just programmed.
1import time
2
3from PySide2.QtCore import QObject, QMetaObject, Slot, Signal, SIGNAL, SLOT, QTimer, QProcess, QEventLoop
4from PySide2.QtWidgets import QMainWindow
5from PySide2.QtGui import QWhatsThis
6
7from elt.cut.task import Task
8from taurus.core.util.log import Logger, TraceIt
9from taurus import Manager
10from cut_rad_hello.stdcmdsfacade import StdCmdsFacade
11from cut_rad_hello.mainwindow import Ui_MainWindow # WAF will automatically generated this
4.4.5.2. Initialization
We create a new object called self.server_facade
. This is a an object from the StdCmdsFacade
class.
In order to properly construct it, we need to pass it the URI of the hellocii endpoint.
We have also added a new self.server_process
object from the QProcess class. This will keep track of a
process for us while offering Signal/Slots in order to do so.
1def __init__(self):
2 QMainWindow.__init__(self)
3 Logger.__init__(self,name=qApp.applicationName())
4 # Construction of UI
5 self.ui = Ui_MainWindow()
6 self.ui.setupUi(self)
7 MY_SERVER_URL = qApp.get_command_line_options().host
8 MY_SERVER_PORT = qApp.get_command_line_options().port
9 uri = 'zpb.rr://' + str(MY_SERVER_URL) + ':' +\
10 str(MY_SERVER_PORT) + '/StdCmds'
11 self.server_facade = StdCmdsFacade(uri)
12 self.server_process = QProcess(self)
13 self._init_gui()
In the _init_gui
method we have added a bit of customization to our GUI, and recommended, we put it in this
method. We start the application by setting some default values. First of all, the self.ui.centralwidget
(line
8) is set to be disabled.
This will disable interaction of most of the GUI. Particularly useful when the facade is not connected to the server,
which is the starting case.
We also disable the actionServerExit
QAction, which automatically disables its entry in the Menu. This goes in
the same line of thought as the previous change. You cannot execute an Exit
command on a server you are not yet
connected to.
On line 10, we connect the Signal self.server_facede.connectionChanged(bool)
to Slot
self.on_server_connection_changed(bool)
. This will handle the enabling and disabling of widgets and menu
entries when the connection status is detected to have change.
On line 12 and 13, we connect started and finished Signals to slots we implement that handled again, enabling and disabled menu entries to avoid presenting inconsistent options to the user.
1def _init_gui(self):
2 '''
3 In some cases, widgets are not ready for modification until
4 __init__ is done. This can be workaround by invoking another
5 method at the end of __init__.
6
7 '''
8 self.ui.centralwidget.setEnabled(False)
9 self.ui.actionServerExit.setEnabled(False)
10 QObject.connect( self.server_facade, SIGNAL('connectionChanged(bool)'),
11 self, SLOT('on_server_connection_changed(bool)'))
12 self.server_process.started.connect(self.on_server_process_started)
13 self.server_process.finished.connect(self.on_server_process_finished)
14 QTimer.singleShot(250, lambda: self.on_actionServerConnect_triggered())
15 pass
At the end of this, we use QTimer to automatically call a slot when a timer has ended. This will start after our GUI has been created and shown to the user. We avoid starting the connection while initializing the GUI to present it promptly to the user.
4.4.5.3. closeEvent()
We override the usual behavior defined by Qt library by redefining this method by adding more instructions before
invoking the parent class closeEvent
. This will ensure that the normal behaviour is not lost (otherwise, the
window will not close).
We start a new QEventLoop, and set a timeout of 1 second. At that point, the Event Loop will quit. When closing windows we reach a very particular condition, where we cannot keep a QTimer tied to this class, because it can be destroyed while closing the window. In fact, as usual, while destroying or closing windows, one should avoid the creation of object tied to the lifecycle of the very same object being destroyed.
We need the timer in order to give the on_actionServerDisconnect_triggered
method to perform its duties, but
still having a timeout for the operation. This gives us that.
You can see that we start another Event Loop by the name of the loop.exec_()
method.
1def closeEvent(self, event):
2 '''
3 Not to be confused with actionExit, this method is special. Instead
4 of using signals to know when an application is closed, we can
5 override the closeEvent method from the QApplication class, and
6 determine here what will happen when the window is closed.
7 In this case, we are just forwarding the event to its parent class.
8 This is useful when disconnection from server or prevention of
9 current work needs to be implemented.
10
11 :param event: Event received from Qt event pump
12 :type event: QtCore.QEvent
13
14 '''
15 self.info('Application shutting down...')
16 loop = QEventLoop()
17 self.server_facade.connectionChanged.connect(loop.quit)
18 self.on_actionServerDisconnect_triggered()
19 QTimer.singleShot(1*1000, lambda: loop.quit())
20 self.info('Waiting for Hellocii client to shutdown or timeout of 1 second...')
21 loop.exec_()
22 QMainWindow.closeEvent(self, event)
4.4.5.4. Server (dis)connection Slots
The first and second Slot in this code snippet reach to the Action defined in the UI. The first starts with the
server_facade.setConnection(True)
method call a request to connect to the hellocii server. The
second one does the opposite. Both also take care of enable/disable certain widgets/action of the UI so that erroneous
operations are avoided.
@TraceIt()
@Slot()
def on_actionServerConnect_triggered(self):
'''
Slot that auto-connects to the QAction actionServerConnect.
actionServerConnect is not declared in the code, but in the mainwindow.ui.
See: https://doc.qt.io/qt-5/designer-using-a-ui-file.html#widgets-and-dialogs-with-auto-connect
'''
self.server_facade.setConnection(True)
self.ui.actionServerConnect.setEnabled(False)
self.ui.actionServerDisconnect.setEnabled(True)
self.ui.actionServerExit.setEnabled(True)
@Slot()
def on_actionServerDisconnect_triggered(self):
'''
Slot that auto-connects to the QAction actionServerConnect.
actionServerConnect is not declared in the code, but in the mainwindow.ui.
See: https://doc.qt.io/qt-5/designer-using-a-ui-file.html#widgets-and-dialogs-with-auto-connect
'''
self.server_facade.setConnection(False)
self.ui.actionServerConnect.setEnabled(True)
self.ui.actionServerDisconnect.setEnabled(False)
self.ui.actionServerExit.setEnabled(False)
@TraceIt()
@Slot(bool)
def on_server_connection_changed(self, state):
'''
Slot that auto-connects to the QAction actionServerConnect.
actionServerConnect is not declared in the code, but in the mainwindow.ui.
See: https://doc.qt.io/qt-5/designer-using-a-ui-file.html#widgets-and-dialogs-with-auto-connect
'''
self.ui.centralwidget.setEnabled(state)
self.ui.actionServerConnect.setEnabled(not state)
self.ui.actionServerDisconnect.setEnabled(state)
self.ui.actionServerExit.setEnabled(state)
The third method has been connected during the initialization to the connectionChanged
Signal. This means that
every time the connection state is True (connected) or False (disconnected), this Slot is called with that boolean.
It then proceeds to set different state for Action/Widgets so that erroneous operations are avoided.
4.4.5.5. Slots for methods in the interface
There are 8 Slots that react to buttons being pressed, and one for an Action being triggered. They are quite simple, and
just invoke the appropriate method in the server_facade
object.
1@TraceIt()
2@Slot()
3def on_initPushButton_clicked(self):
4 '''
5 Slot that auto-connects to the QPushButton actionServerConnect.
6 actionServerConnect is not declared in the code, but in the mainwindow.ui.
7 See: https://doc.qt.io/qt-5/designer-using-a-ui-file.html#widgets-and-dialogs-with-auto-connect
8 '''
9 self.server_facade.init()
10
11@TraceIt()
12@Slot()
13def on_resetPushButton_clicked(self):
14 '''
15 Slot that auto-connects to the QPushButton actionServerConnect.
16 actionServerConnect is not declared in the code, but in the mainwindow.ui.
17 See: https://doc.qt.io/qt-5/designer-using-a-ui-file.html#widgets-and-dialogs-with-auto-connect
18 '''
19 self.server_facade.reset()
20
21@TraceIt()
22@Slot()
23def on_enablePushButton_clicked(self):
24 '''
25 Slot that auto-connects to the QPushButton actionServerConnect.
26 actionServerConnect is not declared in the code, but in the mainwindow.ui.
27 See: https://doc.qt.io/qt-5/designer-using-a-ui-file.html#widgets-and-dialogs-with-auto-connect
28 '''
29 self.server_facade.enable()
30
31@TraceIt()
32@Slot()
33def on_disablePushButton_clicked(self):
34 '''
35 Slot that auto-connects to the QPushButton actionServerConnect.
36 actionServerConnect is not declared in the code, but in the mainwindow.ui.
37 See: https://doc.qt.io/qt-5/designer-using-a-ui-file.html#widgets-and-dialogs-with-auto-connect
38 '''
39 self.server_facade.disable()
40 pass
41
42@TraceIt()
43@Slot()
44def on_getStatePushButton_clicked(self):
45 '''
46 Slot that auto-connects to the QPushButton actionServerConnect.
47 actionServerConnect is not declared in the code, but in the mainwindow.ui.
48 See: https://doc.qt.io/qt-5/designer-using-a-ui-file.html#widgets-and-dialogs-with-auto-connect
49 '''
50 self.server_facade.get_state(result_slot=lambda x: self.info("Return: {}".format(x)))
51 pass
52
53@TraceIt()
54@Slot()
55def on_getStatusPushButton_clicked(self):
56 '''
57 Slot that auto-connects to the QPushButton actionServerConnect.
58 actionServerConnect is not declared in the code, but in the mainwindow.ui.
59 See: https://doc.qt.io/qt-5/designer-using-a-ui-file.html#widgets-and-dialogs-with-auto-connect
60 '''
61 self.server_facade.get_status(result_slot=lambda x: self.info("Return: {}".format(x)))
62 pass
63
64@TraceIt()
65@Slot()
66def on_getVersionPushButton_clicked(self):
67 '''
68 Slot that auto-connects to the QPushButton actionServerConnect.
69 actionServerConnect is not declared in the code, but in the mainwindow.ui.
70 See: https://doc.qt.io/qt-5/designer-using-a-ui-file.html#widgets-and-dialogs-with-auto-connect
71 '''
72 self.server_facade.get_version(result_slot=lambda x: self.info("Return: {}".format(x)))
73 pass
74
75@TraceIt()
76@Slot()
77def on_stopPushButton_clicked(self):
78 '''
79 Slot that auto-connects to the QPushButton actionServerConnect.
80 actionServerConnect is not declared in the code, but in the mainwindow.ui.
81 See: https://doc.qt.io/qt-5/designer-using-a-ui-file.html#widgets-and-dialogs-with-auto-connect
82 '''
83 self.server_facade.stop()
84 pass
85
86@TraceIt()
87@Slot()
88def on_actionServerExit_triggered(self):
89 '''
90 Slot that auto-connects to the QAction actionServerConnect.
91 actionServerConnect is not declared in the code, but in the mainwindow.ui.
92 NOTE: This is different from others slots. It uses a QAction instead of a QPushButton
93 See: https://doc.qt.io/qt-5/designer-using-a-ui-file.html#widgets-and-dialogs-with-auto-connect
94 '''
95 self.server_facade.exit()
96 pass
97
98@TraceIt()
99@Slot()
100def on_setLogLevelPushButton_clicked(self):
101 '''
102 Slot that auto-connects to the QPushButton actionServerConnect.
103 actionServerConnect is not declared in the code, but in the mainwindow.ui.
104 See: https://doc.qt.io/qt-5/designer-using-a-ui-file.html#widgets-and-dialogs-with-auto-connect
105 '''
106 self.server_facade.set_log_level(
107 self.ui.levelComboBox.currentText(),
108 self.ui.loggerComboBox.currentText(),
109 result_slot=lambda x: self.info("Return: {}".format(x)))
110 pass
The on_actionServerExit_triggered
is auto-connected to a QAction instead of a QPushButton.
on_setLogLevelPushButton_clicked
is special, as it needs to collect arguments for the method. It is important
to establish a line between UI and domain/middleware. Here is an example where it is drawn. This file tried to concern
itself only with UI elements. We pass the level and logger as strings to the self.server_facade
method.
4.4.5.6. Starting Hellocii process
This are auxiliary methods that will make our like more easy. The first one is an auto-connected slot, that react to a QAction (also located in the Top-Level Menubar Entry ). This will start a process using the QProcess object we created during initialization.
Also during initialization, we have connected two signals from the QProcess object, to the last two Slots. When the process is detected to be started, and when it finishes, the slots will enable Actions so that erroneous operations are avoided.
1@TraceIt()
2@Slot()
3def on_actionServerStart_triggered(self):
4
5 if (self.server_process.state() == QProcess.NotRunning):
6 self.warning("Hellocii will be started up in a xfce4-terminal" )
7 self.server_process.start("xfce4-terminal",["-x","hellocii","-c", "hellocii/config.yaml", "-l", "DEBUG"])
8 else:
9 self.warning("Hellocii already running" )
10
11@TraceIt()
12@Slot()
13def on_server_process_started(self):
14 self.ui.actionServerStart.setEnabled(False)
15 self.ui.actionServerExit.setEnabled(True)
16
17@TraceIt()
18def on_server_process_finished(self, code):
19 self.ui.actionServerStart.setEnabled(True)
20 pass