.. include:: ../icons.inc
.. _dialogs:
Creating dialogs in SMITER
--------------------------
There are prepared classes for writing dialogs in SMITER that may ease the
writing of a dialog. Otherwise to create a dialog simply use the *PyQt5* module
for creating the GUI and provide corresponding functions that are called when
using the dialog.
In this howto we will write a simple dialog that accepts a input field and upon
clicking :guilabel:`Apply` it will open a message box and printing the input
string. We will also use the prepared base classes for writing simple dialogs.
Creating the file
^^^^^^^^^^^^^^^^^
Create a file ``example_dialog.py`` in the ``smiter/SMITER/src/Dialogs``
directory. The naming convention for the files is *Snake Case*.
Writing the code
^^^^^^^^^^^^^^^^
The base code:
.. code-block:: python
from SMITER_dialogs import Dialog
class ExampleDialog(Dialog):
def __init__(self, parent=None):
super(ExampleDialog, self).__init__(parent)
if __name__ == '__main__':
from qtsalome import QApplication
# Check if there already is an instance of QApplication
app = QApplication.instance()
execute = False
if app is None:
# Create an instance of QApplication
execute = True
app = QApplication([])
# Instance the dialog and show it.
d = ExampleDialog()
d.show()
if execute:
# Run only if there was no QApplication instance found.
app.exec_()
At the top we import the ``SMITER_dialogs.Dialog`` class from which our
``ExampleDialog`` will inherit.
The initialize function always takes first argument as a parent (optional) and
immediately initializes the inherited class by calling ``super``, which takes
our dialog class name as argument and a reference to itself. So if you change
the class name to ``SomeDifferentDialog`` the *super* function would be
``super(SomeDifferentDialog, self).__init__(parent)``.
That's it for the dialog and on the bottom we have a conditional code block
used for testing the dialog by running it outside or inside of ``SMITER``. So
far this takes most of the example and should be of no concern to us.
.. _running_dialogs:
Running the code outside of SMITER
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Linux
"""""
In ``Linux`` you can run the dialog, to see if the layout is correct. To do
that the ``SMITER`` environment must be loaded, which is done by **sourcing**
the ``env_launch.sh`` file in ``SMITER`` top directory.
.. code-block:: shell
# Go to SMITER top directory
cd smiter
# Generate the env_launch.sh file
./sat environ smiter
# Load the env_launch.sh
source env_launch.sh
# Run the example_dialog.py
python SMITER/src/Dialogs/example_dialog.py
Windows
"""""""
In ``Windows``, if you wish to test how the Dialog looks, you will have to run
it from SMITER. Run SMITER and then in the python console write
.. code-block:: python
exec(open('C:\path\to\smiter\SMITER\src\Dialogs\example_dialog.py').read())
Result
""""""
The following dialog should pop up.
.. image:: images/dialogs_1.*
:align: center
We can click on :guilabel:`Apply`, :guilabel:`Cancel`, :guilabel:`Help`, and
only the :guilabel:`Cancel` will actually do something, that is, closing the
dialog.
Populating the code further
^^^^^^^^^^^^^^^^^^^^^^^^^^^
Now we change the dialogs name to ``Example dialog``. After the line with the
``super`` statement, we will use ``self.setWindowTitle('Example dialog')``.
.. code-block:: python
def __init__(self, parent=None):
super(ExampleDialog, self).__init__(parent)
self.setWindowTitle('Example dialog')
Next step we will add an input field. For this we will use a method
called ``addSpecialWidgets`` to our ``ExampleDialog`` class.
.. code-block:: python
def addSpecialWidgets(self):
self.inputField = self.addLineEdit('Please input:')
If we rerun the dialog, we will get
.. image:: images/dialogs_2.*
:align: center
Next step is to process what has been given to the dialog. For this purpose,
adding parameters to the dialog and clicking :guilabel:`Apply`, we use the
method ``onAppl``.
.. code-block:: python
def onApply(self):
# Using the variable self.inputField we extract the text.
text = self.inputField.text()
# Now we will display the message with the use of
# ``self.displayMessage``, which takes a title and text for its
# arguments.
self.displayMessage('Message!', text)
Now if we rerun the dialog and write something to the input field and click on
:guilabel:`Apply`, we should get
.. image:: images/dialogs_3.*
:align: center
Resulting code
^^^^^^^^^^^^^^
.. code-block:: python
from SMITER_dialogs import Dialog
class ExampleDialog(Dialog):
def __init__(self, parent=None):
super(ExampleDialog, self).__init__(parent)
self.setWindowTitle('Example dialog')
def addSpecialWidgets(self):
self.inputField = self.addLineEdit('Please input:')
def onApply(self):
# Using the variable self.inputField we extract the text.
text = self.inputField.text()
# Now we will display the message with the use of
# ``self.displayMessage``, which takes a title and text for its
# arguments.
self.displayMessage('Message!', text)
if __name__ == '__main__':
from qtsalome import QApplication
# Check if there already is an instance of QApplication
app = QApplication.instance()
execute = False
if app is None:
# Create an instance of QApplication
execute = True
app = QApplication([])
# Instance the dialog and show it.
d = ExampleDialog()
d.show()
if execute:
# Run only if there was no QApplication instance found.
app.exec_()
.. note::
For more information on the base dialog check :ref:`base_dialog`
Writing more specific dialogs
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
If the base dialog does not provide enough functionality, i.e. you wish
multiple layouts, showing images and so on, we can of course write our own
dialogs or extend the base class.
Minimum requirements for a dialog
"""""""""""""""""""""""""""""""""
The dialog must be written as a python class that inherits the ``QDialog`` or
any other derivites of the ``QDialog`` class. It must contains the standard
buttons box, for example :guilabel:`Apply`, :guilabel:`Cancel` and
:guilabel:`Help`. The dialog must contain a function for each of the before
mentioned buttons.
An example of such a dialog:
.. code-block:: python
from qtsalome import (QDialog, QGridLayout, QHBoxLayout, QVBoxLayout,
QFormLayout, QDialogButtonBox, QLineEdit, QPlainTextEdit, QDialogButtonBox,
QLabel, QPixmap, pyqtSlot, QMessageBox)
# Do not import salome.geom or salome.smesh here. Not even numpy. This avoids
# the initialization of the said modules when SMITER starts and prolongs the
# activation time.
class MyCustomDialog(QDialog):
def __init__(self, parent=None):
super(MyCustomDialog, self).__init__(parent)
self.setWindowTitle('Example custom dialog')
self.setupUi()
def setupUi(self):
# Setting up the main layout
layout = QGridLayout()
# Having 3 sublayouts.
# +-------------------+---------------------+
# | Upper left | Upper right |
# | layout | layout |
# | | |
# |-------------------+---------------------+
# | |
# | Lower layout |
# | |
# +-----------------------------------------+
# Using form layout for simple ``Label : Input field`` widgets.
# Also contains the ``Apply``, ``Cancel`` and ``Help`` buttons.
layoutUpperLeft = QFormLayout()
self.inputLineEdit = QLineEdit('')
layoutUpperLeft.addRow('Your input', QLineEdit())
# Use the QDialogButtonBox for adding standard buttons
buttons = QDialogButtonBox(QDialogButtonBox.Apply |
QDialogButtonBox.Cancel | QDialogButtonBox.Help)
# Connect the standard buttons signals
buttons.accepted.connect(self.onApply)
buttons.rejected.connect(self.onCancel)
buttons.helpRequested.connect(self.onHelp)
layoutUpperLeft.addWidget(buttons)
# Vertical layout for stacking widgets vertically
layoutUpperRight = QVBoxLayout()
self.textEdit = QPlainTextEdit()
layoutUpperRight.addWidget(self.textEdit)
# Horizontal layout for stacking widgets horizontally
layoutLower = QHBoxLayout()
# Adding two QLabel widgets with images
image1 = QLabel()
# Load the image into QPixMap. The image is loaded from Qt resources.
# To see how to add images
pixmap = QPixmap(':/SMITER/example_dialog_image1.png')
image1.setPixmap(pixmap)
# Do the same
image2 = QLabel()
pixmap = QPixmap(':/SMITER/example_dialog_image2.png')
image2.setPixmap(pixmap)
layoutLower.addWidget(image1)
layoutLower.addWidget(image2)
# Now put the layouts into the main layout
layout.addLayout(layoutUpperLeft, 0, 0)
layout.addLayout(layoutUpperRight, 0, 1)
# The lower layout will expand over columns, thats why we add the
# additional 1, -1 argument
layout.addLayout(layoutLower, 1, 0, 1, -1)
# Set the main layout
self.setLayout(layout)
@pyqtSlot()
def onHelp(self):
"""Display a help message regarding this dialog.
"""
helpMsg = "This is an example of a custom dialog"
QMessageBox.information(self, 'Information', helpMsg)
@pyqtSlot()
def onApply(self):
"""Do something with the inputs of the dialog here. If you need to
import modules, such as salome.geom or salome.smesh, do it here and
not on the top of the file.
It is preferred to get the input values from the widgets and then call
a separate function which accepts the input values and do something
with them. Also, again, it is mandatory if you need certain modules
such as geompy, smesh or even numpy, import them in the function and
not on top of the file!
.. code-block:: python
from salome.geom import geomBuilder
geompy = geomBuilder.New()
# or
from salome.smesh import smeshBuilder
sb = smeshBuilder.New()
# Now ei
"""
# Do something with the inputs of the dialog
lineEditText = self.inputLineEdit.text()
print(lineEditText)
plainTextEditText = self.textEdit.toPlainText()
print(plainTextEditText)
@pyqtSlot()
def onCancel(self):
"""Either just close the dialog or perform additional actions.
"""
self.close()
if __name__ == '__main__':
from qtsalome import QApplication
# Check if there already is an instance of QApplication
app = QApplication.instance()
execute = False
if app is None:
# Create an instance of QApplication
execute = True
app = QApplication([])
# Instance the dialog and show it.
d = MyCustomDialog()
d.show()
if execute:
# Run only if there was no QApplication instance found.
app.exec_()
If we run this dialog in SMITER as desribed in :ref:`base_dialog` the dialog
will look like this.
.. image:: images/dialogs_4.*
:align: center
.. note::
The code snippets are also available in ``smiter/SMITER/src/Dialogs``
directory.
Adding images to Qt resource system
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Images and other multimedia files can be added to the Qt resource system.
Specifically, if we want to use images in our dialogs, we should avoid
embedding them into the python files itself, but rather add them to the
``smiter/SMITER/src/Dialogs/images`` directory and editing the
``dialog_images.qrc``.
The ``.qrc`` file is an XML file, in which we can specify a namespace and
assign images to the namespace. If we look at the content of
``smiter/SMITER/src/Dialogs/images/dialog_images.qrc``.
.. code-block:: xml
example_dialog_image1.png
example_dialog_image2.png
We see that there is a prefix ``SMITER`` then a list of image file names,
enclosed with the ```` markups. That is all what we need to do,
and if we want to access or display these images inside PyQt, we simple write
the path of the image inside the Qt resource system, i.e.
``:/SMITER/example_dialog_image1.png``. Example code:
.. code-block:: python
from qtsalome import QLabel, QPixmap
label = QLabel()
# Load the image
pixmap = QPixmap(':/SMITER/example_dialog_image1.png')
# Set the pixmap
label.setPixmap(pixmap)
# The image is ready to be displayed.