GTK-3 Example downloader using a listbox



tut12-listbox.png

Figure 1: File downloader

A slightly more complex example, loading our gui from a glade file and dynamically add and remove widgets based on an xml file.

This is the start of a simple file downloader, it reads an xml file and creates a gui dynamically with download buttons for each element in the xml data to retrieve files.

#!/usr/bin/env python
import os
import requests
from io import StringIO, BytesIO
import subprocess
from lxml.html import parse
from gi.repository import Gtk, GLib, Gdk, GdkPixbuf


class application_gui:
    """Tutorial 13 custom treeview list boxes"""
    count = 0

    retrieve_job = None

    def __init__(self):
	#load in our glade interface
	xml = Gtk.Builder()
	xml.add_from_file('tut13.glade')

	#grab our widget using get_object this is the name of the widget from glade, window1 is the default name
	self.window = xml.get_object('winFetcher')

	#load our widgets from the glade file
	self.widgets = {}
	self.widgets['listbox'] = xml.get_object('listbox1')
	self.widgets['progress'] = xml.get_object('listProgress')
	self.widgets['refresh'] = xml.get_object('btnRefresh')
	self.widgets['refresh'].connect('button_press_event', self.refresh)
	self.widgets['close'] = xml.get_object('btnClose')
	self.widgets['close'].connect('button_press_event', self.closeFetcher)

	#wrap the listbox so we can reuse the code, pass in the listbox widget to our wrapper class
	self.listbox = ListBoxSelect(self.widgets['listbox'])

	#connect to events, in this instance just quit our application
	self.window.connect('delete_event', Gtk.main_quit)
	self.window.connect('destroy', lambda quit: Gtk.main_quit())

	#show the window else there is nothing to see :)
	self.openFetcher()
	self.refresh()

    def openFetcher(self):
	self.window.show_all()

    def refresh(self, *args):
	""" get a new xml and start the progress bar"""
	self.listbox.clear()
	self.widgets['progress'].show()
	self.retrieve_job = subprocess.Popen(
	    ['curl', 'file://%s/example.xml' % os.path.abspath('./')],
	    shell=False,
	    stdout=subprocess.PIPE,
	    stderr=subprocess.PIPE)
	GLib.timeout_add_seconds(1, self.update_active_progress_bar)

    def update_active_progress_bar(self):
	""" move the progress bar, when the subprocess returns handle the xml and hide the progress bar"""
	self.widgets['progress'].pulse()
	if self.retrieve_job.poll():
	    return True
	self.widgets['progress'].hide()
	self.update_list()
	return False

    def update_list(self):
	""" parse the xmland grab the elements we are intrested in"""
	nsmap = {'media': 'http://search.yahoo.com/mrss/'}
	results = BytesIO(self.retrieve_job.communicate()[0])
	doc = parse(results).getroot()
	for item in doc.iterfind(".//item", namespaces=nsmap):
	    title = item.find('title').text
	    link = item.find('link').tail
	    description = item.find('description').text
	    image = item.find('thumbnail', namespaces=nsmap).get('url')
	    self.listbox.model_append(image, title, description, link)

    def download(self):
	""" retrieve the xml file in a subprocess using curl """
	self.retrieve_job = subprocess.Popen(
	    ['curl', 'file://%s/example.xml' % os.path.abspath('./')],
	    shell=False,
	    stdout=subprocess.PIPE,
	    stderr=subprocess.PIPE)

    def closeFetcher(self, widget):
	self.window.hide()

class ListBoxSelect:
    """ handle the listbox rows dynamically add and remove widgets, and handle download. """
    listbox = None
    gui_rows = []  # store widgets here so we can destroy them later.

    def __init__(self, listbox):
	""" pass in list box to manage and connect event"""
	self.listbox = listbox
	self.listbox.connect('row-activated', self.selection)

    def selection(self, lbox, lbrow):
	""" row selected we may want to react here"""
	boxrow = lbrow.get_children()[0]
	boxinfo = boxrow.get_children()[1]
	print(boxinfo.get_children()[1].get_text())

    def model_append(self, image, title, description, link):
	""" create new widgets, and connect events for our new row"""
	items = {}
	items['row'] = Gtk.ListBoxRow()
	items['vbox'] = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
	items['label1'] = Gtk.Label(title, xalign=0)
	items['label2'] = Gtk.Label(link, xalign=0)
	items['progress'] = Gtk.ProgressBar()
	items['progress'].hide()
	items['progress'].set_fraction(0) 
	items['vbox'].pack_start(items['label1'], True, False, 0)
	items['vbox'].pack_start(items['label2'], True, False, 0)
	items['vbox'].pack_start(items['progress'], False, False, 0)

	items['hbox'] = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
	items['image'] = Gtk.Image.new_from_file(image)
	items['button'] = Gtk.Button(label="Download")
	items['button'].connect('button_press_event', self.download, items, link)

	items['hbox'].pack_start(items['image'], False, False, 0)
	items['hbox'].pack_start(items['vbox'], True, True, 0)
	items['button_box'] = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)

	items['button_box'].pack_end(items['button'], False, False, 0)
	items['hbox'].pack_start(items['button_box'], False, False, 0)
	items['row'].add(items['hbox'])

	self.listbox.add(items['row'])
	items['row'].show_all()

	self.gui_rows.append(items)

    def download(self, widget, args, items, link):
	""" download button click, change widgets and start the progress bar and download """
	items['button'].hide()
	items['job'] = subprocess.Popen(
	    ['curl', '-O', link],
	    shell=False,
	    stdout=subprocess.PIPE,
	    stderr=subprocess.PIPE)

	GLib.timeout_add_seconds(1, self.update_active_progress_bar, items)

    def update_active_progress_bar(self, widgets):
	""" update progress bar until command finished """
	widgets['progress'].pulse()
	if widgets['job'].poll():
	    return True
	widgets['progress'].hide()
	return False

    def clear(self):
	""" remove all rows so we can pre-populate"""
	for item in self.gui_rows:
	    item['row'].destroy()
	self.gui_rows = []

application = application_gui()
Gtk.main()

Comments