/gtk/prep-images

To get this branch, use:
bzr branch http://bzr.ed.am/gtk/prep-images
1 by edam
- initial commit, includes project and build setup
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