summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGuillaume Seguin <guillaume@segu.in>2010-07-12 05:52:08 -0400
committerGuillaume Seguin <guillaume@segu.in>2010-07-12 05:52:08 -0400
commitac5693369e44b5b95729f998b39f9f843204ae61 (patch)
tree186bfa3ebf5346515dffcdcfd84dbec49ac76772
downloadmultiwpcreator-ac5693369e44b5b95729f998b39f9f843204ae61.tar.gz
multiwpcreator-ac5693369e44b5b95729f998b39f9f843204ae61.tar.bz2
Initial import
-rw-r--r--.gitignore4
-rw-r--r--multiwpcreator.py6
-rw-r--r--wpcreator.gtkbuilder154
-rw-r--r--wpcreator/__init__.py0
-rw-r--r--wpcreator/cropper.py229
-rw-r--r--wpcreator/generator.py21
-rw-r--r--wpcreator/ui.py292
7 files changed, 706 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..15a4796
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+*.swp
+*.swo
+*.pyo
+*.pyc
diff --git a/multiwpcreator.py b/multiwpcreator.py
new file mode 100644
index 0000000..119b630
--- /dev/null
+++ b/multiwpcreator.py
@@ -0,0 +1,6 @@
+#!/usr/bin/env python
+
+from wpcreator.ui import main
+
+if __name__ == "__main__":
+ main ()
diff --git a/wpcreator.gtkbuilder b/wpcreator.gtkbuilder
new file mode 100644
index 0000000..9d1f5b4
--- /dev/null
+++ b/wpcreator.gtkbuilder
@@ -0,0 +1,154 @@
+<?xml version="1.0"?>
+<interface>
+ <requires lib="gtk+" version="2.16"/>
+ <!-- interface-naming-policy project-wide -->
+ <object class="GtkWindow" id="mainWindow">
+ <property name="title" translatable="yes">Wallpaper Creator</property>
+ <child>
+ <object class="GtkVBox" id="mainBox">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkToolbar" id="toolbar">
+ <property name="visible">True</property>
+ <property name="toolbar_style">both</property>
+ <child>
+ <object class="GtkToolButton" id="loadButton">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Open</property>
+ <property name="use_underline">True</property>
+ <property name="stock_id">gtk-open</property>
+ <signal name="clicked" handler="load_files"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToolItem" id="targetWidthButton">
+ <property name="visible">True</property>
+ <property name="visible_horizontal">False</property>
+ <property name="visible_vertical">False</property>
+ <child>
+ <object class="GtkHBox" id="targetWidthBox">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkLabel" id="targetWidthLabel">
+ <property name="visible">True</property>
+ <property name="xpad">4</property>
+ <property name="ypad">4</property>
+ <property name="label" translatable="yes">Workspace width :</property>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="targetWidthSpin">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">&#x25CF;</property>
+ <property name="adjustment">targetWidthAdjustment</property>
+ <property name="numeric">True</property>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToolItem" id="targetHeightButton">
+ <property name="visible">True</property>
+ <property name="visible_horizontal">False</property>
+ <property name="visible_vertical">False</property>
+ <child>
+ <object class="GtkHBox" id="targetHeightBox">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkLabel" id="targetHeightLabel">
+ <property name="visible">True</property>
+ <property name="xpad">4</property>
+ <property name="ypad">4</property>
+ <property name="label" translatable="yes">Workspace height :</property>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="targetHeightSpin">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">&#x25CF;</property>
+ <property name="adjustment">targetHeightAdjustment</property>
+ <property name="numeric">True</property>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToolButton" id="generateButton">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Generate</property>
+ <property name="use_underline">True</property>
+ <property name="stock_id">gtk-execute</property>
+ <signal name="clicked" handler="make_wallpaper"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="scroller">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">automatic</property>
+ <property name="vscrollbar_policy">automatic</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <object class="GtkAdjustment" id="targetWidthAdjustment">
+ <property name="value">240</property>
+ <property name="lower">1</property>
+ <property name="upper">3000</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="targetHeightAdjustment">
+ <property name="value">320</property>
+ <property name="lower">1</property>
+ <property name="upper">3000</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+</interface>
diff --git a/wpcreator/__init__.py b/wpcreator/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/wpcreator/__init__.py
diff --git a/wpcreator/cropper.py b/wpcreator/cropper.py
new file mode 100644
index 0000000..5a54d5e
--- /dev/null
+++ b/wpcreator/cropper.py
@@ -0,0 +1,229 @@
+import pygtk
+pygtk.require ("2.0")
+import gtk
+import cairo
+
+from PIL import Image
+
+CROP_MOVE = 0
+CROP_RESIZE_TL = 1
+CROP_RESIZE_TR = 2
+CROP_RESIZE_BL = 3
+CROP_RESIZE_BR = 4
+
+class CropperUi (gtk.DrawingArea):
+
+ path = None
+ pixbuf = None
+
+ crop_x = 0
+ crop_y = 0
+ crop_width = 0
+ crop_height = 0
+ ratio = 1.
+
+ prev_x = 0
+ prev_y = 0
+
+ full_region = None
+ crop_region = None
+ tl_region = None
+ tr_region = None
+ bl_region = None
+ br_region = None
+
+ move_cursor = None
+ resize_tl_cursor = None
+ resize_tr_cursor = None
+ resize_bl_cursor = None
+ resize_br_cursor = None
+
+ tl_br_vector = None
+ tr_bl_vector = None
+
+ target_width = None
+ target_height = None
+
+ mode = None
+
+ def __init__ (self, path, target_width, target_height):
+ super (CropperUi, self).__init__ ()
+ self.path = path
+ self.target_width = target_width
+ self.target_height = target_height
+ self.target_ratio = float (target_width) / target_height
+ self.pixbuf = gtk.gdk.pixbuf_new_from_file (path)
+ self.add_events (gtk.gdk.BUTTON_PRESS_MASK \
+ | gtk.gdk.BUTTON_RELEASE_MASK
+ | gtk.gdk.POINTER_MOTION_MASK)
+ self.connect ("expose-event", self.on_expose)
+ self.connect ("motion-notify-event", self.on_motion_notify)
+ self.connect ("button-press-event", self.on_button_press)
+ self.connect ("button-release-event", self.on_button_release)
+ self.connect_after ("size-allocate", self.on_size_allocate)
+ self.ratio = 1.
+ self.crop_x = 0
+ self.crop_y = 0
+ pixbuf_w, pixbuf_h = self.pixbuf.get_width (), self.pixbuf.get_height ()
+ pixbuf_ratio = float (pixbuf_w) / pixbuf_h
+ # If there is more width than needed, use full height
+ if pixbuf_ratio > self.target_ratio:
+ self.crop_width = int (self.target_ratio * pixbuf_h)
+ self.crop_height = pixbuf_h
+ self.crop_x = (pixbuf_w - self.crop_width) / 2
+ else: # Otherwise, use full width
+ self.crop_width = pixbuf_w
+ self.crop_height = int (float (pixbuf_w) / self.target_ratio)
+ self.crop_y = (pixbuf_h - self.crop_height) / 2
+ self.recompute_vectors ()
+ self.mode = None
+ self.move_cursor = gtk.gdk.Cursor (gtk.gdk.FLEUR)
+ self.resize_tl_cursor = gtk.gdk.Cursor (gtk.gdk.TOP_LEFT_CORNER)
+ self.resize_tr_cursor = gtk.gdk.Cursor (gtk.gdk.TOP_RIGHT_CORNER)
+ self.resize_bl_cursor = gtk.gdk.Cursor (gtk.gdk.BOTTOM_LEFT_CORNER)
+ self.resize_br_cursor = gtk.gdk.Cursor (gtk.gdk.BOTTOM_RIGHT_CORNER)
+
+ def recompute_regions (self):
+ pixbuf_w, pixbuf_h = self.pixbuf.get_width (), self.pixbuf.get_height ()
+ full_rectangle = gtk.gdk.Rectangle (0, 0, pixbuf_w, pixbuf_h)
+ self.full_region = gtk.gdk.region_rectangle (full_rectangle)
+ crop_rectangle = gtk.gdk.Rectangle (self.crop_x, self.crop_y,
+ self.crop_width, self.crop_height)
+ self.crop_region = gtk.gdk.region_rectangle (crop_rectangle)
+ corner_side = int (float (20) / self.ratio)
+ base_corner_rectangle = gtk.gdk.Rectangle (0, 0,
+ corner_side, corner_side)
+ base_corner = gtk.gdk.region_rectangle (base_corner_rectangle)
+ crop_x2 = self.crop_x + self.crop_width - corner_side
+ crop_y2 = self.crop_y + self.crop_height - corner_side
+ self.tl_region = base_corner.copy ()
+ self.tl_region.offset (self.crop_x, self.crop_y)
+ self.tr_region = base_corner.copy ()
+ self.tr_region.offset (crop_x2, self.crop_y)
+ self.bl_region = base_corner.copy ()
+ self.bl_region.offset (self.crop_x, crop_y2)
+ self.br_region = base_corner.copy ()
+ self.br_region.offset (crop_x2, crop_y2)
+
+ def recompute_vectors (self):
+ x0, y0 = self.crop_x, self.crop_y
+ x1, y1 = self.crop_x + self.crop_width, self.crop_y + self.crop_height
+ self.tl_br_vector = (x1 - x0, y1 - y0, x0, y0)
+ self.tr_bl_vector = (x0 - x1, y1 - y0, x1, y0)
+
+ def get_proj (self, (p_x, p_y), (v_x, v_y, v0_x, v0_y)):
+ k = (p_x - v0_x) * v_x + (p_y - v0_y) * v_y
+ n_v_2 = v_x * v_x + v_y * v_y
+ k2 = float (k) / n_v_2
+ return int (v0_x + k2 * v_x), int (v0_y + k2 * v_y)
+
+ def on_size_allocate (self, widget, alloc):
+ pixbuf_w, pixbuf_h = self.pixbuf.get_width (), self.pixbuf.get_height ()
+ _, _, w, h = self.get_allocation ()
+ w_ratio = float (w) / float (pixbuf_w)
+ h_ratio = float (h) / float (pixbuf_h)
+ self.ratio = min (w_ratio, h_ratio)
+ self.recompute_regions ()
+ self.queue_draw ()
+
+ def on_expose (self, widget, event):
+ cr = self.window.cairo_create ()
+ cr.set_operator (cairo.OPERATOR_OVER)
+ cr.save ()
+ cr.scale (self.ratio, self.ratio)
+ bg_color = self.get_style ().fg[self.get_state ()]
+ cr.set_source_rgb (bg_color.red_float,
+ bg_color.green_float,
+ bg_color.blue_float)
+ cr.paint ()
+ cr.set_source_pixbuf (self.pixbuf, 0, 0)
+ cr.paint ()
+ cr.set_source_rgba (1, 1, 1, 0.5)
+ cr.paint ()
+ cr.rectangle (self.crop_x, self.crop_y,
+ self.crop_width, self.crop_height)
+ cr.save ()
+ cr.clip_preserve ()
+ cr.set_source_pixbuf (self.pixbuf, 0, 0)
+ cr.paint ()
+ cr.restore ()
+ cr.set_source_rgb (0.2, 0.2, 0.2)
+ cr.set_dash ([3, 3])
+ cr.set_line_width (int (float (1) / self.ratio))
+ cr.stroke_preserve ()
+ cr.restore ()
+
+ def scale_event_coords (self, event):
+ x, y = float (event.x) / self.ratio, float (event.y) / self.ratio
+ return int (x), int (y)
+
+ def on_button_press (self, widget, event):
+ x, y = self.scale_event_coords (event)
+ if self.tl_region.point_in (x, y):
+ self.mode = CROP_RESIZE_TL
+ self.window.set_cursor (self.resize_tl_cursor)
+ elif self.tr_region.point_in (x, y):
+ self.mode = CROP_RESIZE_TR
+ self.window.set_cursor (self.resize_tr_cursor)
+ elif self.bl_region.point_in (x, y):
+ self.mode = CROP_RESIZE_BL
+ self.window.set_cursor (self.resize_bl_cursor)
+ elif self.br_region.point_in (x, y):
+ self.mode = CROP_RESIZE_BR
+ self.window.set_cursor (self.resize_br_cursor)
+ elif self.crop_region.point_in (x, y):
+ self.mode = CROP_MOVE
+ self.window.set_cursor (self.move_cursor)
+ self.prev_x = x
+ self.prev_y = y
+
+ def on_button_release (self, widget, event):
+ if self.window:
+ self.window.set_cursor (None)
+ self.recompute_regions ()
+ self.recompute_vectors ()
+ self.mode = None
+
+ def on_motion_notify (self, widget, event):
+ if self.mode == None:
+ return
+ x, y = self.scale_event_coords (event)
+ dx = x - self.prev_x
+ dy = y - self.prev_y
+ self.prev_x = x
+ self.prev_y = y
+ x0, y0 = self.crop_x, self.crop_y
+ x1, y1 = x0 + self.crop_width, y0 + self.crop_height
+ if self.mode == CROP_MOVE:
+ rect = gtk.gdk.Rectangle (self.crop_x + dx, self.crop_y + dy,
+ self.crop_width, self.crop_height)
+ if self.full_region.rect_in (rect) == gtk.gdk.OVERLAP_RECTANGLE_IN:
+ self.crop_x += dx
+ self.crop_y += dy
+ elif self.mode == CROP_RESIZE_TL:
+ x0, y0 = self.get_proj ((x, y), self.tl_br_vector)
+ elif self.mode == CROP_RESIZE_TR:
+ x1, y0 = self.get_proj ((x, y), self.tr_bl_vector)
+ elif self.mode == CROP_RESIZE_BL:
+ x0, y1 = self.get_proj ((x, y), self.tr_bl_vector)
+ elif self.mode == CROP_RESIZE_BR:
+ x1, y1 = self.get_proj ((x, y), self.tl_br_vector)
+ if self.mode != CROP_MOVE:
+ rect = gtk.gdk.Rectangle (x0, y0, x1 - x0, y1 - y0)
+ if self.full_region.rect_in (rect) == gtk.gdk.OVERLAP_RECTANGLE_IN:
+ self.crop_x = x0
+ self.crop_y = y0
+ self.crop_width = x1 - x0
+ self.crop_height = y1 - y0
+
+ self.queue_draw ()
+
+ def get_cropped_image (self):
+ image = Image.open (open (self.path))
+ x0, y0 = self.crop_x, self.crop_y
+ x1 = self.crop_x + self.crop_width
+ y1 = int (self.crop_y + float (self.crop_width) / self.target_ratio)
+ image = image.crop ((x0, y0, x1, y1))
+ image = image.resize ((self.target_width, self.target_height),
+ Image.ANTIALIAS)
+ return image
diff --git a/wpcreator/generator.py b/wpcreator/generator.py
new file mode 100644
index 0000000..7f22c55
--- /dev/null
+++ b/wpcreator/generator.py
@@ -0,0 +1,21 @@
+import pygtk
+pygtk.require ("2.0")
+import gtk
+import cairo
+
+from PIL import Image
+
+def assemble (images):
+ full_width = 0
+ full_height = 0
+ for image in images:
+ width, height = image.size
+ full_width += width
+ full_height = max (height, full_height)
+ dest = Image.new ("RGB", (full_width, full_height))
+ x = 0
+ for image in images:
+ width, height = image.size
+ dest.paste (image, (x, 0))
+ x += width
+ return dest
diff --git a/wpcreator/ui.py b/wpcreator/ui.py
new file mode 100644
index 0000000..b1debfa
--- /dev/null
+++ b/wpcreator/ui.py
@@ -0,0 +1,292 @@
+import pygtk
+pygtk.require ("2.0")
+import gtk
+import cairo
+import pangocairo
+
+import StringIO
+import cropper
+import generator
+
+target_width = 240
+target_height = 320
+mainWindow = None
+
+def prepare_text (cr, text):
+ '''Prepare text for drawing with Pango/Cairo'''
+ pcr = pangocairo.CairoContext (cr)
+ layout = pcr.create_layout ()
+ layout.set_markup (text)
+ return pcr, layout
+
+def pilimage_to_pixbuf (image):
+ f = StringIO.StringIO ()
+ image.save (f, 'ppm')
+ contents = f.getvalue()
+ f.close ()
+ loader = gtk.gdk.PixbufLoader ('pnm')
+ loader.write (contents, len (contents))
+ pixbuf = loader.get_pixbuf ()
+ loader.close ()
+ return pixbuf
+
+class ImagePreview (gtk.DrawingArea):
+
+ number = None
+ pixbuf = None
+ number_colors = [
+ (0.94, 0.16, 0.16),
+ (0.13, 0.29, 0.53),
+ (0.54, 0.89, 0.20),
+ (0.99, 0.69, 0.24),
+ ]
+
+ def __init__ (self, cropped):
+ super (ImagePreview, self).__init__ ()
+ self.set_cropped (cropped)
+ self.set_size_request (140, 170)
+ self.connect ("expose-event", self.on_expose)
+
+ def set_cropped (self, cropped):
+ cropped = cropped.resize ((120, 160))
+ self.pixbuf = pilimage_to_pixbuf (cropped)
+ self.queue_draw ()
+
+ def set_number (self, number):
+ self.number = number
+ self.queue_draw ()
+
+ def on_expose (self, widget, event):
+ cr = self.window.cairo_create ()
+ cr.rectangle (10, 10, 120, 160)
+ cr.clip ()
+ cr.set_source_pixbuf (self.pixbuf, 10, 10)
+ cr.paint ()
+ if self.number:
+ cr.set_source_rgba (0.6, 0.6, 0.6, 0.8)
+ cr.paint ()
+ text = '<span font_desc="Sans 42"><b>%s</b></span>' % self.number
+ pcr, layout = prepare_text (cr, text)
+ text_width, text_height = layout.get_pixel_size ()
+ _, _, width, height = self.get_allocation ()
+ cr.move_to ((width - text_width) / 2, (height - text_height) / 2)
+ colors_count = len (self.number_colors)
+ color_id = (self.number - 1) % colors_count
+ cr.set_source_rgb (*self.number_colors[color_id])
+ pcr.show_layout (layout)
+
+class ImageItem (gtk.VBox):
+
+ path = None
+ table = None
+
+ preview = None
+
+ cropped = None
+ cropper = None
+ cropperWindow = None
+
+ def __init__ (self, path, table):
+ super (ImageItem, self).__init__ ()
+ self.path = path
+ self.table = table
+ self.cropper = cropper.CropperUi (path, target_width, target_height)
+ self.cropped = self.cropper.get_cropped_image ()
+ self.preview = ImagePreview (self.cropped)
+ width, height = self.preview.get_size_request ()
+ self.set_size_request (width, height + 35)
+ self.pack_start (self.preview, False, False)
+ box = gtk.HBox (homogeneous = True)
+ self.pack_start (box, False, False)
+ use_button = gtk.CheckButton ("Use")
+ use_button.connect ("toggled", self.on_use)
+ box.pack_start (use_button, False, False)
+ crop_button = gtk.Button ("Crop")
+ crop_button.connect ("clicked", self.on_crop)
+ box.pack_start (crop_button, False, False)
+
+ def on_use (self, toggle):
+ if toggle.get_active ():
+ self.table.select_item (self)
+ else:
+ self.table.unselect_item (self)
+
+ def on_crop (self, widget):
+ self.cropperWindow = gtk.Window ()
+ self.cropperWindow.set_title ("Wallpaper Creator - Crop")
+ self.cropperWindow.set_modal (True)
+ self.cropperWindow.set_transient_for (mainWindow)
+ base_width, base_height = 800, 600
+ pixbuf_width = self.cropper.pixbuf.get_width ()
+ pixbuf_height = self.cropper.pixbuf.get_height ()
+ if pixbuf_height > pixbuf_width:
+ height = base_height
+ width = float (base_height) * pixbuf_width / pixbuf_height
+ else:
+ width = base_width
+ height = float (base_width) * pixbuf_height / pixbuf_width
+ self.cropper.set_size_request (int (width), int (height))
+ box = gtk.VBox ()
+ box.pack_start (self.cropper, True, True)
+ ok_button = gtk.Button ("Crop")
+ ok_button.connect ("clicked", self.crop)
+ box.pack_start (ok_button, False, False)
+ self.cropperWindow.add (box)
+ self.cropperWindow.show_all ()
+
+ def crop (self, button):
+ self.cropped = self.cropper.get_cropped_image ()
+ self.preview.set_cropped (self.cropped)
+ self.cropperWindow.get_child ().remove (self.cropper)
+ self.cropperWindow.destroy ()
+
+ def set_number (self, number):
+ self.preview.set_number (number)
+
+class ImageTable (gtk.Fixed):
+
+ items = None
+ selected = None
+ alloc = None
+
+ def __init__ (self):
+ super (ImageTable, self).__init__ ()
+ self.connect_after ("size-allocate", self.on_size_allocate)
+ self.items = []
+ self.selected = []
+
+ def on_size_allocate (self, widget, alloc):
+ if alloc == self.alloc:
+ return
+ self.alloc = alloc
+ for i in range (len (self.items)):
+ item = self.items[i]
+ self.move (item, *self.get_item_pos (item, i))
+
+ def get_item_pos (self, item, i):
+ width, height = item.get_size_request ()
+ alloc = self.get_allocation ()
+ per_line = alloc.width / width
+ if per_line == 0:
+ return 0, 0
+ line = i / per_line
+ pos = i - per_line * line
+ return pos * width, line * height
+
+ def add_files (self, files):
+ for f in files:
+ item = ImageItem (f, self)
+ self.put (item, *self.get_item_pos (item, len (self.items)))
+ self.items.append (item)
+ self.show_all ()
+
+ def select_item (self, item):
+ self.selected.append (item)
+ item.set_number (len (self.selected))
+
+ def unselect_item (self, item):
+ if item in self.selected:
+ self.selected.remove (item)
+ item.set_number (None)
+ for i in range (len (self.selected)):
+ self.selected[i].set_number (i + 1)
+
+ def get_selection (self):
+ return [item.cropped for item in self.selected]
+
+def add_image_filters (dialog):
+ filter = gtk.FileFilter ()
+ filter.set_name ("All files")
+ filter.add_pattern ("*")
+ dialog.add_filter (filter)
+ filter = gtk.FileFilter ()
+ filter.set_name ("Images")
+ filter.add_mime_type ("image/png")
+ filter.add_mime_type ("image/jpeg")
+ filter.add_pattern ("*.png")
+ filter.add_pattern ("*.jpg")
+ dialog.add_filter (filter)
+ dialog.set_filter (filter)
+
+class PreviewWindow (gtk.Window):
+
+ def __init__ (self, parent, image):
+ super (PreviewWindow, self).__init__ ()
+ self.set_modal (True)
+ self.set_transient_for (parent)
+ self.set_title ("Wallpaper Creator - Preview")
+ self.pixbuf = pilimage_to_pixbuf (image)
+ box = gtk.VBox ()
+ box.pack_start (gtk.image_new_from_pixbuf (self.pixbuf), True, True)
+ hbox = gtk.HBox ()
+ cancel_button = gtk.Button (stock = gtk.STOCK_CANCEL)
+ cancel_button.connect ("clicked", self.on_cancel)
+ save_button = gtk.Button (stock = gtk.STOCK_SAVE)
+ save_button.connect ("clicked", self.on_save)
+ hbox.pack_end (cancel_button, False, False)
+ hbox.pack_end (save_button, False, False)
+ box.pack_start (hbox, False, False)
+ self.add (box)
+
+ def on_save (self, widget):
+ dialog = gtk.FileChooserDialog (title = "Save to...", parent = self,
+ action = gtk.FILE_CHOOSER_ACTION_SAVE,
+ buttons = (gtk.STOCK_CANCEL,
+ gtk.RESPONSE_CANCEL,
+ gtk.STOCK_OPEN,
+ gtk.RESPONSE_OK))
+ add_image_filters (dialog)
+ response = dialog.run ()
+ if response == gtk.RESPONSE_OK:
+ path = dialog.get_filename ()
+ if path.lower ().endswith (".png"):
+ self.pixbuf.save (path, "png")
+ else:
+ self.pixbuf.save (path, "jpeg", {"quality": "95"})
+ self.destroy ()
+ dialog.destroy ()
+
+ def on_cancel (self, widget):
+ self.destroy ()
+
+def load_files (window, table):
+ dialog = gtk.FileChooserDialog (title = "Open images", parent = window,
+ action = gtk.FILE_CHOOSER_ACTION_OPEN,
+ buttons = (gtk.STOCK_CANCEL,
+ gtk.RESPONSE_CANCEL,
+ gtk.STOCK_OPEN,
+ gtk.RESPONSE_OK))
+ dialog.set_select_multiple (True)
+
+ add_image_filters (dialog)
+
+ if dialog.run () == gtk.RESPONSE_OK:
+ filenames = dialog.get_filenames ()
+ else:
+ filenames = None
+ dialog.destroy ()
+ if filenames:
+ table.add_files (filenames)
+
+def make_wallpaper (window, table):
+ image = generator.assemble (table.get_selection ())
+ preview = PreviewWindow (window, image)
+ preview.show_all ()
+
+def main ():
+ global mainWindow
+ builder = gtk.Builder ()
+ builder.add_from_file ("wpcreator.gtkbuilder")
+ builder.get_object ("targetWidthSpin").set_value (target_width)
+ builder.get_object ("targetHeightSpin").set_value (target_height)
+ table = ImageTable ()
+ builder.get_object ("scroller").add_with_viewport (table)
+ mainWindow = builder.get_object ("mainWindow")
+ mainWindow.set_default_size (800, 600)
+ mainWindow.connect ("delete-event", gtk.main_quit)
+ handlers = {"load_files": lambda widget: load_files (mainWindow, table),
+ "make_wallpaper": lambda widget: make_wallpaper (mainWindow,
+ table)}
+ builder.connect_signals (handlers)
+ mainWindow.show_all ()
+ gtk.main ()