#+TITLE: GTK-3 Example downloader using a listbox
#+DATE: 2014-05-01 12:00:00 UTC
#+DESCRIPTION: Populate a listbox with custom widgets, in this case an example file downloader could be the start of a plugin installer.
#+FILETAGS: GTK-3:python:widgets:application:requests 
#+CATEGORY: python
#+SLUG: 13-example-list-box-downloader
#+THUMBNAIL: ../../../images/gtk/tut13-listbox.png
#+BEGIN_COMMENT
.. title: GTK-3 Example downloader using a listbox
.. slug: 13-example-list-box-downloader
.. date: 2014-05-01 12:00:00 UTC
.. tags: GTK-3, python, widgets, application, requests
.. category: python
.. description: Populate a listbox with custom widgets, in this case an example file downloader could be the start of a plugin installer.
.. type: text
#+END_COMMENT

#+CAPTION: File downloader
[[../../../images/gtk/tut13-listbox.png]]


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.

#+BEGIN_SRC python
#!/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()
#+END_SRC
