Merge lp:~brunogirin/python-snippets/webkit-easter-html into lp:~jonobacon/python-snippets/trunk

Proposed by Bruno Girin
Status: Merged
Merged at revision: not available
Proposed branch: lp:~brunogirin/python-snippets/webkit-easter-html
Merge into: lp:~jonobacon/python-snippets/trunk
Diff against target: 381 lines (+352/-0)
6 files modified
webkit/webkit_table-business.css (+30/-0)
webkit/webkit_table-colourful.css (+35/-0)
webkit/webkit_table-plain.css (+10/-0)
webkit/webkit_table-rounded.css (+34/-0)
webkit/webkit_table.csv (+17/-0)
webkit/webkit_table.py (+226/-0)
To merge this branch: bzr merge lp:~brunogirin/python-snippets/webkit-easter-html
Reviewer Review Type Date Requested Status
Jono Bacon Pending
Review via email: mp+22821@code.launchpad.net

Description of the change

Added a new Webkit snippet that:
- loads data from a CSV file
- displays the data in a HTML table using Webkit
- uses an external CSS file to style the table
- provides the user with a combo box to change the CSS style sheet

To post a comment you must log in.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'webkit/webkit_table-business.css'
2--- webkit/webkit_table-business.css 1970-01-01 00:00:00 +0000
3+++ webkit/webkit_table-business.css 2010-04-05 19:08:17 +0000
4@@ -0,0 +1,30 @@
5+body {
6+ color: black;
7+ background-color: white;
8+ font-size: 80%;
9+}
10+table {
11+ border-collapse: collapse;
12+ border-bottom: 2px solid black;
13+}
14+thead {
15+ color: white;
16+ background-color: black;
17+ font-weight: bold;
18+}
19+thead th {
20+ padding: 0 1em;
21+}
22+tbody {
23+ font-size: 80%;
24+}
25+tbody tr:nth-child(even) {
26+ background: #eee;
27+}
28+td.left {
29+ text-align: left;
30+}
31+td.right {
32+ text-align: right;
33+}
34+
35
36=== added file 'webkit/webkit_table-colourful.css'
37--- webkit/webkit_table-colourful.css 1970-01-01 00:00:00 +0000
38+++ webkit/webkit_table-colourful.css 2010-04-05 19:08:17 +0000
39@@ -0,0 +1,35 @@
40+body {
41+ color: black;
42+ background-color: white;
43+ font-size: 80%;
44+}
45+h1 {
46+ color: #B90091;
47+}
48+table {
49+ border-collapse: collapse;
50+}
51+thead {
52+ color: white;
53+ background-color: #B90091;
54+ font-weight: bold;
55+}
56+thead th {
57+ padding: 0 1em;
58+}
59+tbody {
60+ font-size: 80%;
61+}
62+tbody tr:nth-child(odd) {
63+ background: #48DD00;
64+}
65+tbody tr:nth-child(even) {
66+ background: #FFF100;
67+}
68+td.left {
69+ text-align: left;
70+}
71+td.right {
72+ text-align: right;
73+}
74+
75
76=== added file 'webkit/webkit_table-plain.css'
77--- webkit/webkit_table-plain.css 1970-01-01 00:00:00 +0000
78+++ webkit/webkit_table-plain.css 2010-04-05 19:08:17 +0000
79@@ -0,0 +1,10 @@
80+body {
81+ font-size: 80%;
82+}
83+td.left {
84+ text-align: left;
85+}
86+td.right {
87+ text-align: right;
88+}
89+
90
91=== added file 'webkit/webkit_table-rounded.css'
92--- webkit/webkit_table-rounded.css 1970-01-01 00:00:00 +0000
93+++ webkit/webkit_table-rounded.css 2010-04-05 19:08:17 +0000
94@@ -0,0 +1,34 @@
95+body {
96+ font-size: 80%;
97+ color: white;
98+}
99+h1 {
100+ background-color: #090;
101+ padding: 0.25em;
102+ border-radius: 0.5em;
103+}
104+div#content {
105+ background-color: #090;
106+ padding: 0.5em;
107+ border-radius: 1em;
108+}
109+table {
110+ border-collapse: collapse;
111+}
112+thead th {
113+ padding: 0 1em;
114+ font-weight: bold;
115+}
116+tbody {
117+ font-size: 80%;
118+}
119+tbody tr:nth-child(even) {
120+ background: #6c6;
121+}
122+td.left {
123+ text-align: left;
124+}
125+td.right {
126+ text-align: right;
127+}
128+
129
130=== added file 'webkit/webkit_table.csv'
131--- webkit/webkit_table.csv 1970-01-01 00:00:00 +0000
132+++ webkit/webkit_table.csv 2010-04-05 19:08:17 +0000
133@@ -0,0 +1,17 @@
134+"100 grams","Pure chocolate","Milk chocolate","White chocolate"
135+"Protein (g)",4.7,8.4,8
136+"Fat (g)",29.2,30.3,30.9
137+"Calories (kcal)",525,529,529
138+"Carbohydrate (g)",64.8,59.4,58.3
139+"Calcium (mg)",38,220,270
140+"Magnesium (mg)",100,55,26
141+"Iron (mg)",2.4,1.6,0.2
142+"Zink (mg)",0.2,0.2,0.9
143+"Vitamin A (µg)",40,40,75
144+"Vitamin E (mg)",0.85,0.74,1.14
145+"Vitamin B1 (mg)",0.07,0.1,0.08
146+"Vitamin B2 (mg)",0.08,0.23,0.49
147+"Vitamin B3 (mg)",0.4,0.2,0.2
148+"Vitamin B6 (mg)",0.07,0.07,0.07
149+"Vitamin B12 (µg)","-","trace","trace"
150+"Folate (µg)",10,10,10
151
152=== added file 'webkit/webkit_table.py'
153--- webkit/webkit_table.py 1970-01-01 00:00:00 +0000
154+++ webkit/webkit_table.py 2010-04-05 19:08:17 +0000
155@@ -0,0 +1,226 @@
156+#!/usr/bin/env python
157+#
158+# [SNIPPET_NAME: Webkit Table]
159+# [SNIPPET_CATEGORIES: Webkit, PyGTK, CSV]
160+# [SNIPPET_DESCRIPTION: Shows how to load tabular data into a Webkit view]
161+# [SNIPPET_AUTHOR: Bruno Girin <brunogirin@gmail.com>]
162+# [SNIPPET_LICENSE: GPL]
163+#
164+# This snippet was derived from Andy Breiner's "Webkit Button" snippet and
165+# Ryan Paul's article at Ars Technica:
166+# http://arstechnica.com/open-source/guides/2009/07/how-to-build-a-desktop-wysiwyg-editor-with-webkit-and-html-5.ars/
167+# It demonstrates how to create a HTML table from the content of a CSV file,
168+# display it in a Webkit view and handle change events from a GTK combo box to
169+# change the document's style sheet.
170+#
171+# The garish colours for the "Colourful" style were generated using:
172+# http://colorschemedesigner.com/
173+#
174+# It's Easter, so this snippet shows details about the nutritional information
175+# of chocolate, found here: http://www.chokladkultur.se/facts.htm
176+
177+import csv
178+import sys
179+
180+import gtk
181+import webkit
182+
183+class TableData:
184+ """
185+ Data model class that encapsulates the content of the CSV file.
186+
187+ This class reads the content of the CSV file, stores the first row as a
188+ header and the other rows as a list of list representing the content.
189+ """
190+ def __init__(self, csv_file):
191+ reader=csv.reader(open(csv_file))
192+ self.headers = []
193+ self.content = []
194+ for row in reader:
195+ if reader.line_num == 1:
196+ self.headers = row
197+ else:
198+ self.content.append(row)
199+
200+class TableView:
201+ """
202+ View class that displays the content of the data model class.
203+
204+ This class creates a HTML table from the data held in the model class
205+ and uses Webkit to display it. It also provides the user with a combo box
206+ to change the style used to display the table.
207+ """
208+ def delete_event(self, widget, event, data=None):
209+ """Handles delete events and ignores them."""
210+ return False
211+
212+ def destroy(self, widget, data=None):
213+ """Handles the destroy event and quits the application."""
214+ gtk.main_quit()
215+
216+ def __init__(self, file_stem, title, data):
217+ """
218+ Initialises the view class, creates a HTML document and wires events.
219+
220+ This is the main method in the view class. It initialises all elements
221+ of the view, creates a HTML document based on the data model and wires
222+ GTK and Webkit events to handling methods.
223+ """
224+ # Store the file stem
225+ self.file_stem = file_stem
226+
227+ # Setup the window properties
228+ self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
229+ self.window.set_resizable(True)
230+ self.window.connect("delete_event", self.delete_event)
231+ self.window.connect("destroy", self.destroy)
232+
233+ # Initialize webkit
234+ self.web = webkit.WebView()
235+
236+ # listen for clicks of links
237+ self.web.connect("navigation-requested", self.on_navigation_requested)
238+
239+ # the %s will be replaced later on
240+ self.template = """
241+ <html>
242+ <head>
243+ <style>
244+ {style}
245+ </style>
246+ </head>
247+ {body}
248+ </html>
249+ """
250+ self.body = """
251+ <body>
252+ <h1>{title}</h1>
253+ <div id="content">
254+ <table>
255+ <thead>
256+ {thead}
257+ </thead>
258+ <tbody>
259+ {tbody}
260+ </tbody>
261+ </table>
262+ </div>
263+ </body>
264+ """
265+ self.tr = """<tr>{content}</tr>"""
266+ self.th = """<th scope="{scope}">{content}</th>"""
267+ self.td = """<td class="{hclass}">{content}</td>"""
268+
269+ self.document_body = self.create_document_body(title, data)
270+ document = self.create_document('plain')
271+ # tell webkit to load local html and this is where the %s will get
272+ # replaced
273+ self.web.load_html_string(document, "file:///")
274+
275+ # Create the style combo box
276+ combobox = gtk.combo_box_new_text()
277+ combobox.append_text('Plain')
278+ combobox.append_text('Business')
279+ combobox.append_text('Rounded')
280+ combobox.append_text('Colourful')
281+ combobox.set_active(0)
282+ combobox.connect('changed', self.changed_style_combo)
283+
284+ # Create a scroll area and add the webkit item
285+ scroll = gtk.ScrolledWindow()
286+ scroll.add(self.web)
287+
288+ # Create a vbox and add the combo box and scroll area to it
289+ vbox = gtk.VBox()
290+ vbox.pack_start(combobox, False) # don't expand
291+ vbox.pack_start(scroll, True, True) # expand and fill
292+
293+ # add the vbox to the window and show all items on the window
294+ self.window.add(vbox)
295+ self.window.show_all()
296+ self.window.move(0, 10)
297+ self.window.resize(580, 350)
298+
299+ def create_document_body(self, title, data):
300+ """
301+ Create the document's body from the content of the data model.
302+
303+ This method creates the body of the document by inserting headers and
304+ body row elements in the core template.
305+ """
306+ # Create th nodes and wrap them in tr
307+ thead = self.tr.format(
308+ content = ''.join(
309+ [self.th.format(scope = 'col', content = h) for h in data.headers])
310+ )
311+ # Create td nodes, wrap the tr nodes and join them
312+ # The expression used to set the value of hclass is derived from:
313+ # http://code.activestate.com/recipes/52282-simulating-the-ternary-operator-in-python/
314+ # For more details on nested list comprehensions, as used below, see:
315+ # http://docs.python.org/tutorial/datastructures.html#nested-list-comprehensions
316+ tbody = '\n'.join(
317+ [self.tr.format(
318+ content = ''.join([self.td.format(
319+ hclass = (i>0 and 'right' or 'left'), content = d
320+ ) for i, d in enumerate(l)]))
321+ for l in data.content]
322+ )
323+ # Create the document body and return
324+ return self.body.format(
325+ title = title, thead = thead, tbody = tbody)
326+
327+ def create_document(self, style):
328+ """
329+ Create the complete document from the body and the style sheet.
330+
331+ This method creates the final document by reading the CSS style sheet
332+ file and inserting it along with the document body into the template.
333+ """
334+ # Load the style sheet
335+ f = open(
336+ '{stem}-{style}.css'.format(stem = self.file_stem, style = style), 'r')
337+ # Apply to the document and return
338+ return self.template.format(
339+ style = f.read(), body = self.document_body)
340+
341+ def changed_style_combo(self, combobox):
342+ """
343+ Change the style by re-creating the document from the combo's selection.
344+
345+ This method gets the current value out of the combo box, transforms it
346+ to lower case and uses the resulting value to re-create the complete
347+ document with the relevant CSS style sheet. It then re-displays the
348+ document using the Webkit view.
349+ """
350+ model = combobox.get_model()
351+ index = combobox.get_active()
352+ style = model[index][0].lower()
353+ print 'Changing style to {style}'.format(style = model[index][0])
354+ document = self.create_document(style)
355+ self.web.load_html_string(document, "file:///")
356+
357+ def on_navigation_requested(self, view, frame, req, data=None):
358+ """
359+ Describes what to do when a href link is clicked.
360+
361+ In this case, we ignore all navigation requests, as there are no
362+ clickable area in the document.
363+ """
364+ # As Ryan Paul stated he likes to use the prefix program:/
365+ uri = req.get_uri()
366+ if uri.startswith("program:/"):
367+ print uri.split("/")[1]
368+ else:
369+ return False
370+ return True
371+
372+ def main(self):
373+ """Start the main GTK thread."""
374+ gtk.main()
375+
376+if __name__ == "__main__":
377+ data = TableData(sys.argv[0].replace('.py', '.csv'))
378+ view = TableView(
379+ sys.argv[0].replace('.py', ''),
380+ "Chocolate's nutritional information", data)
381+ view.main()

Subscribers

People subscribed via source and target branches