.. 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.