/gtk/prep-images

To get this branch, use:
bzr branch http://bzr.ed.am/gtk/prep-images

« back to all changes in this revision

Viewing changes to src/add_copyright_to_images/main_window.py

  • Committer: edam
  • Date: 2011-02-23 19:43:45 UTC
  • Revision ID: edam@waxworlds.org-20110223194345-f7ichg83dzo6m87x
- initial commit, includes project and build setup

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# main_window.py
 
2
#
 
3
# Copyright (C) 2011 Tim Marston <edam@waxworlds.org>
 
4
#
 
5
# This file is part of add-copyright-to-images (hereafter referred to as "this
 
6
# program").
 
7
# See http://www.waxworlds.org/edam/software/add-copyright-to-images for more
 
8
# information.
 
9
#
 
10
# This program is free software: you can redistribute it and/or modify
 
11
# it under the terms of the GNU Lesser General Public License as published
 
12
# by the Free Software Foundation, either version 3 of the License, or
 
13
# (at your option) any later version.
 
14
#
 
15
# This program is distributed in the hope that it will be useful,
 
16
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
17
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
18
# GNU Lesser General Public License for more details.
 
19
#
 
20
# You should have received a copy of the GNU Lesser General Public License
 
21
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
22
 
 
23
 
 
24
import sys
 
25
import os
 
26
import gobject
 
27
import pygtk
 
28
pygtk.require( '2.0' );
 
29
import gtk
 
30
from subprocess import call
 
31
from .preferences import *
 
32
from .prefs_dialog import *
 
33
 
 
34
 
 
35
class MainWindow:
 
36
 
 
37
        DRAWING_AREA_INIT_WIDTH = 550
 
38
        DRAWING_AREA_INIT_HEIGHT = 500
 
39
        IMAGEMAGICK_CONVERT_BIN = '/usr/bin/convert'
 
40
 
 
41
        def __init__( self, filename ):
 
42
                self.filename = filename
 
43
 
 
44
                # load preferences
 
45
                self.prefs = Preferences()
 
46
 
 
47
                # load main image and reset copyright image state to unloaded
 
48
                self.load_main_image()
 
49
                self.copyright_pic = None
 
50
 
 
51
                # init pos
 
52
                self.pos = "bottom-left";
 
53
 
 
54
                # work out the initial size of the drawing area so that the greatest of
 
55
                # the two dimensions is 450px
 
56
                scale = min( \
 
57
                                float( self.DRAWING_AREA_INIT_WIDTH ) / self.pic.get_width(), \
 
58
                                float( self.DRAWING_AREA_INIT_HEIGHT ) / self.pic.get_height() )
 
59
                init_width = int( round( scale * self.pic.get_width() ) )
 
60
                init_height = int( round( scale * self.pic.get_height() ) )
 
61
 
 
62
                # create window
 
63
                self.window = gtk.Window( gtk.WINDOW_TOPLEVEL )
 
64
                self.window.connect( "delete_event", self.delete_event )
 
65
                self.window.connect( "destroy", self.destroy )
 
66
                self.window.connect( "key-press-event", self.key_press_event )
 
67
                self.window.set_title( "Add Copyright to Images" );
 
68
                self.window.set_border_width( 10 )
 
69
                self.window.set_position( gtk.WIN_POS_CENTER )
 
70
                vbox = gtk.VBox( False, 10 )
 
71
                self.window.add( vbox )
 
72
 
 
73
                # create drawing area
 
74
                self.drawing_area = gtk.DrawingArea()
 
75
                self.drawing_area.set_size_request( init_width, init_height )
 
76
                self.drawing_area.add_events( gtk.gdk.POINTER_MOTION_MASK | \
 
77
                                gtk.gdk.POINTER_MOTION_HINT_MASK | \
 
78
                                gtk.gdk.BUTTON_PRESS_MASK )
 
79
                self.drawing_area.connect( "configure_event", \
 
80
                                self.drawing_area_configure_event )
 
81
                self.drawing_area.connect( "expose_event", \
 
82
                                self.drawing_area_expose_event )
 
83
                self.drawing_area.connect( "motion_notify_event", \
 
84
                                self.motion_notify_event )
 
85
                self.drawing_area.connect( "button_press_event", \
 
86
                                self.drawing_area_button_press_event )
 
87
                vbox.pack_start( self.drawing_area, True, True, 0 )
 
88
 
 
89
                # create buttons
 
90
                hbox = gtk.HBox( False, 10 )
 
91
                vbox.pack_end( hbox, False, False, 0 )
 
92
                left_button_box = gtk.HButtonBox()
 
93
                left_button_box.set_layout( gtk.BUTTONBOX_START )
 
94
                hbox.pack_start( left_button_box, True, True, 0 )
 
95
                button_box = gtk.HButtonBox()
 
96
                button_box.set_layout( gtk.BUTTONBOX_END )
 
97
                button_box.set_spacing( 10 )
 
98
                hbox.pack_end( button_box, True, True, 0 )
 
99
                prefs_button = gtk.Button( stock = gtk.STOCK_PREFERENCES )
 
100
                prefs_button.connect( "clicked", self.prefs_button_pressed )
 
101
                left_button_box.pack_start( prefs_button, True, True, 0 )
 
102
                cancel_button = gtk.Button( stock = gtk.STOCK_CANCEL )
 
103
                cancel_button.connect_object( "clicked", gtk.Widget.destroy, \
 
104
                                self.window )
 
105
                button_box.pack_end( cancel_button, True, True, 0 )
 
106
                self.ok_button = gtk.Button( stock = gtk.STOCK_APPLY )
 
107
                self.ok_button.connect( "clicked", self.ok_button_pressed )
 
108
                button_box.pack_end( self.ok_button, True, True, 0 )
 
109
                self.ok_button.set_flags( gtk.CAN_DEFAULT )
 
110
                self.ok_button.grab_default()
 
111
                self.ok_button.grab_focus()
 
112
 
 
113
                # show it all
 
114
                self.window.show_all()
 
115
 
 
116
                # load copyright image
 
117
                self.load_copyright_image()
 
118
 
 
119
        def load_main_image( self ):
 
120
                # load image file
 
121
                try:
 
122
                        self.pic = gtk.gdk.pixbuf_new_from_file( self.filename )
 
123
                except gobject.GError as e:
 
124
                        dialog = gtk.MessageDialog( None, gtk.DIALOG_MODAL, \
 
125
                                        gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE, e.message )
 
126
                        dialog.run()
 
127
                        dialog.destroy()
 
128
                        exit( 1 )
 
129
 
 
130
                # scale it
 
131
                if self.prefs.resize:
 
132
                        scale = min( \
 
133
                                        float( self.prefs.resize_width ) / self.pic.get_width(), \
 
134
                                        float( self.prefs.resize_height ) / self.pic.get_height() )
 
135
                        self.pic = self.pic.scale_simple( \
 
136
                                int( round( scale * self.pic.get_width() ) ), \
 
137
                                int( round( scale * self.pic.get_height() ) ), \
 
138
                                gtk.gdk.INTERP_BILINEAR )
 
139
 
 
140
        def load_copyright_image( self ):
 
141
 
 
142
                # reset state
 
143
                self.copyright_pic = None
 
144
                self.ok_button.set_sensitive( False )
 
145
 
 
146
                # check if we've got a copyright filename set
 
147
                if self.prefs.copyright_filename == '':
 
148
                        dialog = gtk.MessageDialog( self.window, gtk.DIALOG_MODAL, \
 
149
                                        gtk.MESSAGE_INFO, gtk.BUTTONS_OK, \
 
150
                                        "No copyright image has been set. " + \
 
151
                                        "Please set one in the Preferences." )
 
152
                        dialog.format_secondary_text( "While this issue persists, you " + \
 
153
                                        "can not proceed with this operation" )
 
154
                        dialog.run()
 
155
                        dialog.destroy()
 
156
                        return
 
157
 
 
158
                # load copyright overlay image file
 
159
                try:
 
160
                        self.copyright_pic = gtk.gdk.pixbuf_new_from_file( \
 
161
                                        os.path.expanduser( self.prefs.copyright_filename ) )
 
162
                except gobject.GError as e:
 
163
                        dialog = gtk.MessageDialog( None, gtk.DIALOG_MODAL, \
 
164
                                        gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, \
 
165
                                        "Unable to load copyright overlay image: " + \
 
166
                                        e.message )
 
167
                        dialog.format_secondary_text( "While this issue persists, you " + \
 
168
                                        "can not proceed with this operation" )
 
169
                        dialog.run()
 
170
                        dialog.destroy()
 
171
                        return
 
172
 
 
173
                # check the copyright overlay pic is smaller than the main pic
 
174
                if self.copyright_pic.get_width() > self.pic.get_width() or \
 
175
                                self.copyright_pic.get_height() > self.pic.get_height():
 
176
                        dialog = gtk.MessageDialog( None, gtk.DIALOG_MODAL, \
 
177
                                        gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, \
 
178
                                        "The copyright overlay image is larger than the photo!" )
 
179
                        dialog.format_secondary_text( "While this issue persists, you " + \
 
180
                                        "can not proceed with this operation" )
 
181
                        dialog.run()
 
182
                        dialog.destroy()
 
183
                        self.copyright_pic = None
 
184
                        return
 
185
 
 
186
                # enable the "ok" button and reconfigure the drawing area to generate
 
187
                # scaled puixbufs
 
188
                self.ok_button.set_sensitive( True )
 
189
                self.drawing_area.queue_resize()
 
190
 
 
191
        def main( self ):
 
192
                gtk.main()
 
193
 
 
194
        def ok_button_pressed( self, widget ):
 
195
 
 
196
                # calculate output filename
 
197
                pos = self.filename.rindex( '.' )
 
198
                if self.prefs.export_inplace:
 
199
                        append = ".tmp"
 
200
                else:
 
201
                        append = self.prefs.export_append
 
202
                out_filename = self.filename[ : self.filename.rindex( '.' ) ] + \
 
203
                                append + self.filename[ self.filename.rindex( '.' ) : ]
 
204
 
 
205
                # calculate geometry
 
206
                top = 0 if self.pos.find( 'top' ) != -1 else \
 
207
                                self.pic.get_height() - self.copyright_pic.get_height()
 
208
                left = 0 if self.pos.find( 'left' ) != -1 else \
 
209
                                self.pic.get_width() - self.copyright_pic.get_width()
 
210
 
 
211
                infile = self.filename
 
212
 
 
213
                # caluclate program args
 
214
                resize_geometry = str( self.prefs.resize_width ) + "x" + \
 
215
                                str( self.prefs.resize_height )
 
216
                copyright_geometry = str( self.copyright_pic.get_width() ) + \
 
217
                                "x" + str( self.copyright_pic.get_height() ) + "+" + \
 
218
                                str( left ) + "+" + str( top )
 
219
                args = [ self.IMAGEMAGICK_CONVERT_BIN, \
 
220
                                os.path.expanduser( self.prefs.copyright_filename ), \
 
221
                                "-geometry", copyright_geometry, \
 
222
                                "-compose", "screen", "-composite", \
 
223
                                out_filename ]
 
224
                if( self.prefs.resize ):
 
225
                        args[ 1 : 1 ] = [ "(", "-resize", resize_geometry, \
 
226
                                        self.filename, ")" ]
 
227
                else:
 
228
                        args[ 1 : 1 ] = [ self.filename ]
 
229
 
 
230
                # call ImageMagick to do our dirtywork
 
231
                try:
 
232
                        ret = call( args )
 
233
                        if ret != 0:
 
234
                                print >>sys.stderr, "ImageMagick failed, returned ", ret
 
235
                                return
 
236
                except OSError as e:
 
237
                        print >>sys.stderr, "Failed to execute ImageMagick: ", e
 
238
                        return
 
239
 
 
240
                if self.prefs.export_inplace:
 
241
                        # move original file out of the way
 
242
                        try:
 
243
                                os.rename( self.filename, self.filename + ".old" )
 
244
                        except OSError as e:
 
245
                                print >>sys.stderr, "Could not rename original image file: ", \
 
246
                                                e.strerror
 
247
                                exit( 1 )
 
248
 
 
249
                        # move new file in-place
 
250
                        try:
 
251
                                os.rename( out_filename, self.filename )
 
252
                        except OSError as e:
 
253
                                # try and put the original file back again
 
254
                                try:
 
255
                                        os.rename( self.filename + ".old", self.filename )
 
256
                                except OSError:
 
257
                                        pass
 
258
                                print >>sys.stderr, e.strerror
 
259
                                exit( 1 )
 
260
 
 
261
                        # delete original
 
262
                        try:
 
263
                                os.unlink( self.filename + ".old" )
 
264
                        except OSError as e:
 
265
                                print >>sys.stderr, "Unable to remove original image file (it ", \
 
266
                                        "now has '.old' appended to it's name): ", e.strerror
 
267
                                exit( 1 )
 
268
 
 
269
                # close this window
 
270
                self.window.destroy()
 
271
 
 
272
        def prefs_button_pressed( self, widget ):
 
273
                PreferencesDialog( self.window ).main()
 
274
                self.prefs = Preferences()
 
275
                self.load_main_image()
 
276
                self.load_copyright_image()
 
277
                self.drawing_area.queue_resize()
 
278
                self.drawing_area.queue_draw()
 
279
 
 
280
        def key_press_event( self, widget, event ):
 
281
                if event.keyval == gtk.keysyms.Escape:
 
282
                        self.window.destroy()
 
283
                elif event.keyval == gtk.keysyms.Up:
 
284
                        self.move_pos( "bottom", "top" )
 
285
                elif event.keyval == gtk.keysyms.Down:
 
286
                        self.move_pos( "top", "bottom" )
 
287
                elif event.keyval == gtk.keysyms.Left:
 
288
                        self.move_pos( "right", "left" )
 
289
                elif event.keyval == gtk.keysyms.Right:
 
290
                        self.move_pos( "left", "right" )
 
291
                else:
 
292
                        return False
 
293
 
 
294
                return True
 
295
 
 
296
        def delete_event( self, widget, event, data = None ):
 
297
                return False
 
298
 
 
299
        def destroy( self, widget, data = None ):
 
300
                gtk.main_quit()
 
301
 
 
302
        def motion_notify_event( self, widget, event ):
 
303
                x, y, width, height = widget.get_allocation()
 
304
 
 
305
                # get mouse position
 
306
                if event.is_hint:
 
307
                        x, y, state = event.window.get_pointer()
 
308
                else:
 
309
                        x = event.x
 
310
                        y = event.y
 
311
                        state = event.state
 
312
 
 
313
                # calculate copyright overlay pos
 
314
                pos =   ( "top" if y <= height / 2 else "bottom" ) + "-" + \
 
315
                                ( "left" if x <= width / 2 else "right" )
 
316
 
 
317
                # redraw if necessary
 
318
                if getattr( self, "pos", None ) != pos:
 
319
                        self.pos = pos
 
320
                        self.generate_pixmap()
 
321
                        widget.queue_draw()
 
322
 
 
323
                return True
 
324
 
 
325
        def drawing_area_button_press_event( self, widget, event ):
 
326
                if self.ok_button.get_property( "sensitive" ):
 
327
                        self.ok_button.clicked()
 
328
 
 
329
        def drawing_area_configure_event( self, widget, event ):
 
330
                x, y, width, height = widget.get_allocation()
 
331
 
 
332
                # work out minimum scale required to fit pic_orig in to the widget area
 
333
                scale = min( float( width ) / self.pic.get_width(),
 
334
                                float( height ) / self.pic.get_height() )
 
335
 
 
336
                # scale the images
 
337
                self.pic_scale = self.pic.scale_simple( \
 
338
                                int( round( scale * self.pic.get_width() ) ), \
 
339
                                int( round( scale * self.pic.get_height() ) ), \
 
340
                                gtk.gdk.INTERP_BILINEAR )
 
341
                if self.copyright_pic != None:
 
342
                        self.copyright_pic_scale = self.copyright_pic.scale_simple( \
 
343
                                        int( round( scale * self.copyright_pic.get_width() ) ), \
 
344
                                        int( round( scale * self.copyright_pic.get_height() ) ), \
 
345
                                        gtk.gdk.INTERP_BILINEAR )
 
346
 
 
347
                # recreate the pixmap to render to the screen
 
348
                self.generate_pixmap()
 
349
 
 
350
        def generate_pixmap( self ):
 
351
                widget = self.drawing_area
 
352
                x, y, width, height = widget.get_allocation()
 
353
 
 
354
                # calculate offsets to center pic in drawing area
 
355
                pre_width = ( width - self.pic_scale.get_width() ) / 2
 
356
                pre_height = ( height - self.pic_scale.get_height() ) / 2
 
357
                post_width = width - self.pic_scale.get_width() - pre_width
 
358
                post_height = height - self.pic_scale.get_height() - pre_height
 
359
 
 
360
                # create pixmap
 
361
                self.pixmap = gtk.gdk.Pixmap( widget.window, width, height )
 
362
 
 
363
                # create composite pixbuf containing the main image
 
364
                pixbuf = self.pic_scale.copy()
 
365
 
 
366
                if self.copyright_pic != None and \
 
367
                                getattr( self, "copyright_pic_scale", None ) != None:
 
368
 
 
369
                        # work out position of copyright overlay
 
370
                        if self.pos == "top-left" or self.pos == "bottom-left":
 
371
                                x = 0
 
372
                        if self.pos == "top-right" or self.pos == "bottom-right":
 
373
                                x = self.pic_scale.get_width() - \
 
374
                                                self.copyright_pic_scale.get_width()
 
375
                        if self.pos == "top-left" or self.pos == "top-right":
 
376
                                y = 0
 
377
                        if self.pos == "bottom-left" or self.pos == "bottom-right":
 
378
                                y = self.pic_scale.get_height() - \
 
379
                                                self.copyright_pic_scale.get_height()
 
380
 
 
381
                        # draw copyright
 
382
                        self.copyright_pic_scale.composite( pixbuf, x, y, \
 
383
                                        self.copyright_pic_scale.get_width(), \
 
384
                                        self.copyright_pic_scale.get_height(), \
 
385
                                        x, y, 1, 1, gtk.gdk.INTERP_NEAREST, 255 )
 
386
 
 
387
                # draw composit pixbuf to the off-screen pixmap
 
388
                self.pixmap.draw_pixbuf( widget.get_style().white_gc, \
 
389
                                pixbuf, 0, 0, pre_width, pre_height, \
 
390
                                pixbuf.get_width(), pixbuf.get_height() )
 
391
 
 
392
                # draw black rectangles if the pic is being centered
 
393
                if pre_width > 0 or pre_height > 0:
 
394
                        self.pixmap.draw_rectangle( widget.get_style().black_gc, True, \
 
395
                                        0, 0, \
 
396
                                        pre_width if pre_width > 0 else width, \
 
397
                                        pre_height if pre_height > 0 else height )
 
398
                if post_width > 0 or post_height > 0:
 
399
                        self.pixmap.draw_rectangle( widget.get_style().black_gc, True, \
 
400
                                        width - post_width if post_width > 0 else 0, \
 
401
                                        height - post_height if post_height > 0 else 0, \
 
402
                                        post_width if post_width > 0 else width, \
 
403
                                        post_height if post_height > 0 else height )
 
404
 
 
405
        def move_pos( self, tok_from, tok_to ):
 
406
                # calculate copyright overlay pos
 
407
                pos = self.pos.replace( tok_from, tok_to, 1 )
 
408
 
 
409
                # redraw if necessary
 
410
                if self.pos != pos:
 
411
                        self.pos = pos
 
412
                        self.generate_pixmap()
 
413
                        self.drawing_area.queue_draw()
 
414
 
 
415
        def drawing_area_expose_event( self, widget, event ):
 
416
                x, y, width, height = event.area
 
417
                widget.window.draw_drawable( \
 
418
                                widget.get_style().fg_gc[ gtk.STATE_NORMAL ], \
 
419
                                self.pixmap, x, y, x, y, width, height )
 
420
                return False