-
Notifications
You must be signed in to change notification settings - Fork 1
/
SlideshowScreenlet.py
executable file
·547 lines (447 loc) · 17.9 KB
/
SlideshowScreenlet.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
#!/usr/bin/env python
# This application is released under the GNU General Public License
# v3 (or, at your option, any later version). You can find the full
# text of the license under http://www.gnu.org/licenses/gpl.txt.
# By using, editing and/or distributing this software you agree to
# the terms and conditions of this license.
# Thank you for using free software!
# SlideshowScreenlet (c) Whise <[email protected]>
# Modifed by David Lyons <[email protected]> to add image centering and much better image resizing.
# 08 Jan 2010 Modified by Ross Adamson <[email protected]> to add orientation correction based on exif.
import screenlets
from screenlets.options import FileOption, IntOption, FloatOption, StringOption, BoolOption
from screenlets import DefaultMenuItem , utils
import cairo
import gtk
import pango
import gobject
try:
import Image
except:
print 'Error - Please install python image module'
import os
#import re
#from urllib import urlopen
import commands
import random
from screenlets import Plugins
Flickr = Plugins.importAPI('Flickr')
class SlideshowScreenlet (screenlets.Screenlet):
"""A Screenlet that displays a slideshow from your folder or from the Flickr.com website.You can add new images by drag drop them into the screenlet's window area. You need a package called Python imaging"""
# --------------------------------------------------------------------------
# meta-info, options
# --------------------------------------------------------------------------
__name__ = 'SlideshowScreenlet'
__version__ = '1.3'
__author__ = 'Helder Fraga aka Whise(+David Lyons+Ross Adamson)'
__desc__ = __doc__
# attributes
__image = None
__timeout = None
# editable options
update_interval = 60
image_filename = ''
image_scale = 0.878
IMAGE_FRAME_X_FUDGE_FACTOR = 0.95
IMAGE_FRAME_Y_FUDGE_FACTOR = 1.05
image_offset_x = 12
image_offset_y = 12
image_offset_x_orig = 12
image_offset_y_orig = 12
url = ''
slide = True
home = commands.getoutput("echo $HOME")
folders = home
use_types = ['.jpg', '.gif', '.png','.bmp', '.svg', '.jpeg', '.tif', '.tiff']
engine = ''
engine1 = 'directory'
engine_sel = ['directory', 'Flickr']
frame = 'normal'
frame_sel = ['normal', 'wide']
paint_menu = False
showbuttons = True
img_name = ''
factor = 1
preserve_aspect = True
center_image = True
crop_to_fit = False
recursive = False
flickrurl = 'http://www.flickr.com/explore/interesting/7days/'
use_higher_quality_flicker = True
# --------------------------------------------------------------------------
# constructor and internals
# --------------------------------------------------------------------------
def __init__ (self, **keyword_args):
# call super (and enable drag/drop)
screenlets.Screenlet.__init__(self, width=200, height=200,
uses_theme=True, drag_drop=True, **keyword_args)
# set theme
self.theme_name = "default"
# initially apply default image (for newly created instances)
#self.image_filename = screenlets.PATH + '/Picframe/dali.png'
# add default menuitems (all the standard ones)
self.add_default_menuitems(DefaultMenuItem.XML)
# add option group to properties-dialog
self.add_options_group('SlideShow', 'Slideshow-related settings ...')
# add editable options
#self.add_option(FileOption('Slideshow', 'image_filename',
# self.image_filename, 'Filename',
self.add_option(IntOption('SlideShow', 'update_interval',
self.update_interval, 'Update interval',
'The interval for updating info (in seconds ,3660 = 1 day, 25620 = 1 week)', min=1, max=25620))
self.add_option(StringOption('SlideShow', 'engine', self.engine,'Select Engine', '',choices = self.engine_sel),realtime=False)
self.add_option(StringOption('SlideShow', 'flickrurl', self.flickrurl,'Flickr Url', 'flickr url'))
self.add_option(StringOption('SlideShow', 'folders', self.folders,'Select Folders', 'The folder where pictures are',))
self.add_option(BoolOption('SlideShow', 'recursive',bool(self.recursive), 'Recursive folders','Show images on sub folders'))
self.add_option(BoolOption('SlideShow', 'showbuttons',bool(self.showbuttons), 'Show Buttons on focus','Show Buttons on focus'))
self.add_option(StringOption('SlideShow', 'frame', self.frame,'Select frame type', 'Select frame type',choices = self.frame_sel),)
# 'Filename of image to be shown in this Slideshow ...'))
self.add_option(FloatOption('SlideShow', 'image_scale', self.image_scale,
'Image Scale', 'Scale of image within this Picframe ...',
min=0.01, max=10.0, digits=2, increment=0.01,hidden=True))
self.add_option(IntOption('SlideShow', 'image_offset_x',
self.image_offset_x, 'Image Offset X', 'X-offset of upper left '+\
'corner of the image within this Picframe ...',
min=0, max=self.width,hidden=True))
self.add_option(IntOption('SlideShow', 'image_offset_y',
self.image_offset_y, 'Image Offset Y', 'Y-offset of upper left '+\
'corner of the image within this Picframe ...',
min=0, max=self.height,hidden=True))
self.add_option(BoolOption('SlideShow', 'preserve_aspect', bool(self.preserve_aspect),'Preserve aspect ratio', 'Preserve the aspect ratio when resizing images ,thanks to Mike Peters'))
self.add_option(BoolOption('SlideShow', 'center_image', bool(self.center_image),'Center the image', 'Center the image in the canvas, if preserve aspect is on. Thanks to Dave Lyons'))
self.add_option(BoolOption('SlideShow', 'crop_to_fit', bool(self.crop_to_fit),'Crop to fit frame', 'Crop the image to fit the frame, sort of like zoom. Overrides preserve_aspect and center_image'))
self.add_option(BoolOption('SlideShow', 'use_higher_quality_flicker', bool(self.use_higher_quality_flicker),'Higher quality Flickr', 'Try to use higher quality images from Flickr. May break. By David Lyons'))
self.update_interval = self.update_interval
self.engine = self.engine
self.folders = self.folders
def __setattr__ (self, name, value):
screenlets.Screenlet.__setattr__(self, name, value)
if name == 'showbuttons':
self.redraw_canvas()
if name == 'engine':
if value == 'directory' :
self.engine1 = 'directory'
self.update()
if value == '' :
self.engine1 = 'directory'
self.update()
if value == 'Flickr':
self.engine1 = value
self.update()
if name == 'folders' and self.engine == 'directory':
self.engine1 = 'directory'
self.update()
if name == 'frame':
if value == 'wide':
self.factor = 0.8
else:
self.factor = 1
#if name == "crop_to_fit" && self.crop_to_fit:
# self.preserve_aspect = False
# self.center_image = False
if name == "image_filename":
screenlets.Screenlet.__setattr__(self, name, value)
# update view
self.redraw_canvas()
#self.update_shape()
#if the scale has changed, resize the image & redraw
#because, the image is drawn always at 1x1 because cairo img resize sux.
if name == "scale":
self.read_and_resize_png(self.image_filename)
if self.__image:
self.redraw_canvas()
if name == "update_interval":
if value > 0:
self.__dict__['update_interval'] = value
if self.__timeout:
gobject.source_remove(self.__timeout)
self.__timeout = gobject.timeout_add(int(value * 1000), self.update)
else:
self.__dict__['update_interval'] = 1
pass
def on_init(self):
self.width = 200
self.height = int(200 * self.factor)
self.update()
print "Screenlet has been initialized."
# add default menuitems
self.add_default_menuitems()
def set_image(self, filename):
self.image_filename = filename
self.read_and_resize_png(self.image_filename)
def fetch_image(self):
#if self.slide == True:
if self.engine1 == 'Flickr':
imgs = []
a = Flickr.Flickr()
try:
imgs = a.get_image_list(self.flickrurl)
except:return ''
choice = random.choice(imgs)
self.url = a.url_list[str(choice)]
self.img_name = self.home + "/slide.jpg"
random.choice(imgs)
saveto = self.home + "/slide.jpg"
if self.use_higher_quality_flicker:
#we can make the images higher qual by deleting the _m from the tail of the filename
choice = choice.replace('_m.','.')
print "using HQ url: %s " % choice
a.save_image(choice,saveto)
self.img_name = self.home + "/slide.jpg"
forecast = self.img_name
elif self.engine1 == 'directory':
imgs = []
if self.recursive:
for root, dirs, files in os.walk(self.folders):
for file in files:
try:
if os.path.splitext(file)[1].lower() in self.use_types:
imgs.append(os.path.join(root,file))
except: pass
else:
if os.path.exists(self.folders) and os.path.isdir(self.folders):
for f in os.listdir(self.folders):
try: #splitext[1] may fail
if os.path.splitext(f)[1].lower() in self.use_types:
imgs.append(self.folders + os.sep + f) #if so, add it to our list
#print f
except: pass
try:
forecast = random.choice(imgs) #get a random entry from our list
self.img_name = forecast
except:
pass
try:return forecast
except:
pass
# --------------------------------------------------------------------------
# Screenlet handlers
# --------------------------------------------------------------------------
def update(self):
#screenlets.show_error(self,
# 'Failed to load image "%s": %s (only PNG images supported yet)' )# % (filename, ex))
if self.slide == True:
self.set_image (self.fetch_image())
self.redraw_canvas()
return True
def on_drag_enter (self, drag_context, x, y, timestamp):
self.redraw_canvas()
def on_drag_leave (self, drag_context, timestamp):
self.redraw_canvas()
def on_drop (self, x, y, sel_data, timestamp):
print "Data dropped ..."
filename = ''
filename = utils.get_filename_on_drop(sel_data)[0]
print filename
if filename != '':
#self.set_image(filename)
self.image_filename = filename.replace(chr(34),'')
def show_install_dialog (self):
"""Craete/Show the install-dialog."""
# create filter
flt = gtk.FileFilter()
flt.add_pattern('*.')
# create dialog
dlg = gtk.FileChooserDialog(action=gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER,buttons=(gtk.STOCK_CANCEL,
gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK))
dlg.set_current_folder(os.environ['home'])
dlg.set_title(('Select a folder'))
dlg.set_filter(flt)
# run
resp = dlg.run()
filename = dlg.get_filename()
dlg.destroy()
if resp == gtk.RESPONSE_OK:
# create new installer
# try installing and show result dialog
self.window.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
self.folders = filename
self.window.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.LEFT_PTR))
def on_draw (self, ctx):
ctx.set_operator(cairo.OPERATOR_OVER)
#draw the frame at the current scale, then revert the scale.
#we always draw the image at 1:1 scale, because it has been resized to the proper size
# already in read_and_resize_png().
ctx.save()
ctx.scale(self.scale , self.factor * self.scale)
self.theme.render(ctx, 'frame')
ctx.restore()
if self.theme:
# if something is dragged over, lighten up the whole thing
if self.dragging_over:
ctx.set_operator(cairo.OPERATOR_XOR)
#try to render the image
try:
#ctx.scale(1,1)
#set image offsets to take into account the frame's scaled size
self.image_offset_x = self.image_offset_x_orig * self.scale
self.image_offset_y = self.image_offset_y_orig * self.scale
#translate the origin of the picture to the frame offsets
ctx.translate(self.image_offset_x, self.image_offset_y)
img_x_pos = img_y_pos = 0
#center the image, if appropriate.
if self.center_image:
img_w, img_h = (self.__image.get_width(), self.__image.get_height())
thumb_w, thumb_h = self.calc_thumb_size()
if img_w < thumb_w:
img_x_pos = (thumb_w - img_w) / 2
if img_h < thumb_h:
img_y_pos = (thumb_h - img_h) / 2
ctx.set_source_surface(self.__image, img_x_pos, img_y_pos)
ctx.paint()
#ctx.restore()
#self.draw_scaled_image(ctx,w_offset,h_offset,self.image_filename,w,h)
except Exception, ex:
print "Failed to display image: %s" % ex
pass
ctx.restore()
ctx.translate(60,158)
if self.paint_menu == True and self.showbuttons == True: self.theme.render(ctx, 'menu')
#get the maximum thumbnail size that fits in the frame.
#for some reason, I have to fudge the widths and heights a little to make it nice,
# the frame must not be perfectly square.
def calc_thumb_size(self):
img_border_factor = self.scale * (1 - self.image_scale) #% of total size occupied by the frame
width = self.width * self.scale - self.width * img_border_factor * self.IMAGE_FRAME_X_FUDGE_FACTOR
height = self.height * self.scale - self.height * img_border_factor * self.IMAGE_FRAME_Y_FUDGE_FACTOR
return (width, height)
def read_and_resize_png(self,filename):
#Reads the picture file from filename, then crops/resizes it as nessicary,
# and writes it to self.__image in Cairo ctx image form.
#NOTE: This routine resizes to the thumbnail SCREEN size, not the 200x200 scale 1 size
# that most versions of this screenlet use.
# Thats because if we resize down to 200x200, then we have to re-resize that
# *back up* to whatever size, which looks like shit.
#TODO: support for more image-types (currently only png supported)"""
print "Setting new image for SlideshowScreenlet from: %s" % filename
#clear old image variable
if self.__image:
self.__image.finish()
del self.__image
#try and process the image
try:
#get the maximum SCREEN size
thumb_size = self.calc_thumb_size()
thumb_width, thumb_height = thumb_size
original_image = Image.open(filename)
orientation_code = 0x0112
exif_info = original_image._getexif()
orientation = exif_info.get(orientation_code, 1)
if orientation == 1:
# correctly oriented
flip_degrees = 0
elif orientation == 8:
# oriented to the right
flip_degrees = 90
elif orientation == 6:
# oriented to the left
flip_degrees = -90
else:
# other orientations are uncommon
flip_degrees = 0
# orient the image correctly
image = original_image.rotate(flip_degrees)
# Get image dimensions
(width, height) = image.size
#if we are cropping, zoom & crop the picture to exactly the thumb dimensions
if self.crop_to_fit:
# If original image is not a square, crop it
if width > height:
chopSize = (width - height) / 2
image = image.crop((0 + chopSize, 0, width - chopSize, height))
elif height > width:
chopSize = (height - width) / 2
image = image.crop((0, 0 + chopSize, width, height - chopSize))
#otherwise if we are presevering aspect, maximise the img to the thumb using ratios
elif self.preserve_aspect:
if width > height:
ratio = float(thumb_width) / width
height = int(height * ratio)
width = thumb_width
else:
ratio = float(thumb_height) / height
width = int(width * ratio)
height = thumb_height
#set the thumb_size to our new aspect'd size.
thumb_size = (width, height)
#diagnostic info.
#print "original:%i x %i canvas: %i x %i thumb:%i x %i scale:%f" % \
# (image.size[0],image.size[1],thumb_width, thumb_height, thumb_size[0], thumb_size[1], self.scale)
#do the actual resize - ANTIALIAS is the best quality.
image = image.resize(thumb_size, Image.ANTIALIAS)
#write the image out to disk, then read it into a cairo image surface.
#TODO: is there an in-memory way to do this?
home = commands.getoutput("echo $HOME")
image.save (home + '/slide' + '.png')
img = cairo.ImageSurface.create_from_png(home + '/slide' + '.png')
if img:
self.__image = img
return True
except Exception, ex:
print "Failed to load image %s" % ex
return False
def on_focus(self, event):
self.paint_menu = True
self.redraw_canvas()
def on_unfocus(self, event):
self.paint_menu = False
self.redraw_canvas()
def on_mouse_down(self,event):
x, y = self.window.get_pointer()
x /= (self.scale)
y /= (self.scale*self.factor)
if y >= 158 and y <=180:
if x >= 60 and x <= 86 :
self.slide = False
self.update()
elif x >= 87 and x <= 109 :
self.slide = True
self.update()
elif x >= 110 and x <= 136 :
self.set_image (self.fetch_image())
self.redraw_canvas()
def on_menuitem_select (self, id):
"""handle MenuItem-events in right-click menu"""
if id == "next":
# TODO: use DBus-call for this
#self.switch_hide_show()
self.set_image (self.fetch_image())
self.redraw_canvas()
if id == "visit":
# TODO: use DBus-call for this
#self.switch_hide_show()
if self.engine1 == 'Flickr':
os.system('firefox ' + self.url + " &")
elif self.engine1 == 'directory':
os.system('gnome-open ' + chr(34) + self.img_name + chr(34) + " &")
if id == "wall":
# TODO: use DBus-call for this
#self.switch_hide_show()
if self.engine1 == 'directory':
os.system("gconftool-2 -t string -s /desktop/gnome/background/picture_filename " + chr(34) + self.img_name + chr(34))
os.system("gconftool-2 -t bool -s /desktop/gnome/background/draw_background False")
os.system("gconftool-2 -t bool -s /desktop/gnome/background/draw_background True")
elif self.engine1 == 'Flickr':
screenlets.show_message(self,'Can only set wallpaper to local images')
if id == "start":
self.slide = True
self.update()
if id == "stop":
self.slide = False
if id[:7] == "Install":
# TODO: use DBus-call for this
self.show_install_dialog()
self.update()
def on_draw_shape (self, ctx):
ctx.scale(self.scale, self.scale)
if self.theme:
#self.theme['control-bg.svg'].render_cairo(ctx)
ctx.set_source_rgba(1, 1, 1, 1)
ctx.rectangle (0,0,self.width,self.height)
ctx.fill()
# If the program is run directly or passed as an argument to the python
# interpreter then launch as new application
if __name__ == "__main__":
# create session object here, the rest is done automagically
import screenlets.session
screenlets.session.create_session(SlideshowScreenlet)