The previous chapters laid the foundation for getting started in PyQt5 as you learned to create GUIs with more functionality by adding widgets such as QPushButton and QCheckBox. Rather than continue to barrage you with PyQt’s numerous widgets, taking a moment to learn about the various layout managers will save us some trouble moving forward.
Layout management is the way in which we arrange widgets in our windows. This can involve a number of different factors, including size and position, resize handling, and adding or removing widgets. Layout management is also very important to consider for the user looking at your application. Do a quick image search on the Internet for “worst GUI layouts” and you will see numerous applications crammed with widgets with no clear reasoning.
A layout manager is a class that contains methods which we can use to arrange widgets inside windows. They are useful for communicating between child and parent widgets to make them utilize the space in a window more efficiently.
In this chapter we are going to take a look at four methods that can be used for layout management in PyQt:
1.
Absolute positioning with move().
2.
QBoxLayout which is useful for creating simple GUIs with horizontal or vertical layouts.
3.
QFormLayout is a convenience layout useful for making application forms.
4.
QGridLayout allows for more control over arranging widgets by specifying x and y coordinate values.
This chapter will also cover the idea of nesting layouts for creating more elaborate applications.
We will also take a look at a few new widgets and classes:
QTextEdit – Similar to QLineEdit but creates a text entry field with more space
QFileDialog – Native file dialog of PyQt that allows the user to open or save files
QButtonGroup – To organize push button and checkbox widgets
QSpinBox – A text box that displays integer values that the user can cycle through
QComboBox – Presents a list of options in a dropdown-style widget
To make things simpler to understand in this chapter, let’s first take a look at a few key concepts for using layout managers.
Choosing a Layout Manager
Setting the layout manager or changing the one we want to use in our applications isn’t very difficult to do.
When you import modules in the beginning of your program, be sure to also include the layout manager(s) you want to use like so:
from PyQt5.QtWidgets import (QApplication, QWidget, QHBoxLayout, QFormLayout)
Here we import both the QHBoxLayout and QFormLayout classes from QtWidgets. Then to set a specific layout manager inside a parent window or widget, we first must create an instance of that layout manager. In the following code, we call QVBoxLayout and then set the layout within the parent window using setLayout().
v_box = QVBoxLayout()
parent_window.setLayout(v_box)
Using a layout manager isn’t necessary, but it is definitely preferred in order to make your applications easier to organize and to rearrange if necessary.
Customizing the Layout
So you’ve got your layout manager chosen. You’ve created your widgets. How do you go about adding them to the window?
name = QTextEdit()
v_box.addWidget(name)
In this code, we create a QTextEdit widget and then call the addWidget() method to add it in the layout. Be sure to consider what type of layout manager the parent window or widget is using before adding a child widget. For each layout manager, some of the parameters may change or you may need to call a different method to add widgets, but the concept is the same – create a widget, then add that widget into your layout. We will go over more details when we get to each specific manager.
If you need to create a more complex application with widgets arranged horizontally, vertically, or maybe even arranged in a grid, it is also possible to nest layouts in PyQt. Nesting layouts involves placing one layout manager inside of another. This can be accomplished by calling the addLayout() method and passing the name of a layout as a parameter.
One of the great things about using a layout manager is that when you resize the windows, the widgets in the window will all adjust accordingly. However, each layout manager has its own way of determining the spacing, alignment, size, or border around the widgets. These can all be manipulated and we will look at a few of these methods in the upcoming projects.
Absolute Positioning – Move( )
While many people will recommend using layout managers, you can of course create layouts without them. This idea is called absolute positioning and it involves specifying the size and position values for each widget. This is the method we used in the previous chapters when we used move() to arrange widgets.
If you do decide to use absolute positioning, there are a few drawbacks to keep in mind. First of all, resizing the main window will not cause the widgets in it to adjust their size or position. Something else to keep in mind is the differences between operating systems, such as fonts and font sizes which could drastically change the look and layout of our GUI.
Absolute positioning can be most useful for setting the position and size values of widgets that are contained within other widgets.
Project 4.1 – Basic Notepad GUI
For our first project, let’s take a look at creating a simple interface, a notepad GUI, to demonstrate how to use absolute positioning. A notepad is a way to capture our ideas or to take notes. It generally starts off blank and we fill in the information line by line. The benefit of having a digital notepad is that we can input and edit text much more easily than with real paper. Electronic notepads don’t just include a blank area to write, but also tools which can be found at the top of the GUI window to help open, save, or edit notes.
This project, as can be seen in Figure 4-1, lays the foundation for our notepad GUI. In Chapter 5, we will take a look at how to improve upon this example by creating a menu interface and adding editing tools.
The QTextEdit Widget
If we are going to create a notepad GUI, then we need a text entry field that will allow us to enter and edit more than one line of text at a time.
The QTextEdit widget allows a user to enter text, either plain or rich text, and permits editing such as copy, paste, or cut. The widget can handle characters or paragraphs of text. Paragraphs are simply long strings that are word-wrapped into the widget and end with a newline character. QTextEdit is also useful for displaying lists, images, and tables or providing an interface for displaying text using HTML.
Take a look at the Solution code to the notepad GUI to see how to create a QTextEdit widget.
The QFileDialog Class
The QFileDialogclass can be used to select files or directories found on your computer. This can be useful for locating and opening a file or looking for a directory to save a file and giving your file a name.
To open a file, we call the getOpenFileName() method, set the parent, create a title for the dialog box, display contents of a specific directory, and display files matching the patterns given in the string "All Files (*);;Text Files (*.txt)". You can also display image or other file types.
The look of the dialog box that appears will also reflect the type of system you are using. To change these properties, you could access QFileDialog.Options() and alter the dialog properties and appearance.
options = QFileDialog.Options()
options = QFileDialog.DontUseNativeDialog # By default native dialog is used
Basic Notepad GUI Solution
For this project, the GUI will consist of three widgets, two QPushButtons and a QTextEdit field (Listing 4-1). Users will be able to select the new button to clear the text in the line edit field or save the text to a file by clicking the save button and opening a dialog box.
# notepad.py
# Import necessary modules
import sys
from PyQt5.QtWidgets import (QApplication, QWidget, QPushButton, QTextEdit, QMessageBox, QFileDialog)
class Notepad(QWidget):
def __init__(self): # constructor
super().__init__()
self.initializeUI()
def initializeUI(self):
"""
Initialize the window and display its contents to the screen
"""
self.setGeometry(100, 100, 300, 400)
self.setWindowTitle('4.1 – Notepad GUI')
self.notepadWidgets()
self.show()
def notepadWidgets(self):
"""
Create widgets for notepad GUI and arrange them in window
"""
# Create push buttons for editing menu
new_button = QPushButton("New", self)
new_button.move(10, 20)
new_button.clicked.connect(self.clearText)
save_button = QPushButton("Save", self)
save_button.move(80, 20)
save_button.clicked.connect(self.saveText)
# Create text edit field
self.text_field = QTextEdit(self)
self.text_field.resize(280, 330)
self.text_field.move(10, 60)
def clearText(self):
"""
If the new button is clicked, display dialog asking user if they want to clear the text edit field or not.
"""
answer = QMessageBox.question(self, "Clear Text",
"Do you want to clear the text?", QMessageBox.No | QMessageBox.Yes,
QMessageBox.Yes)
if answer == QMessageBox.Yes:
self.text_field.clear()
else:
pass
def saveText(self):
"""
If the save button is clicked, display dialog to save the text in the text edit field to a text file.
Our notepad can be seen in Figure 4-1. The text displayed in the widgets shows that the QLineEdit widget can support different fonts, colors, and text sizes.
Explanation
When you use absolute positioning, you can think of the window as a grid where the top-left corner has the x and y coordinates (0, 0). If you create a window with height equal to 100 and width also 100, then the bottom-right corner has values (99, 99). To arrange widgets using move(), you need to specify values within the height and width range.
For example, the new_button push button is created and positioned as follows:
new_button = QPushButton("New", self)
new_button.move(10, 20) # x = 10, y = 20
Three widgets are created in Listing 4-1, two buttons and a QTextEdit widget for inputting text. We can use the buttons to either clear text or save the text we have typed.
When the user clicks the save_button, the saveText() method is called which displays a QFileDialog like the one shown in Figure 4-2.
The contents of the text file are shown in Figure 4-3. Because we save the file as a text file, it loses some information but still retains the spacing and paragraphs separated by newlines. To keep the rich text information, you could save the file using HTML. This will be covered in Chapter 5.
The QHBoxLayout and QVBoxLayout Classes
Arranging widgets can be accomplished easily with the QBoxLayout classes. PyQt has two different QBoxLayout styles, QHBoxLayout and QVBoxLayout:
QHBoxLayout – Used to arrange widgets horizontally from left to right in the window
QVBoxLayout – Used to arrange widgets vertically from top to bottom in the window
Creating a basic GUI with only one of these layout managers is possible, but the real potential comes from being able to combine the two of them to create more elaborate layouts. Together we can combine them, along with the addStretch() method, to place widgets anywhere in the window. Think of addStretch() as an adjustable blank space that can be used to help arrange widgets relative to each other or to help place widgets in the window.
In the following project, we will take a look at how to use QHBoxLayout and QVBoxLayout in the same program to create a simple survey GUI application.
Project 4.2 – Survey GUI
Creating a survey to collect data from users can be very useful for businesses or for research. In the following project, we will take a look at how to use the QBoxLayout class to create a simple window that displays a question to the user and allows them to select an answer.
From personal experience, the Python language is very good at automating repetitive tasks. In university, I needed to collect data from almost a thousand participants in order to research marketing trends related to how they spent money at sporting events. For my research I decided to create an application that would ask the user a question and then store their answers in a Python list. When a participant reached the end of the survey, their answers were written to a file. This greatly helped later when I needed to use that same data to create graphs and charts.
Figure 4-4 shows the program we are going to make in Project 4.2.
The QButtonGroup Class
You may often have a few checkboxes or buttons that need to be grouped together to make it easier to manage them. Luckily, PyQt has the QButtonGroupclass to help not only group and arrange buttons together, but also has the ability to make buttons mutually exclusive. This is also helpful if you only want one checkbox to be checked at a time.
QButtonGroup is not actually a widget, but a container where you can add widgets. Therefore, you can’t actually add QButtonGroup to a layout. The following code shows the method of how to import and set up QButtonGroup in your application:
from PyQt5.QtWidgets import QButtonGroup, QCheckBox
b_group = QButtonGroup() # Create instance of QButtonGroup
# Create two checkboxes
cb_1 = QCheckBox("CB 1")
cb_2 = QCheckBox("CB 2")
# Add checkboxes into QButtonGroup
b_group.addButton(cb_1)
b_group.addButton(cb_2)
# Connect all buttons in a group to one signal
b_group.buttonClicked.connect(cbClicked)
def cbClicked(cb):
print(cb)
In the above code we create two QCheckBox widgets and add them to the QButtonGroup using the addButton() method. To make the buttons mutually exclusive, we check to see if a signal is sent not from each individual button but from the button group instead. This is done with
b_group.buttonClicked.connect(cbClicked)
Survey GUI Solution
The survey GUI consists of QLabel widgets to display the title, question, and ratings labels for each checkbox. For the checkboxes in the window, the text beside each label could have been left blank, but the numbers are left as a visual cue to the user. Once the user selects a choice, they can close the window using a QPushButton (Listing 4-2).
After importing all of the PyQt classes and setting up the DisplaySurvey class, we begin by creating some labels, setting up the text for the ratings labels, and creating the close_button.
The application consists of three separate QHBoxLayout objects – title_h_box, ratings_h_box, and cb_h_box – and a single QVBoxLayout layout, v_box. For this GUI, v_box will act as the container for all of the other widgets and layouts, arranged vertically from top to bottom.
Combining Box Layouts and Arranging Widgets
When we say combining layouts, what that really means is nesting one type of box layout inside of another type to get the benefit of vertical or horizontal layouts.
The following bit of code shows how to create a QHBoxLayout object and add a widget to it:
# Create horizontal layouts
title_h_box = QHBoxLayout()
title_h_box.addStretch()
title_h_box.addWidget(title)
title_h_box.addStretch()
The addStretch() method acts like an invisible widget that can be used to help arrange widgets in a layout manager. Widgets in QHBoxLayout are organized left to right, so in title_h_box, addStretch is added to the left, title in the middle, and another addStretch to the right. This centers the title in title_h_box.
To add the rating labels and checkboxes to the window, a separate QHBoxLayout is created for each one. Each widget added is spaced out using the setSpacing() method, which is useful for creating a fixed amount of space between widgets inside of a layout.
Adding layouts or widgets to a parent layout is as simple as changing the method called.
In Chapter 3 we looked at how to make a create new user GUI (Project 3.2) that would collect a user’s information. In that project, each line consisted of a QLabel widget on the left and a QLineEdit widget on the right. They were then arranged in the window using absolute positioning.
For situations like this where you need to create a form to collect information from a user, PyQt provides the QFormLayout class. It is a layout class that arranges its children widgets into a two-column layout, the left column consisting of labels and the right one consisting of entry field widgets such as QLineEdit or QSpinBox. The QFormLayout class makes designing these kinds of GUIs very convenient.
Project 4.3 – Application Form GUI
We all have to fill out application forms at some point, applying for a job, when you want to go to university, trying to get insurance for your car, or signing up for a new bank account.
For this project let’s take a look at creating an application form that someone could use to set up an appointment at the hospital like in Figure 4-5. When filling out an electronic application, you can combine many different widgets to not only reduce the size of the window but also minimize the amount of clutter from text that you might usually see on a paper application.
Before getting started on the application form, we should learn about two new widgets – QSpinBox and QComboBox – that we will use in the application GUI.
The QSpinBox and QComboBox Widgets
Rather than using a QLineEdit widget for the user to input information, sometimes you may want them to only be allowed to select from a list of predetermined values. Both QSpinBox and QComboBox widgets are very useful for handling this kind of task.
QSpinBox creates an object that is similar to a text box, but allows the user to select integer values either by typing a value into the widget or by clicking the up and down arrows. You can also edit the range of the values, set the step size when the arrow is clicked, set a starting value, or even add prefixes or suffixes in the box. There are also other kinds of spin boxes in PyQt, such as QDateEdit, to select date and time values.
QComboBox is a way to display a list of options for the user to select from. When a user clicks the arrow button, a pop-up list appears and displays a collection of possible selections.
In Listing 4-3 we will take a look at how to create both kinds of objects, add them to our layout, and find out how to use the values in QSpinBox to update other widgets in the GUI window.
# spin_combo_boxes.py
# Import necessary modules
import sys
from PyQt5.QtWidgets import (QApplication, QWidget, QLabel, QComboBox, QSpinBox, QHBoxLayout, QVBoxLayout)
from PyQt5.QtGui import QFont
from PyQt5.QtCore import Qt
class SelectItems(QWidget):
def __init__(self):
super().__init__()
self.initializeUI()
def initializeUI(self):
"""
Initialize the window and display its contents to the screen
"""
self.setGeometry(100, 100, 300, 200)
self.setWindowTitle('ComboBox and SpinBox')
self.itemsAndPrices()
self.show()
def itemsAndPrices(self):
"""
Create the widgets so users can select an item from the combo boxes
and a price from the spin boxes
"""
info_label = QLabel("Select 2 items you had for lunch and their prices.")
Your window should look similar to the one seen in Figure 4-6.
Explanation
The code for this application is also another demonstration of how to create nested layouts in PyQt. Here we create two instances of QHBoxLayout and add them to a vertical layout.
We create two separate combo boxes, lunch_cb1 and lunch_cb2, and add the list of items that we want to be displayed to each of them using the addItems() method. Then two separate spin boxes are created, price_sb1 and price_sb2.
self.price_sb1.setRange(0,100)
self.price_sb1.setPrefix("$")
The setRange() method is used to set the upper and lower boundaries for a spin box and setPrefix() can be used to display other text inside of the text box, in this case a dollar sign. This can be helpful to give the user more information about the widget’s purpose. All of these widgets are then added to two separate horizontal layouts, h_box1 and h_box2.
Note
Since the two QComboBox objects and the two QSpinBox objects each contain the same values, you may have the urge to just try and use them over again when adding them to QVBoxLayout. This won’t work. When you add an item to a widget or to a layout, that widget takes ownership of the item. This means you cannot add an item to more than one widget or layout. You will need to create a new instance.
Finally, as we change the values in the spin boxes, they both send a signal that is connected to the calculateTotal() method. This will dynamically update the value for display_total_label in the window.
Application Form GUI Solution
The application form GUI consists of a number of different widgets, including QLabel, QLineEdit, QSpinBox, QComboBox, QTextEdit, and QPushButton (Listing 4-4). Nesting layouts is also possible with the QFormLayout manager.
When completed, your GUI should look similar to Figure 4-5.
Note
Depending upon what system you are working on, the look and layout of the widgets in your window will change.
Explanation
Using the QFormLayout class is pretty straightforward. In the formWidgets() method, the widgets that will be used in this GUI are instantiated in the beginning. An important one to point out is the mobile_num line edit object.
Any type of character can naturally be typed into the QLineEdit entry field. However, if you want to limit the type, size, or manner in which characters can be input, then you can create an input mask by calling the setInputMask() method. setInputMask() also can be used to set the maximum number of characters.
Two parts of this application have widgets arranged horizontally. For these widgets we will add them in QHBoxLayoutobjects.
The QFormLayout object is created by
app_form_layout = QFormLayout()
Next, all widgets and layouts are added to the form layout using the addRow() method. Finally, the layout for our window is set using self.setLayout(app_form_layout).
Widgets and layouts can be added to a QFormLayout object in the following ways:
form_layout.addRow(QWidget)
form_layout.addRow("text", QWidget)
form_layout.addRow(layout)
The first one will add a widget and may cause it to stretch to fit the window. The second fits the text and its widget into a two-column layout. The last one can be used to nest layouts.
The QGridLayout Class
The QGridLayout layout manager is used to arrange widgets in rows and columns similar to a spreadsheet or matrix. The layout manager takes the space within its parent window or widget and divides it up according to the sizes of the widgets within that row (or column). Adding space between widgets, creating a border, or stretching widgets across multiple rows or columns is also possible.
Understanding how to add and manipulate widgets using QGridLayout is also easier. The grid for the layout manager starts at value (0, 0) which is the top leftmost cell. To add a widget underneath it (the next row), simply add 1 to the left value, (1, 0). To keep moving down the rows, keep increasing the left value. To move across columns, increase the right value.
Let’s take a look at how to make a to-do list using QGridLayout.
Project 4.4 – To-Do List GUI
We all have things that we must do every day, and many of us need a way to help organize our busy lives. For this project we will take a look at creating a basic layout for a to-do list.
Some to-do lists are broken down by hours of the day, by importance of goals, or by various other tasks we may need to do for that day, week, or even month. Once a goal is complete, we need some way to check off a task or remove it.
The project will consist of a to-do list made up of two parts, a list of things you must do on the left and daily appointments on the right. The “Must Dos” will consist of QCheckBox and QLineEdit widgets. The “Appointments” will be separated into three sections, morning, noon, and evening, and will use QTextEdit widgets to give the user an area to write down their tasks. You can see the GUI we will be building in Figure 4-7.
To-Do List GUI Solution
For this project (Listing 4-5), we will be focusing mainly on how to create the GUI and arrange widgets using the QGridLayout class as the main layout. This project also includes a nested QVBoxLayout for the “Appointments” layout.
Initialize the window and display its contents to the screen
"""
self.setGeometry(100, 100, 500, 350)
self.setWindowTitle('4.4 – ToDo List GUI')
self.setupWidgets()
self.show()
def setupWidgets(self):
"""
Create widgets for to-do list GUI and arrange them in the window
"""
# Create grid layout
main_grid = QGridLayout()
todo_title = QLabel("To Do List")
todo_title.setFont(QFont('Arial', 24))
todo_title.setAlignment(Qt.AlignCenter)
close_button = QPushButton("Close")
close_button.clicked.connect(self.close)
# Create section labels for to-do list
mustdo_label = QLabel("Must Dos")
mustdo_label.setFont(QFont('Arial', 20))
mustdo_label.setAlignment(Qt.AlignCenter)
appts_label = QLabel("Appointments")
appts_label.setFont(QFont('Arial', 20))
appts_label.setAlignment(Qt.AlignCenter)
# Create must-do section
mustdo_grid = QGridLayout()
mustdo_grid.setContentsMargins(5, 5, 5, 5)
mustdo_grid.addWidget(mustdo_label, 0, 0, 1, 2)
# Create checkboxes and line edit widgets
for position in range(1, 15):
checkbox = QCheckBox()
checkbox.setChecked(False)
linedit = QLineEdit()
linedit.setMinimumWidth(200)
mustdo_grid.addWidget(checkbox, position, 0)
mustdo_grid.addWidget(linedit, position, 1)
# Create labels for appointments section
morning_label = QLabel("Morning")
morning_label.setFont(QFont('Arial', 16))
morning_entry = QTextEdit()
noon_label = QLabel("Noon")
noon_label.setFont(QFont('Arial', 16))
noon_entry = QTextEdit()
evening_label = QLabel("Evening")
evening_label.setFont(QFont('Arial', 16))
evening_entry = QTextEdit()
# Create vertical layout and add widgets
appt_v_box = QVBoxLayout()
appt_v_box.setContentsMargins(5, 5, 5, 5)
appt_v_box.addWidget(appts_label)
appt_v_box.addWidget(morning_label)
appt_v_box.addWidget(morning_entry)
appt_v_box.addWidget(noon_label)
appt_v_box.addWidget(noon_entry)
appt_v_box.addWidget(evening_label)
appt_v_box.addWidget(evening_entry)
# Add other layouts to main grid layout
main_grid.addWidget(todo_title, 0, 0, 1, 2)
main_grid.addLayout(mustdo_grid, 1, 0)
main_grid.addLayout(appt_v_box, 1, 1)
main_grid.addWidget(close_button, 2, 0, 1, 2)
self.setLayout(main_grid)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = ToDoList()
sys.exit(app.exec_())
Listing 4-5
Code for the to-do list GUI
When you are finished, your GUI should look similar to Figure 4-7.
Explanation
Here we start by creating our ToDoList class which inherits from QWidget. In the setupWidgets() method, a few QLabel widgets which will serve as header labels for the GUI and the different sections are created. A QPushButton, which will close the program, is also instantiated.
The must-do section uses the QGridLayout class. A margin can be set to frame a layout using the setContentMargins() method.
mustdo_grid.setContentsMargins(5, 5, 5, 5)
Each integer specifies the size of the border in pixels, (left, top, right, bottom).
Then the layout managers, mustdo_grid and appt_v_box, and their widgets are created and added. Finally, the title, the two layouts, and the close button are added to the main_grid layout, and main_grid is set as the main layout using setLayout().
Adding Widgets and Spanning Rows and Columns with QGridLayout
Since widgets will be placed in a grid-like structure, when you add a new object to the layout, you must specify the row and column values as parameters of the addWidget() or addLayout() methods. Take a look at the following lines:
main_grid.addWidget(todo_title, 0, 0)
main_grid.addLayout(mustdo_grid, 1, 0)
main_grid.addLayout(appt_v_box, 1, 1)
The todo_title QLabel widget is added to the main_grid layout at the position where the row equals 0 and column equals 0, which is also the top-left corner. Then, the mustdo_grid is added directly below it by increasing the row value to 1 and leaving the column value equal to 0. Finally, we move over one column for the appt_v_box layout by setting the column value to 1. If you want to build a GUI with more widgets using QGridLayout, then you would just continue in this manner moving away from 0, 0.
But what happens if you have a widget in a column or a row that is next to another widget that needs to take up more space in the vertical or horizontal direction? QGridLayout allows us to specify the number of rows or columns that we want a single widget or layout to span. Spanning can be thought of as stretching a widget horizontally or vertically to help us better arrange our GUI.
main_grid.addWidget(clear_button, 2, 0, 1, 3)
The extra two parameters at the end, 1 and 3, tell the layout manager that we want to span one row and three columns. This causes the widget to stretch horizontally.
Summary
Taking the time to learn about layout management will save you time and effort when coding your own GUI applications. In this chapter we reviewed absolute positioning using the move() method and learned about three of PyQt’s layout managers – QBoxLayout, QFormLayout, and QGridLayout. Each of these classes has their own special purpose, but one of the real powers of PyQt is how convenient it is to nest them into other layouts to make more complex GUIs.
It is important to note that any of the subclasses within QWidget can also use a layout manager to manage their children. The advantages of using a layout manager include
Positioning of child widgets
Setting default sizes for windows
Handling resizing of windows
Updating content in the window or parent widget when something changes, such as type of font, font size, and hiding, showing, or removing of a child widget
You can actually design and lay out your interface graphically using the Qt Designer Application. We will take a brief look at how to do this in Chapter 7.
In Chapter 5 we are going to take a look at how to add menus to our applications.