A4

Merge lp:~andrea.corbellini/a4/display-image into lp:a4

Proposed by Andrea Corbellini
Status: Merged
Merged at revision: 2
Proposed branch: lp:~andrea.corbellini/a4/display-image
Merge into: lp:a4
Diff against target: 289 lines (+266/-0)
4 files modified
a4 (+5/-0)
a4lib/app.py (+113/-0)
a4lib/svgimage.py (+28/-0)
ui/window_main.glade (+120/-0)
To merge this branch: bzr merge lp:~andrea.corbellini/a4/display-image
Reviewer Review Type Date Requested Status
Andrea Gasparini Approve
Review via email: mp+26180@code.launchpad.net

Description of the change

This branch is nothing special. It just adds some code to open and display SVG images. The UI is composed of three main widgets: the menu bar, the drawing area and the status bar. I haven't added a toolbar to reserve more space to the drawing area, we may add it later if it turns out to be useful.

This code is fully PEP-8 compliant and almost all the exceptional cases are threated correctly (see the TODOs).

To post a comment you must log in.
Revision history for this message
Andrea Gasparini (gaspa) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'a4'
2--- a4 2010-05-21 14:30:23 +0000
3+++ a4 2010-05-27 14:33:28 +0000
4@@ -1,3 +1,8 @@
5 #!/usr/bin/python
6 # Copyright 2010 A4 Developers. This software is licensed under the
7 # GNU General Public License version 3 (see the file LICENSE).
8+
9+from a4lib.app import main
10+
11+if __name__ == '__main__':
12+ main()
13
14=== added file 'a4lib/app.py'
15--- a4lib/app.py 1970-01-01 00:00:00 +0000
16+++ a4lib/app.py 2010-05-27 14:33:28 +0000
17@@ -0,0 +1,113 @@
18+# Copyright 2010 A4 Developers. This software is licensed under the
19+# GNU General Public License version 3 (see the file LICENSE).
20+
21+"""The ingress point of the application."""
22+
23+import optparse
24+import os
25+import gtk
26+from a4lib.svgimage import SVGImage, SVGImageError
27+
28+class WindowMain(object):
29+ """The main window of the application."""
30+
31+ def __init__(self):
32+ self.svg_image = None
33+ self.builder = gtk.Builder()
34+ # TODO Look into '/usr/share' too.
35+ self.builder.add_from_file('ui/window_main.glade')
36+ self.builder.connect_signals(self)
37+
38+ self.gtk_window = self.builder.get_object('window_main')
39+ self.gtk_window.show_all()
40+ self.drawing_area = self.builder.get_object('drawing_area').window
41+
42+ def set_status(self, status):
43+ """Put the given string in the status bar."""
44+ label = self.builder.get_object('label_status')
45+ label.set_text(status)
46+
47+ def open_file(self, file_name):
48+ """Open a file inside this window."""
49+ self.set_status('Loading {0}...'.format(file_name))
50+ try:
51+ # Try to open a file.
52+ image = SVGImage(file_name)
53+ except SVGImageError as error:
54+ # The file doesn't exist, or is not an SVG file. Show an error
55+ # dialog and don't do anything else.
56+ dialog = gtk.MessageDialog(
57+ self.gtk_window, gtk.DIALOG_DESTROY_WITH_PARENT,
58+ gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE,
59+ 'Cannot open {0!r}: {1[0]}'.format(file_name, error.args))
60+ dialog.run()
61+ dialog.destroy()
62+ else:
63+ # The file was OK. Set up the environment.
64+ widget = self.builder.get_object('drawing_area')
65+ widget.set_size_request(image.width + 20, image.height + 20)
66+ self.svg_image = image
67+ finally:
68+ # Restore the status string.
69+ self.set_status('')
70+
71+ def on_open_clicked(self, widget):
72+ """Even called when the 'Open' button is clicked."""
73+ # Set up the file chooser dialog.
74+ dialog = gtk.FileChooserDialog(
75+ None, None, gtk.FILE_CHOOSER_ACTION_OPEN, (
76+ gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
77+ gtk.STOCK_OPEN, gtk.RESPONSE_OK)
78+ )
79+ dialog.set_default_response(gtk.RESPONSE_OK)
80+ # If an another file has been opened, use its location as starting
81+ # folder for the dialog.
82+ if self.svg_image is not None:
83+ path = os.path.dirname(self.svg_image.file_name)
84+ dialog.set_current_folder(path)
85+
86+ # Show the dialog, back up the relevant properties and destroy it.
87+ response = dialog.run()
88+ file_name = dialog.get_filename()
89+ dialog.destroy()
90+ # Open the file, if necessary.
91+ if response == gtk.RESPONSE_OK:
92+ self.open_file(file_name)
93+
94+ def on_drawing_area_expose(self, widget, event):
95+ """This method is called everytime the drawing area should be redrawn.
96+ """
97+ image = self.svg_image
98+ if image is None:
99+ return
100+ context = self.drawing_area.cairo_create()
101+
102+ context.rectangle(
103+ event.area.x, event.area.y, event.area.width, event.area.height)
104+ context.clip()
105+ context.set_source_rgb(1, 1, 1)
106+ context.paint()
107+
108+ width, height = self.drawing_area.get_size()
109+ context.translate(
110+ (width - image.width) / 2, (height - image.height) / 2)
111+ image.render_cairo(context)
112+
113+ def quit(self, widget=None):
114+ """Close the window and quit the application."""
115+ gtk.main_quit()
116+
117+
118+def parse_arguments(args=None):
119+ """Parse the command line arguments."""
120+ parser = optparse.OptionParser()
121+ return parser.parse_args(args)
122+
123+def main(args=None):
124+ """Start the application."""
125+ options, files = parse_arguments(args)
126+ window = WindowMain()
127+ if files:
128+ # TODO What if more than one file is given?
129+ window.open_file(files[0])
130+ gtk.main()
131
132=== added file 'a4lib/svgimage.py'
133--- a4lib/svgimage.py 1970-01-01 00:00:00 +0000
134+++ a4lib/svgimage.py 2010-05-27 14:33:28 +0000
135@@ -0,0 +1,28 @@
136+# Copyright 2010 A4 Developers. This software is licensed under the
137+# GNU General Public License version 3 (see the file LICENSE).
138+
139+"""Code to deal with SVG images."""
140+
141+import rsvg
142+from glib import GError
143+
144+
145+class SVGImageError(StandardError):
146+ """Error raised when an operation with an image fails."""
147+
148+
149+class SVGImage(object):
150+ """An object that represents an image contained in a SVG file."""
151+
152+ def __init__(self, file_name):
153+ self.file_name = file_name
154+ try:
155+ svg_data = open(file_name).read()
156+ self._svg_handler = rsvg.Handle(data=svg_data)
157+ except IOError as error:
158+ raise SVGImageError(error.args[1])
159+ except GError as error:
160+ raise SVGImageError('Unknown file type')
161+ self.width = self._svg_handler.get_property('width')
162+ self.height = self._svg_handler.get_property('height')
163+ self.render_cairo = self._svg_handler.render_cairo
164
165=== added directory 'ui'
166=== added file 'ui/window_main.glade'
167--- ui/window_main.glade 1970-01-01 00:00:00 +0000
168+++ ui/window_main.glade 2010-05-27 14:33:28 +0000
169@@ -0,0 +1,120 @@
170+<?xml version="1.0"?>
171+<interface>
172+ <requires lib="gtk+" version="2.16"/>
173+ <!-- interface-naming-policy project-wide -->
174+ <object class="GtkWindow" id="window_main">
175+ <property name="title" translatable="yes">A4</property>
176+ <property name="icon_name">emblem-documents</property>
177+ <signal name="destroy" handler="quit"/>
178+ <child>
179+ <object class="GtkVBox" id="vbox1">
180+ <property name="visible">True</property>
181+ <child>
182+ <object class="GtkMenuBar" id="menubar1">
183+ <property name="visible">True</property>
184+ <child>
185+ <object class="GtkMenuItem" id="menuitem_file">
186+ <property name="visible">True</property>
187+ <property name="label" translatable="yes">_File</property>
188+ <property name="use_underline">True</property>
189+ <child type="submenu">
190+ <object class="GtkMenu" id="menu_file">
191+ <property name="visible">True</property>
192+ <child>
193+ <object class="GtkImageMenuItem" id="imagemenuitem_open">
194+ <property name="label">gtk-open</property>
195+ <property name="visible">True</property>
196+ <property name="use_underline">True</property>
197+ <property name="use_stock">True</property>
198+ <signal name="activate" handler="on_open_clicked"/>
199+ </object>
200+ </child>
201+ <child>
202+ <object class="GtkSeparatorMenuItem" id="separatormenuitem1">
203+ <property name="visible">True</property>
204+ </object>
205+ </child>
206+ <child>
207+ <object class="GtkImageMenuItem" id="imagemenuitem_quit">
208+ <property name="label">gtk-quit</property>
209+ <property name="visible">True</property>
210+ <property name="use_underline">True</property>
211+ <property name="use_stock">True</property>
212+ <signal name="activate" handler="quit"/>
213+ </object>
214+ </child>
215+ </object>
216+ </child>
217+ </object>
218+ </child>
219+ <child>
220+ <object class="GtkMenuItem" id="menuitem_help">
221+ <property name="visible">True</property>
222+ <property name="label" translatable="yes">_Help</property>
223+ <property name="use_underline">True</property>
224+ <child type="submenu">
225+ <object class="GtkMenu" id="menu_help">
226+ <property name="visible">True</property>
227+ <child>
228+ <object class="GtkImageMenuItem" id="imagemenuitem_about">
229+ <property name="label">gtk-about</property>
230+ <property name="visible">True</property>
231+ <property name="use_underline">True</property>
232+ <property name="use_stock">True</property>
233+ </object>
234+ </child>
235+ </object>
236+ </child>
237+ </object>
238+ </child>
239+ </object>
240+ <packing>
241+ <property name="expand">False</property>
242+ <property name="position">0</property>
243+ </packing>
244+ </child>
245+ <child>
246+ <object class="GtkScrolledWindow" id="scrolledwindow1">
247+ <property name="visible">True</property>
248+ <property name="can_focus">True</property>
249+ <property name="hscrollbar_policy">automatic</property>
250+ <property name="vscrollbar_policy">automatic</property>
251+ <child>
252+ <object class="GtkViewport" id="viewport1">
253+ <property name="visible">True</property>
254+ <property name="resize_mode">queue</property>
255+ <child>
256+ <object class="GtkDrawingArea" id="drawing_area">
257+ <property name="visible">True</property>
258+ <signal name="expose_event" handler="on_drawing_area_expose"/>
259+ </object>
260+ </child>
261+ </object>
262+ </child>
263+ </object>
264+ <packing>
265+ <property name="position">1</property>
266+ </packing>
267+ </child>
268+ <child>
269+ <object class="GtkStatusbar" id="statusbar1">
270+ <property name="visible">True</property>
271+ <child>
272+ <object class="GtkLabel" id="label_status">
273+ <property name="visible">True</property>
274+ </object>
275+ <packing>
276+ <property name="expand">False</property>
277+ <property name="position">0</property>
278+ </packing>
279+ </child>
280+ </object>
281+ <packing>
282+ <property name="expand">False</property>
283+ <property name="position">2</property>
284+ </packing>
285+ </child>
286+ </object>
287+ </child>
288+ </object>
289+</interface>

Subscribers

People subscribed via source and target branches