Installing PyQt5 in Linux
To install PyQt5 in latest version of Ubuntu, run the command below:
If you are using any other Linux distribution, search for the term “Pyqt5” in the package manager and install it from there. Alternatively, you can install PyQt5 from pip package manager using the command below:
Note that in some distributions, you may have to use pip3 command to correctly install PyQt5.
Full Code
I am posting full code beforehand so that you can better understand context for individual code snippets explained later in the article. If you are familiar with Python and PyQt5, you can just refer to the code below and skip the explanation.
import sys
from PyQt5.QtWidgets import QWidget, QApplication, QVBoxLayout, QHBoxLayout
from PyQt5.QtWidgets import QTextEdit, QLabel, QShortcut, QFileDialog, QMessageBox
from PyQt5.QtGui import QKeySequence
from PyQt5 import Qt
class Window(QWidget):
def __init__(self):
super().__init__()
self.file_path = None
self.open_new_file_shortcut = QShortcut(QKeySequence('Ctrl+O'), self)
self.open_new_file_shortcut.activated.connect(self.open_new_file)
self.save_current_file_shortcut = QShortcut(QKeySequence('Ctrl+S'), self)
self.save_current_file_shortcut.activated.connect(self.save_current_file)
vbox = QVBoxLayout()
text = "Untitled File"
self.title = QLabel(text)
self.title.setWordWrap(True)
self.title.setAlignment(Qt.Qt.AlignCenter)
vbox.addWidget(self.title)
self.setLayout(vbox)
self.scrollable_text_area = QTextEdit()
vbox.addWidget(self.scrollable_text_area)
def open_new_file(self):
self.file_path, filter_type = QFileDialog.getOpenFileName(self, "Open new file",
"", "All files (*)")
if self.file_path:
with open(self.file_path, "r") as f:
file_contents = f.read()
self.title.setText(self.file_path)
self.scrollable_text_area.setText(file_contents)
else:
self.invalid_path_alert_message()
def save_current_file(self):
if not self.file_path:
new_file_path, filter_type = QFileDialog.getSaveFileName(self, "Save this file
as...", "", "All files (*)")
if new_file_path:
self.file_path = new_file_path
else:
self.invalid_path_alert_message()
return False
file_contents = self.scrollable_text_area.toPlainText()
with open(self.file_path, "w") as f:
f.write(file_contents)
self.title.setText(self.file_path)
def closeEvent(self, event):
messageBox = QMessageBox()
title = "Quit Application?"
message = "WARNING !!\n\nIf you quit without saving, any changes made to the file
will be lost.\n\nSave file before quitting?"
reply = messageBox.question(self, title, message, messageBox.Yes | messageBox.No |
messageBox.Cancel, messageBox.Cancel)
if reply == messageBox.Yes:
return_value = self.save_current_file()
if return_value == False:
event.ignore()
elif reply == messageBox.No:
event.accept()
else:
event.ignore()
def invalid_path_alert_message(self):
messageBox = QMessageBox()
messageBox.setWindowTitle("Invalid file")
messageBox.setText("Selected filename or path is not valid. Please select a
valid file.")
messageBox.exec()
if __name__ == '__main__':
app = QApplication(sys.argv)
w = Window()
w.showMaximized()
sys.exit(app.exec_())
Explanation
The first part of the code just imports modules that will be used throughout the sample:
from PyQt5.QtWidgets import QWidget, QApplication, QVBoxLayout, QHBoxLayout
from PyQt5.QtWidgets import QTextEdit, QLabel, QShortcut, QFileDialog, QMessageBox
from PyQt5.QtGui import QKeySequence
from PyQt5 import Qt
In the next part, a new class called “Window” is created that inherits from “QWidget” class. QWidget class provides commonly used graphical components in Qt. By using “super” you can ensure that the parent Qt object is returned.
def __init__(self):
super().__init__()
Some variables are defined in the next part. File path is set to “None” by default and shortcuts for opening a file using <CTRL+O> and saving a file using <CTRL+S> are defined using QShortcut class. These shortcuts are then connected to their respective methods that are called whenever a user presses the defined key combinations.
self.open_new_file_shortcut = QShortcut(QKeySequence('Ctrl+O'), self)
self.open_new_file_shortcut.activated.connect(self.open_new_file)
self.save_current_file_shortcut = QShortcut(QKeySequence('Ctrl+S'), self)
self.save_current_file_shortcut.activated.connect(self.save_current_file)
Using QVBoxLayout class, a new layout is created to which child widgets will be added. A center-aligned label is set for the default file name using QLabel class.
text = "Untitled File"
self.title = QLabel(text)
self.title.setWordWrap(True)
self.title.setAlignment(Qt.Qt.AlignCenter)
vbox.addWidget(self.title)
self.setLayout(vbox)
Next, a text area is added to the layout using a QTextEdit object. The QTextEdit widget will give you an editable, scrollable area to work with. This widget supports typical copy, paste, cut, undo, redo, select-all etc. keyboard shortcuts. You can also use a right click context menu within the text area.
vbox.addWidget(self.scrollable_text_area)
The “open_new_fie” method is called when a user completes <CTRL+O> keyboard shortcut. QFileDialog class presents a file picker dialog to the user. File path is determined after a user selects a file from the picker. If file path is valid, text content is read from the file and set to QTextEdit widget. This makes text visible to the user, changes the title to the new filename and completes the process of opening a new file. If for some reason, file path cannot be determined, an “invalid file” alert box is shown to the user.
self.file_path, filter_type = QFileDialog.getOpenFileName(self, "Open new file", "",
"All files (*)")
if self.file_path:
with open(self.file_path, "r") as f:
file_contents = f.read()
self.title.setText(self.file_path)
self.scrollable_text_area.setText(file_contents)
else:
self.invalid_path_alert_message()
The “save_current_file” method is called whenever a user completes <CTRL+S> keyboard shortcut. Instead of retrieving a new file path, QFileDialog now asks the user to provide a path. If file path is valid, contents visible in QTextEdit widget are written to the full file path, otherwise an “invalid file” alert box is shown. Title of the file currently being edited is also changed to the new location provided by the user.
if not self.file_path:
new_file_path, filter_type = QFileDialog.getSaveFileName(self, "Save this file
as...", "", "All files (*)")
if new_file_path:
self.file_path = new_file_path
else:
self.invalid_path_alert_message()
return False
file_contents = self.scrollable_text_area.toPlainText()
with open(self.file_path, "w") as f:
f.write(file_contents)
self.title.setText(self.file_path)
The “closeEvent” method is part of the PyQt5 event handling API. This method is called whenever a user tries to close a window using the cross button or by hitting <ALT+F4> key combination. On firing of the close event, the user is shown a dialog box with three choices: “Yes”, “No” and “Cancel”. “Yes” button saves the file and closes the application while “No” button closes the file without saving the contents. “Cancel” button closes the dialog box and takes user back to the application.
messageBox = QMessageBox()
title = "Quit Application?"
message = "WARNING !!\n\nIf you quit without saving, any changes made to the file will
be lost.\n\nSave file before quitting?"
reply = messageBox.question(self, title, message, messageBox.Yes | messageBox.No |
messageBox.Cancel, messageBox.Cancel)
if reply == messageBox.Yes:
return_value = self.save_current_file()
if return_value == False:
event.ignore()
elif reply == messageBox.No:
event.accept()
else:
event.ignore()
The “invalid file” alert box doesn’t have any bells and whistles. It just conveys the message that file path couldn’t be determined.
messageBox = QMessageBox()
messageBox.setWindowTitle("Invalid file")
messageBox.setText("Selected filename or path is not valid. Please select a valid file.")
messageBox.exec()
Lastly, the main application loop for event handling and drawing of widgets is started by using the “.exec_()” method.
app = QApplication(sys.argv)
w = Window()
w.showMaximized()
sys.exit(app.exec_())
Running the App
Just save full code to a text file, set the file extension to “.py”, mark the file executable and run it to launch the app. For instance, if the file name is “simple_text_editor.py”, you need to run following two commands:
$ ./simple_text_editor.py
Things You can Do to Improve the Code
The code explained above works fine for a bare-bones text editor. However, it may not be useful for practical purposes as it lacks many features commonly seen in good text editors. You can improve the code by adding new features like line numbers, line highlighting, syntax highlighting, multiple tabs, session saving, toolbar, dropdown menus, buffer change detection etc.
Conclusion
This article mainly focuses on providing a starting ground for creating PyQt apps. If you find errors in the code or want to suggest something, feedback is welcome.