rss_feed

GTK-3 Example downloader using a listbox

homeHome
pagesGTK-3
pagespython
pageswidgets
pagesrequests

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.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
#!/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()