Hi! Hope you're enjoying this blog. I have a new home at www.goldsborough.me. Be sure to also check by there for new posts <3

Thursday, August 8, 2013

Tutorial: PyQt Web Browser

Today I want to show you how to make a small, but cool, web browser using PyQt's QWebView. It will include a button for going back, forward, reloading, launching you into space, bookmarking as well as a bookmarks combo box and a status bar that displays the url to a link that is being hovered and a progress bar while the web page is loading. It will look like this:



First of all, here's the full code, only 180 lines(!) :

import sys, pickle
from PyQt4 import QtGui, QtCore
from PyQt4.QtWebKit import *

b = open("bookmarks.txt","rb")
bookmarks = pickle.loads(b.read())
b.close()

url = ""

class Main(QtGui.QMainWindow):

    def __init__(self):
        QtGui.QMainWindow.__init__(self)
        self.initUI()

    def initUI(self):

        global bookmarks
        
        self.centralwidget = QtGui.QWidget(self)

        self.line = QtGui.QLineEdit(self)
        self.line.setMinimumSize(1150,30)
        self.line.setStyleSheet("font-size:15px;")

        self.enter = QtGui.QPushButton(self)
        self.enter.resize(0,0)
        self.enter.clicked.connect(self.Enter)
        self.enter.setShortcut("Return")

        self.reload = QtGui.QPushButton("↻",self)
        self.reload.setMinimumSize(35,30)
        self.reload.setShortcut("F5")
        self.reload.setStyleSheet("font-size:23px;")
        self.reload.clicked.connect(self.Reload)

        self.back = QtGui.QPushButton("◀",self)
        self.back.setMinimumSize(35,30)
        self.back.setStyleSheet("font-size:23px;")
        self.back.clicked.connect(self.Back)

        self.forw = QtGui.QPushButton("▶",self)
        self.forw.setMinimumSize(35,30)
        self.forw.setStyleSheet("font-size:23px;")
        self.forw.clicked.connect(self.Forward)

        self.book = QtGui.QPushButton("☆",self)
        self.book.setMinimumSize(35,30)
        self.book.clicked.connect(self.Bookmark)
        self.book.setStyleSheet("font-size:18px;")

        self.pbar = QtGui.QProgressBar()
        self.pbar.setMaximumWidth(120)

        self.web = QWebView(loadProgress = self.pbar.setValue, loadFinished = self.pbar.hide, loadStarted = self.pbar.show, titleChanged = self.setWindowTitle)
        self.web.setMinimumSize(1360,700)

        self.list = QtGui.QComboBox(self)
        self.list.setMinimumSize(35,30)

        for i in bookmarks:
            self.list.addItem(i)

        self.list.activated[str].connect(self.handleBookmarks)
        self.list.view().setSizePolicy(QtGui.QSizePolicy.Minimum,QtGui.QSizePolicy.Minimum)

        self.web.urlChanged.connect(self.UrlChanged)
        
        self.web.page().linkHovered.connect(self.LinkHovered)

        grid = QtGui.QGridLayout()

        grid.addWidget(self.back,0,0, 1, 1)
        grid.addWidget(self.line,0,3, 1, 1)
        grid.addWidget(self.book,0,4, 1, 1)
        grid.addWidget(self.forw,0,1, 1, 1)
        grid.addWidget(self.reload,0,2, 1, 1)
        grid.addWidget(self.list,0,5, 1, 1)
        grid.addWidget(self.web, 2, 0, 1, 6)

        self.centralwidget.setLayout(grid)

#---------Window settings --------------------------------

        self.setGeometry(50,50,1360,768)
        self.setWindowTitle("PySurf")
        self.setWindowIcon(QtGui.QIcon(""))
        self.setStyleSheet("background-color:")
        
        self.status = self.statusBar()
        self.status.addPermanentWidget(self.pbar)
        self.status.hide()

        self.setCentralWidget(self.centralwidget)

    def Enter(self):
        global url
        global bookmarks
        
        url = self.line.text()

        http = "http://"
        www = "www."
        
        if www in url and http not in url:
            url = http + url
            
        elif "." not in url:
            url = "http://www.google.com/search?q="+url
            
        elif http in url and www not in url:
            url = url[:7] + www + url[7:]

        elif http and www not in url:
            url = http + www + url


        self.line.setText(url)

        self.web.load(QtCore.QUrl(url))

        if url in bookmarks:
            self.book.setText("★")
            
        else:
            self.book.setText("☆")
            
        self.status.show()
        
    def Bookmark(self):
        global url
        global bookmarks

        bookmarks.append(url)

        b = open("bookmarks.txt","wb")
        pickle.dump(bookmarks,b)
        b.close()
        
        self.list.addItem(url)
        self.book.setText("★")

def handleBookmarks(self,choice):
        global url

        url = choice
        self.line.setText(url)
        self.Enter()

def Back(self):
        self.web.back()
        
    def Forward(self):
        self.web.forward()

    def Reload(self):
        self.web.reload()

    def UrlChanged(self):
        self.line.setText(self.web.url().toString())

    def LinkHovered(self,l):
        self.status.showMessage(l)

    

def main():
    app = QtGui.QApplication(sys.argv)
    main= Main()
    main.show()

    sys.exit(app.exec_())

if __name__ == "__main__":
    main()


Just one thing, you need to either create a bookmark.txt file in your directory first or run the program without this: 

b = open("bookmarks.txt","rb")
bookmarks = pickle.loads(b.read())
b.close()

before having added bookmarks, otherwise it'll give you an error (since there is no bookmark.txt file yet).

Let's get started

As always, start by copying the basic PyQt window I provide at the sidebar of this blog. Then, we will need to import a few modules:

import sys, pickle
from PyQt4.QtWebKit import *

Pickle we will need for the bookmarks list, the QtWebKit for making the Bolognese sauce for our Spaghetti. Oh, wait, we're programming here. QtWebKit basically is PyQt's module for displaying web pages, QWebFrame to be more specific.

About the file pickling after, do yourself a favour and create a bookmark.txt file in your directory since you'll get an error otherwise. Lastly, create a variable url and assign it to an empty string.

Next, let's create the buttons and the QWebView and add them to a grid layout:

        global bookmarks
        
        self.centralwidget = QtGui.QWidget(self)

        self.line = QtGui.QLineEdit(self)
        self.line.setMinimumSize(1150,30)
        self.line.setStyleSheet("font-size:15px;")

        self.enter = QtGui.QPushButton(self)
        self.enter.resize(0,0)
        self.enter.clicked.connect(self.Enter)
        self.enter.setShortcut("Return")

        self.reload = QtGui.QPushButton("↻",self)
        self.reload.setMinimumSize(35,30)
        self.reload.setShortcut("F5")
        self.reload.setStyleSheet("font-size:23px;")
        self.reload.clicked.connect(self.Reload)

        self.back = QtGui.QPushButton("◀",self)
        self.back.setMinimumSize(35,30)
        self.back.setStyleSheet("font-size:23px;")
        self.back.clicked.connect(self.Back)

        self.forw = QtGui.QPushButton("▶",self)
        self.forw.setMinimumSize(35,30)
        self.forw.setStyleSheet("font-size:23px;")
        self.forw.clicked.connect(self.Forward)

        self.book = QtGui.QPushButton("☆",self)
        self.book.setMinimumSize(35,30)
        self.book.clicked.connect(self.Bookmark)
        self.book.setStyleSheet("font-size:18px;")

        self.pbar = QtGui.QProgressBar()
        self.pbar.setMaximumWidth(120)

        self.web = QWebView(loadProgress = self.pbar.setValue, loadFinished = self.pbar.hide, loadStarted = self.pbar.show, titleChanged = self.setWindowTitle)
        self.web.setMinimumSize(1360,700)

        self.list = QtGui.QComboBox(self)
        self.list.setMinimumSize(35,30)

        for i in bookmarks:
            self.list.addItem(i)

        self.list.activated[str].connect(self.handleBookmarks)
        self.list.view().setSizePolicy(QtGui.QSizePolicy.Minimum,QtGui.QSizePolicy.Minimum)

        self.web.urlChanged.connect(self.UrlChanged)
        
        self.web.page().linkHovered.connect(self.LinkHovered)

        grid = QtGui.QGridLayout()

        grid.addWidget(self.back,0,0, 1, 1)
        grid.addWidget(self.line,0,3, 1, 1)
        grid.addWidget(self.book,0,4, 1, 1)
        grid.addWidget(self.forw,0,1, 1, 1)
        grid.addWidget(self.reload,0,2, 1, 1)
        grid.addWidget(self.list,0,5, 1, 1)
        grid.addWidget(self.web, 2, 0, 1, 6)

        self.centralwidget.setLayout(grid)

Firstly, import the global variable bookmarks that we created before, which holds the (now non-existent) bookmarks. Because grid layouts only work on QWidget and not on QMainWindow, we simply create self.centralwidget and do everything on there. Then, we need to create the buttons. Most settings are self-explanatory, I simply used unicode symbols as icons (neat trick). Some aren't:

self.enter = QtGui.QPushButton(self)
self.enter.resize(0,0)
self.enter.clicked.connect(self.Enter)
self.enter.setShortcut("Return")

If you tried out the code, you will have noticed that there is no "GO" button, to enter the link. I sort of modeled this browser on google chrome, which also doesn't have one, since it's much easier to just hit enter. Since we can't set a shortcut to the QLineEdit, we have to create a sort of imaginary button, which is like a GO button, just that it's resized to 0 x 0 pixels, and therefore is not visible. Connect this button to the function self.Enter, which is this program's main funtion, and set the Shortcut to the Return button. Problem solved :-)

self.pbar = QtGui.QProgressBar()
self.pbar.setMaximumWidth(120)

Create a progress bar and set it's maximum width to 120.


self.web = QWebView(loadProgress = self.pbar.setValue, loadFinished = self.pbar.hide, loadStarted = self.pbar.show, titleChanged = self.setWindowTitle)
self.web.setMinimumSize(1360,700)

Create the QWebView and assign some variables in it's arguments (didn't know this was possible actually). These variables hold the values of the web page's loading progress, which we will need later on for the progress bar.

self.list = QtGui.QComboBox(self)
 self.list.setMinimumSize(35,30)

for i in bookmarks:
        self.list.addItem(i)

self.list.activated[str].connect(self.handleBookmarks)
self.list.view().setSizePolicy(QtGui.QSizePolicy.Minimum,QtGui.QSizePolicy.Minimum)

Here we create a combo box, which will store the bookmarks. Once bookmarks.txt will actually contain some bookmarks, they will individually be added to the combo box. Then, we need to connect the combo box to a function that handles the user clicking on one of the bookmarks, so connect it to the function self.handleBookmarks, which we will take care of later. Lastly, we need to adjust the combo box's drop down menu, since we want it to display the full links while the combo box button itself should stay at it's original size. This is done by acessing the combo box's drop down menu with .view() and changing it's size policy a bit. I actually have no clue why the size policy needs to be in there twice but I just tried and it worked whereas it wouldn't with only one policy, so yey.

  Before adding all the buttons to the layout, we need to connect two of QWebView's signals to function slots, which we will create later:

self.web.urlChanged.connect(self.UrlChanged)
        
self.web.page().linkHovered.connect(self.LinkHovered)

The first is activated when the url is changed, the second when any link is hovered.

Lastly, create a grid layout and add all the button's to it. Then make it self.centralwidget's AND NOT self's, so the main window's, layout.

Before making all the functions, create a statusbar, add the progressbar to it, and hide it for now. Also set self.centralwidget to the main windows's central widget:

self.status = self.statusBar()
self.status.addPermanentWidget(self.pbar)
self.status.hide()

self.setCentralWidget(self.centralwidget)

THE FUNCTIONS (MUAHAHA)

Now to the functions, which just sounds sort of evil, you know:




Anyway, the functions:

def Enter(self):
        global url
        global bookmarks
        
        url = self.line.text()

        http = "http://"
        
        if http not in url:
            url = http + url

        #You can delete this if you want

        elif "." not in url:
            url = "http://www.google.com/search?q="+url

        self.web.load(QtCore.QUrl(url))

        if url in bookmarks:
            self.book.setText("★")
            
        else:
            self.book.setText("☆")
            
        self.status.show()


The Enter function, which we assigned to self.enter before, takes care of opening the webpage. We call global url and bookmarks since we will need the values stored in them. Next, we fetch the url from self.line using self.line.text. The variable http is just for convenience. What follows, is an if clause, taking care of the different types of links the user inputs. Since nobody really ever types "http://" when typing a url in their webbrowser, we need to add that manually, as QWebKit needs a valid link. The other condition is my attempt at doing a direct search without having to go into google, but it's not a good one, since sometimes queries do involve dots ("F.Scott Fitzgerald") and not everybody uses Google, so delete it if you want.
Then, we load the website using self.web's load function. Also, it looks nice if you see whether or not a webpage is in your bookmarks, so we use the black unicode star instead of the white one for the bookmarking icon if the url is in the bookmarks list. Lastly, we show the statusbar.


def Bookmark(self):
        global url
        global bookmarks

        bookmarks.append(url)

        b = open("bookmarks.txt","wb")
        pickle.dump(bookmarks,b)
        b.close()
        
        self.list.addItem(url)
        self.book.setText("★")

 def handleBookmarks(self,choice):
        global url

        url = choice
        self.line.setText(url)
        self.Enter()

The second function is the one involving bookmarking. First, call the global variables. Next, we append the current url to the bookmarks list, dump it into our bookmarks.txt file, and add it to the combo box. Lastly, we set the icon star to the black one again.

HandleBookmarks is the function that is activated once a link in our bookmarks combo box is activated. This link is stored in the variable choice, which handleBookmarks needs as a parameter. So set the global variable url to this choice, show it in self.line and then just call the Enter function again.

 The following three functions don't really need to be explained, they simply do what they're named after.

The last two are overloaded functions, which we connected earlier. LinkHovered takes the parameter l (which holds the currently hovered url) and is activated whenever a link is hovered. We display this url in the status bar.

That's it! You could actually use this browser as an incognito, or private, browser, since we have no button for displaying the browsing history (you can make one). Private browsers are usually used for browsing privates. Cheers!

No comments :

Post a Comment