In the first part of this tutorial we put together a simple skeleton of a browser using Qt's built-in browser widget. This allows you to open a webpage, view it and click around -- all that is handled automatically for you. But the interface is entirely up to you.
To convert this bare-bones application into something more resembling an actual browser, in this part we'll set about adding
UI controls. These are added as a series of QActions
on a QToolbar
. We add these definitions to the __init__
block of the QMainWindow
.
navtb = QToolBar("Navigation")
navtb.setIconSize( QSize(16,16) )
self.addToolBar(navtb)
back_btn = QAction( QIcon(os.path.join('icons','arrow-180.png')), "Back", self)
back_btn.setStatusTip("Back to previous page")
back_btn.triggered.connect( self.browser.back )
navtb.addAction(back_btn)
The QWebEngineView
includes slots for forward,
back and reload navigation, which we can connect to directly to our
action's .triggered
signals.
next_btn = QAction( QIcon(os.path.join('icons','arrow-000.png')), "Forward", self)
next_btn.setStatusTip("Forward to next page")
next_btn.triggered.connect( self.browser.forward )
navtb.addAction(next_btn)
reload_btn = QAction( QIcon(os.path.join('icons','arrow-circle-315.png')), "Reload", self)
reload_btn.setStatusTip("Reload page")
reload_btn.triggered.connect( self.browser.reload )
navtb.addAction(reload_btn)
home_btn = QAction( QIcon(os.path.join('icons','home.png')), "Home", self)
home_btn.setStatusTip("Go home")
home_btn.triggered.connect( self.navigate_home )
navtb.addAction(home_btn)
While forward, back and reload can use built-in slots to perform their actions, the navigate home button requires a custom slot function. The slot function is defined on our QMainWindow
class, and simply sets the URL of the browser to the Google homepage. Note that the URL must be passed as a QUrl
object.
def navigate_home(self):
self.browser.setUrl( QUrl("http://www.google.com") )
Any decent web browser also needs an URL bar, and some way to stop the navigation.
self.httpsicon = QLabel() # Yes, really!
self.httpsicon.setPixmap( QPixmap( os.path.join('icons','lock-nossl.png') ) )
navtb.addWidget(self.httpsicon)
self.urlbar = QLineEdit()
self.urlbar.returnPressed.connect( self.navigate_to_url )
navtb.addWidget(self.urlbar)
stop_btn = QAction( QIcon(os.path.join('icons','cross-circle.png')), "Stop", self)
stop_btn.setStatusTip("Stop loading current page")
stop_btn.triggered.connect( self.browser.stop )
navtb.addAction(stop_btn)
As before the 'stop' functionality is available as a slot on the QWebEngineView
itself, and we can simply connect the .triggered
signal from the stop button to the existing slot. However, other features of the URL bar we must handle independently.
First we add a QLabel
to hold our SSL or non-SSL icon
to indicate whether the page is secure. Next, we add the URL bar
which is simply a QLineEdit
. To trigger the loading of the URL
in the bar when entered (return key pressed) we connect to the
.returnPressed
signal on the widget to drive a custom slot
function to trigger navigation to the specified URL.
PyQt6 Crash Course — a new tutorial in your Inbox every day
Beginner-focused crash course explaining the basics with hands-on examples.
def navigate_to_url(self): # Does not receive the Url
q = QUrl( self.urlbar.text() )
if q.scheme() == "":
q.setScheme("http")
self.browser.setUrl(q)
We also want the URL bar to update in response to page changes.
To do this we can use the .urlChanged
and .loadFinished
signals from the QWebEngineView
. We set up the connections
from the signals in the __init__
block as follows:
self.browser.urlChanged.connect(self.update_urlbar)
self.browser.loadFinished.connect(self.update_title)
Then we define the target slot functions which for these
signals. The first, to update the URL bar accepts a QUrl
object and determines whether this is a http
or
https
URL, using this to set the SSL icon.
This is a terrible way to test if a connection is 'secure'. To be correct we should perform a certificate validation.
The QUrl
is converted to a string and the URL bar is
updated with the value. Note that we also set the cursor
position back to the beginning of the line to prevent
the QLineEdit
widget scrolling to the end.
def update_urlbar(self, q):
if q.scheme() == 'https':
# Secure padlock icon
self.httpsicon.setPixmap( QPixmap( os.path.join('icons','lock-ssl.png') ) )
else:
# Insecure padlock icon
self.httpsicon.setPixmap( QPixmap( os.path.join('icons','lock-nossl.png') ) )
self.urlbar.setText( q.toString() )
self.urlbar.setCursorPosition(0)
It's also a nice touch to update the title of the
application window with the title of the current page.
We can get this via browser.page().title()
which
returns the contents of the <title></title>
tag
in the currently loaded web page.
def update_title(self):
title = self.browser.page().title()
self.setWindowTitle("%s - Mozarella Ashbadger" % title)
That's the basic interface implemented. In the next part we'll look at adding the standard Load & Save operations, to allow us to open local HTML files and save web pages to disk.
Create GUI Applications with Python & Qt6 by Martin Fitzpatrick — (PySide6 Edition) The hands-on guide to making apps with Python — Over 10,000 copies sold!