treating the error generated when the user tries to use
[cascardo/movie.git] / gzv.py
1 # gzv.py - an user interface to generate-zooming-video
2 #
3 # Copyright (C) 2008  Lincoln de Sousa <lincoln@minaslivre.org>
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation, either version 3 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14
15 import os
16 import gtk
17 import gtk.glade
18 import gobject
19 import math
20 import cairo
21 from ConfigParser import ConfigParser
22
23 _ = lambda x:x
24
25 class Ball(object):
26     DEFAULT_WIDTH = 10
27
28     def __init__(self, x, y, r, name='', position=0):
29         self.position = position
30         self.x = int(x)
31         self.y = int(y)
32         self.radius = int(r)
33         self.name = name
34
35 class BallManager(list):
36     def __init__(self, *args, **kwargs):
37         super(BallManager, self).__init__(*args, **kwargs)
38
39     def save_to_file(self, path):
40         target = open(path, 'w')
41         for i in self:
42             target.write('%d,%d %d %s\n' % (i.x, i.y, i.radius, i.name))
43         target.close()
44
45 class GladeLoader(object):
46     def __init__(self, fname, root=''):
47         self.ui = gtk.glade.XML(fname, root)
48         self.ui.signal_autoconnect(self)
49
50     def get_widget(self, wname):
51         return self.ui.get_widget(wname)
52
53     # little shortcut
54     wid = get_widget
55
56     # glade callbacks
57
58     def gtk_widget_show(self, widget, *args):
59         widget.show()
60         return True
61
62     def gtk_widget_hide(self, widget, *args):
63         widget.hide()
64         return True
65
66     def gtk_main_quit(self, *args):
67         gtk.main_quit()
68
69     def gtk_main(self, *args):
70         gtk.main()
71
72 class Project(object):
73     def __init__(self, image, width, height):
74         self.image = image
75         self.width = width
76         self.height = height
77         self.focus_points_file = ''
78
79     def save_to_file(self, path):
80         bn = os.path.basename(path)
81         name = os.path.splitext(bn)[0]
82
83         cp = ConfigParser()
84         cp.set('Project', 'image', self.image)
85         cp.set('Project', 'width', self.width)
86         cp.set('Project', 'height', self.height)
87         cp.set('Project', 'focus_points', self.focus_points_file)
88         
89         cp.write(open(path, 'w'))
90
91     @staticmethod
92     def parse_file(path):
93         cp = ConfigParser()
94         cp.read(path)
95
96         image = cp.get('Project', 'image')
97         width = cp.getint('Project', 'width')
98         height = cp.getint('Project', 'height')
99         x = cp.getint('Project', 'height')
100
101         proj = Project(image, width, height)
102         proj.focus_points_file = cp.get('Project', 'focus_points')
103
104         return proj
105
106 class NewProject(GladeLoader):
107     def __init__(self, parent=None):
108         super(NewProject, self).__init__('gzv.glade', 'new-project')
109         self.dialog = self.wid('new-project')
110         if parent:
111             self.dialog.set_transient_for(parent)
112
113     def get_project(self):
114         fname = self.wid('image').get_filename()
115         width = self.wid('width').get_text()
116         height = self.wid('height').get_text()
117         return Project(fname, width, height)
118
119     def destroy(self):
120         self.dialog.destroy()
121
122 class Gzv(GladeLoader):
123     def __init__(self):
124         super(Gzv, self).__init__('gzv.glade', 'main-window')
125         self.window = self.wid('main-window')
126         self.window.connect('delete-event', lambda *x: gtk.main_quit())
127
128         self.evtbox = self.wid('eventbox')
129         self.evtbox.connect('button-press-event', self.button_press)
130         self.evtbox.connect('button-release-event', self.button_release)
131         self.evtbox.connect('motion-notify-event', self.motion_notify)
132
133         self.model = gtk.ListStore(int, str)
134         self.treeview = self.wid('treeview')
135         self.treeview.set_model(self.model)
136
137         self.draw = self.wid('draw')
138         self.draw.connect('expose-event', self.expose_draw)
139
140         # Starting with an empty project with no image loaded
141         self.project = None
142         self.image = None
143
144         # This attr may be overriten, if so, call the method (load_balls_to_treeview)
145         self.balls = BallManager()
146
147         self.load_balls_to_treeview()
148         self.setup_treeview()
149
150         # drawing stuff
151         self.selecting = False
152         self.start_x = -1
153         self.start_y = -1
154         self.radius = Ball.DEFAULT_WIDTH
155
156     def setup_treeview(self):
157         self.model.connect('rows-reordered', self.on_rows_reordered)
158
159         renderer = gtk.CellRendererText()
160         column = gtk.TreeViewColumn(_('Position'), renderer, text=0)
161         column.set_property('visible', False)
162         self.treeview.append_column(column)
163
164         renderer = gtk.CellRendererText()
165         renderer.connect('edited', self.on_cell_edited)
166         renderer.set_property('editable', True)
167         self.fpcolumn = gtk.TreeViewColumn(_('Name'), renderer, text=1)
168         self.treeview.append_column(self.fpcolumn)
169
170     def on_rows_reordered(self, *args):
171         print 
172
173     def on_cell_edited(self, renderer, path, value):
174         self.balls[int(path)].name = value
175         self.load_balls_to_treeview()
176         self.draw.queue_draw()
177
178     def new_project(self, button):
179         proj = NewProject(self.window)
180
181         # This '1' was defined in the glade file
182         if proj.dialog.run() == 1:
183             self.load_project(proj.get_project())
184         proj.destroy()
185
186     def open_project(self, *args):
187         fc = gtk.FileChooserDialog(_('Choose a gzv project'), self.window,
188                                    buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
189                                             gtk.STOCK_OK, gtk.RESPONSE_OK))
190         if fc.run() == gtk.RESPONSE_OK:
191             proj_file = fc.get_filename()
192             self.load_project(Project.parse_file(proj_file))
193         fc.destroy()
194
195     def load_project(self, project):
196         self.project = project
197         self.balls = self.load_balls_from_file(project.focus_points_file)
198         self.image = project.image
199         self.load_balls_to_treeview()
200         self.draw.queue_draw()
201
202     def unload_project(self):
203         self.project = None
204         self.image = None
205         self.balls = BallManager()
206         self.draw.queue_draw()
207
208     def load_balls_to_treeview(self):
209         self.model.clear()
210         for i in self.balls:
211             self.model.append([i.position, i.name])
212
213     def load_balls_from_file(self, fname):
214         balls = BallManager()
215         if not os.path.exists(fname):
216             return balls
217
218         for index, line in enumerate(file(fname)):
219             if not line:
220                 continue
221             pos, radius, name = line.split(None, 2)
222             x, y = pos.split(',')
223             balls.append(Ball(x, y, radius, name.strip(), index))
224         return balls
225
226     def remove_fp(self, *args):
227         selection = self.treeview.get_selection()
228         model, path = selection.get_selected()
229         if path:
230             position = model[path][0]
231             for i in self.balls:
232                 if i.position == int(position):
233                     self.balls.remove(i)
234             del model[path]
235             self.draw.queue_draw()
236
237     def save_fp_list(self, *args):
238         assert self.project is not None
239
240         # if the project has no
241         if self.project and not self.project.focus_points_file:
242             fc = gtk.FileChooserDialog(_('Save the focus points file'),
243                                        self.window,
244                                        action=gtk.FILE_CHOOSER_ACTION_SAVE,
245                                        buttons=(gtk.STOCK_CANCEL,
246                                                 gtk.RESPONSE_CANCEL,
247                                                 gtk.STOCK_SAVE,
248                                                 gtk.RESPONSE_OK))
249             if fc.run() == gtk.RESPONSE_OK:
250                 self.project.focus_points_file = fc.get_filename()
251                 fc.destroy()
252             else:
253                 fc.destroy()
254                 return
255
256         self.balls.save_to_file(self.project.focus_points_file)
257
258     def expose_draw(self, draw, event):
259         if not self.image:
260             return
261
262         # loading the picture image and getting some useful
263         # information to draw it in the widget's background
264         try:
265             img = gtk.gdk.pixbuf_new_from_file(self.image)
266         except gobject.GError:
267             msg = _("Couldn't recognize the image file format.")
268             dialog = gtk.MessageDialog(self.window,
269                                        gtk.DIALOG_MODAL,
270                                        gtk.MESSAGE_ERROR,
271                                        gtk.BUTTONS_CLOSE)
272             dialog.set_markup(msg)
273             dialog.run()
274             dialog.destroy()
275
276             self.draw.stop_emission('expose-event')
277             return self.unload_project()
278
279         pixels = img.get_pixels()
280         rowstride = img.get_rowstride()
281         width = img.get_width()
282         height = img.get_height()
283         gc = draw.style.black_gc
284
285         # sets the correct size of the eventbox, to show the scrollbar
286         # when needed.
287         self.evtbox.set_size_request(width, height)
288
289         # drawing the picture in the background of the drawing area,
290         # this is really important.
291         draw.window.draw_rgb_image(gc, 0, 0, width, height,
292                                    'normal', pixels, rowstride,
293                                    0, 0)
294
295         # this call makes the ball being drown be shown correctly.
296         self.draw_current_ball()
297
298         # drawing other balls stored in the self.balls list.
299         ctx = draw.window.cairo_create()
300         ctx.fill()
301
302         ctx.set_line_width(10.0)
303         ctx.set_source_rgba(0.5, 0.0, 0.0, 0.4)
304
305         for i in self.balls:
306             ctx.arc(i.x, i.y, i.radius, 0, 64*math.pi)
307             ctx.fill()
308
309     def draw_current_ball(self):
310         if self.start_x < 0:
311             return
312         ctx = self.draw.window.cairo_create()
313         ctx.arc(self.start_x, self.start_y, self.radius, 0, 64*math.pi)
314         ctx.set_source_rgba(0.5, 0.0, 0.0, 0.4)
315         ctx.fill()
316
317     def button_press(self, widget, event):
318         if event.button == 1:
319             self.selecting = True
320             self.start_x = event.x
321             self.start_y = event.y
322             self.last_x = event.x
323
324     def button_release(self, widget, event):
325         if event.button == 1:
326             self.selecting = False
327             self.finish_drawing()
328
329     def motion_notify(self, widget, event):
330         self.draw.queue_draw()
331
332         if event.x > self.last_x:
333             self.radius += 3
334         else:
335             self.radius -= 3
336
337         self.last_x = event.x
338
339     def finish_drawing(self):
340         self.draw_current_ball()
341         self.ball_width = Ball.DEFAULT_WIDTH
342
343         position = len(self.balls)
344         ball = Ball(self.start_x, self.start_y, self.radius, '', position)
345         self.balls.append(ball)
346         self.model.append([position, ''])
347         self.treeview.set_cursor(str(position), self.fpcolumn, True)
348
349 if __name__ == '__main__':
350     Gzv().window.show_all()
351     gtk.main()