Installing PyQt5 in Linux
To install PyQt5 in latest version of Ubuntu, run the command below:
$ sudo apt install python3-pyqt5If 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:
$ pip install pyqt5Note 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.
#!/usr/bin/env python3import 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:
import sysfrom 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.
class Window(QWidget):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
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.
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)
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.
self.scrollable_text_area = QTextEdit()vbox.addWidget(self.scrollable_text_area)
The “open_new_fie” method is called when a user completes
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
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
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.
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()
Lastly, the main application loop for event handling and drawing of widgets is started by using the “.exec_()” method.
if __name__ == '__main__':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:
$ chmod +x simple_text_editor.py$ ./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.