a4-dev team mailing list archive
-
a4-dev team
-
Mailing list archive
-
Message #00020
[Merge] lp:~andrea-bs/a4/display-image into lp:a4
Andrea Corbellini has proposed merging lp:~andrea-bs/a4/display-image into lp:a4.
Requested reviews:
A4 development (a4-dev)
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).
--
https://code.launchpad.net/~andrea-bs/a4/display-image/+merge/26180
Your team A4 development is requested to review the proposed merge of lp:~andrea-bs/a4/display-image into lp:a4.
=== modified file 'a4'
--- a4 2010-05-21 14:30:23 +0000
+++ a4 2010-05-27 14:33:28 +0000
@@ -1,3 +1,8 @@
#!/usr/bin/python
# Copyright 2010 A4 Developers. This software is licensed under the
# GNU General Public License version 3 (see the file LICENSE).
+
+from a4lib.app import main
+
+if __name__ == '__main__':
+ main()
=== added file 'a4lib/app.py'
--- a4lib/app.py 1970-01-01 00:00:00 +0000
+++ a4lib/app.py 2010-05-27 14:33:28 +0000
@@ -0,0 +1,113 @@
+# Copyright 2010 A4 Developers. This software is licensed under the
+# GNU General Public License version 3 (see the file LICENSE).
+
+"""The ingress point of the application."""
+
+import optparse
+import os
+import gtk
+from a4lib.svgimage import SVGImage, SVGImageError
+
+class WindowMain(object):
+ """The main window of the application."""
+
+ def __init__(self):
+ self.svg_image = None
+ self.builder = gtk.Builder()
+ # TODO Look into '/usr/share' too.
+ self.builder.add_from_file('ui/window_main.glade')
+ self.builder.connect_signals(self)
+
+ self.gtk_window = self.builder.get_object('window_main')
+ self.gtk_window.show_all()
+ self.drawing_area = self.builder.get_object('drawing_area').window
+
+ def set_status(self, status):
+ """Put the given string in the status bar."""
+ label = self.builder.get_object('label_status')
+ label.set_text(status)
+
+ def open_file(self, file_name):
+ """Open a file inside this window."""
+ self.set_status('Loading {0}...'.format(file_name))
+ try:
+ # Try to open a file.
+ image = SVGImage(file_name)
+ except SVGImageError as error:
+ # The file doesn't exist, or is not an SVG file. Show an error
+ # dialog and don't do anything else.
+ dialog = gtk.MessageDialog(
+ self.gtk_window, gtk.DIALOG_DESTROY_WITH_PARENT,
+ gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE,
+ 'Cannot open {0!r}: {1[0]}'.format(file_name, error.args))
+ dialog.run()
+ dialog.destroy()
+ else:
+ # The file was OK. Set up the environment.
+ widget = self.builder.get_object('drawing_area')
+ widget.set_size_request(image.width + 20, image.height + 20)
+ self.svg_image = image
+ finally:
+ # Restore the status string.
+ self.set_status('')
+
+ def on_open_clicked(self, widget):
+ """Even called when the 'Open' button is clicked."""
+ # Set up the file chooser dialog.
+ dialog = gtk.FileChooserDialog(
+ None, None, gtk.FILE_CHOOSER_ACTION_OPEN, (
+ gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
+ gtk.STOCK_OPEN, gtk.RESPONSE_OK)
+ )
+ dialog.set_default_response(gtk.RESPONSE_OK)
+ # If an another file has been opened, use its location as starting
+ # folder for the dialog.
+ if self.svg_image is not None:
+ path = os.path.dirname(self.svg_image.file_name)
+ dialog.set_current_folder(path)
+
+ # Show the dialog, back up the relevant properties and destroy it.
+ response = dialog.run()
+ file_name = dialog.get_filename()
+ dialog.destroy()
+ # Open the file, if necessary.
+ if response == gtk.RESPONSE_OK:
+ self.open_file(file_name)
+
+ def on_drawing_area_expose(self, widget, event):
+ """This method is called everytime the drawing area should be redrawn.
+ """
+ image = self.svg_image
+ if image is None:
+ return
+ context = self.drawing_area.cairo_create()
+
+ context.rectangle(
+ event.area.x, event.area.y, event.area.width, event.area.height)
+ context.clip()
+ context.set_source_rgb(1, 1, 1)
+ context.paint()
+
+ width, height = self.drawing_area.get_size()
+ context.translate(
+ (width - image.width) / 2, (height - image.height) / 2)
+ image.render_cairo(context)
+
+ def quit(self, widget=None):
+ """Close the window and quit the application."""
+ gtk.main_quit()
+
+
+def parse_arguments(args=None):
+ """Parse the command line arguments."""
+ parser = optparse.OptionParser()
+ return parser.parse_args(args)
+
+def main(args=None):
+ """Start the application."""
+ options, files = parse_arguments(args)
+ window = WindowMain()
+ if files:
+ # TODO What if more than one file is given?
+ window.open_file(files[0])
+ gtk.main()
=== added file 'a4lib/svgimage.py'
--- a4lib/svgimage.py 1970-01-01 00:00:00 +0000
+++ a4lib/svgimage.py 2010-05-27 14:33:28 +0000
@@ -0,0 +1,28 @@
+# Copyright 2010 A4 Developers. This software is licensed under the
+# GNU General Public License version 3 (see the file LICENSE).
+
+"""Code to deal with SVG images."""
+
+import rsvg
+from glib import GError
+
+
+class SVGImageError(StandardError):
+ """Error raised when an operation with an image fails."""
+
+
+class SVGImage(object):
+ """An object that represents an image contained in a SVG file."""
+
+ def __init__(self, file_name):
+ self.file_name = file_name
+ try:
+ svg_data = open(file_name).read()
+ self._svg_handler = rsvg.Handle(data=svg_data)
+ except IOError as error:
+ raise SVGImageError(error.args[1])
+ except GError as error:
+ raise SVGImageError('Unknown file type')
+ self.width = self._svg_handler.get_property('width')
+ self.height = self._svg_handler.get_property('height')
+ self.render_cairo = self._svg_handler.render_cairo
=== added directory 'ui'
=== added file 'ui/window_main.glade'
--- ui/window_main.glade 1970-01-01 00:00:00 +0000
+++ ui/window_main.glade 2010-05-27 14:33:28 +0000
@@ -0,0 +1,120 @@
+<?xml version="1.0"?>
+<interface>
+ <requires lib="gtk+" version="2.16"/>
+ <!-- interface-naming-policy project-wide -->
+ <object class="GtkWindow" id="window_main">
+ <property name="title" translatable="yes">A4</property>
+ <property name="icon_name">emblem-documents</property>
+ <signal name="destroy" handler="quit"/>
+ <child>
+ <object class="GtkVBox" id="vbox1">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkMenuBar" id="menubar1">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkMenuItem" id="menuitem_file">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_File</property>
+ <property name="use_underline">True</property>
+ <child type="submenu">
+ <object class="GtkMenu" id="menu_file">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkImageMenuItem" id="imagemenuitem_open">
+ <property name="label">gtk-open</property>
+ <property name="visible">True</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ <signal name="activate" handler="on_open_clicked"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSeparatorMenuItem" id="separatormenuitem1">
+ <property name="visible">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="imagemenuitem_quit">
+ <property name="label">gtk-quit</property>
+ <property name="visible">True</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ <signal name="activate" handler="quit"/>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuItem" id="menuitem_help">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Help</property>
+ <property name="use_underline">True</property>
+ <child type="submenu">
+ <object class="GtkMenu" id="menu_help">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkImageMenuItem" id="imagemenuitem_about">
+ <property name="label">gtk-about</property>
+ <property name="visible">True</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">automatic</property>
+ <property name="vscrollbar_policy">automatic</property>
+ <child>
+ <object class="GtkViewport" id="viewport1">
+ <property name="visible">True</property>
+ <property name="resize_mode">queue</property>
+ <child>
+ <object class="GtkDrawingArea" id="drawing_area">
+ <property name="visible">True</property>
+ <signal name="expose_event" handler="on_drawing_area_expose"/>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkStatusbar" id="statusbar1">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkLabel" id="label_status">
+ <property name="visible">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+</interface>