#!/usr/bin/env python
# -*- coding: utf-8 -*-
### BEGIN LICENSE
# Copyright (C) 2009 Manuel de la Pena <mandel@themacaque.com>
#This program is free software: you can redistribute it and/or modify it
#under the terms of the GNU General Public License version 3, as published
#by the Free Software Foundation.
#
#This program is distributed in the hope that it will be useful, but
#WITHOUT ANY WARRANTY; without even the implied warranties of
#MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
#PURPOSE. See the GNU General Public License for more details.
#
#You should have received a copy of the GNU General Public License along
#with this program. If not, see <http://www.gnu.org/licenses/>.
### END LICENSE
import gtk
import goocanvas
import os
from gtk import gdk
from macaco.macacoconfig import getdatapath
class ResizableImageEntry(gtk.VBox):
"""
Represents an entry that allow the user to point to an image in the
file system and selecte the area to crop in order to fit the size
limits of the app.
"""
def __init__(self, x=300, y=300):
"""
Creates a new entry that can be used to select an image and correctly
crop it.
"""
gtk.VBox.__init__(self)
# store the x and y
self.x = x
self.y = y
# init the image
self.pict_url = os.path.join(getdatapath(), 'media', 'contact-120.png')
self.pict = gdk.pixbuf_new_from_file(self.pict_url)
# set the image used in the shadows
self.shadow_pict = gdk.pixbuf_new_from_file(
os.path.join(getdatapath(), 'media', 'shadow.png'))
# set init the basic vars
self.selected_item = None
self.selection_rectangle = None
self.top_shadow = None
self.bottom_shadow = None
self.left_shadow = None
self.right_shadow = None
# we create the canvas that will be used to draw the image.
self.canvas = goocanvas.Canvas()
self.canvas.set_size_request(x,y)
self.canvas.show()
self.root = self.canvas.get_root_item()
# create an event box for the canvas
self.event = gtk.EventBox()
self.event.show()
self.event.add(self.canvas)
self.pack_start(self.event, expand=True, fill=True, padding=10)
self.pack_start(self._get_zoom_widget(), expand=True, fill=True, padding=10)
# create a button that will allow the user to change the image
# to crop
self.change_button = gtk.Button("Select image")
self.change_button.show()
self.pack_start(self.change_button, expand=False, fill=True, padding=10)
self.show()
# set a flag used to decide if the image is begin moved
self.moving = False
# get the root of the canvas
self.root = self.canvas.get_root_item()
# draw the image for the first time
# connect the different events
self.zoom.connect("value-changed", self._on_changed)
self.canvas.connect("button_press_event",self._mouse_down)
self.canvas.connect("button_release_event",self._mouse_up)
self.canvas.connect("motion_notify_event", self._move)
self.canvas.connect("scroll-event", self._scroll)
self.change_button.connect("clicked", self._change_image)
self.show_all()
# draw for the first time
self._draw(is_init=True)
def _get_zoom_widget(self):
# create a small and bigger image to indecate what the slider is for
small_pict_url = os.path.join(getdatapath(), 'media', 'icon-22.png')
small_pict = gdk.pixbuf_new_from_file_at_size(small_pict_url, 22, 22)
small_image = gtk.image_new_from_pixbuf(small_pict)
small_image.set_alignment(0, 0.85)
small_image.show()
big_pict_url = os.path.join(getdatapath(), 'media', 'icon-48.png')
big_pict = gdk.pixbuf_new_from_file_at_size(big_pict_url, 48, 48)
big_image = gtk.image_new_from_pixbuf(big_pict)
big_image.set_alignment(0, 1)
big_image.show()
self.zoom = gtk.HScale(adjustment=gtk.Adjustment(
value=100, lower=10, upper=200, step_incr=2))
self.zoom.show()
box = gtk.HBox()
box.pack_start(small_image, expand=False, fill=True)
box.pack_start(self.zoom, expand=True, fill=True, padding=10)
box.pack_start(big_image, expand=False, fill=True)
return box
def _scroll(self, menu, event):
if event.direction == gtk.gdk.SCROLL_DOWN:
self.zoom.set_value(self.zoom.get_value() - 2)
else:
self.zoom.set_value(self.zoom.get_value() + 2)
def _change_image(self, button):
"""
Allows to show a dialog that will be used to change the image in the
UI.
"""
dialog = gtk.FileChooserDialog(title="Avatar",action=gtk.FILE_CHOOSER_ACTION_OPEN,
buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK))
response = dialog.run()
if response == gtk.RESPONSE_OK:
# change the image url and draw making it init
self.pict_url = dialog.get_filename()
self.pict = gdk.pixbuf_new_from_file(self.pict_url)
# set the value of the zoom
self.zoom.set_value(100)
self._draw(is_init=True)
dialog.destroy()
def _scale_widget(self):
"""
Returns the HBox that will contain the slider used to let the user
know he can zoom in and out of the image
"""
box = gtk.HBox()
# create the zoom at 100%
self.zoom = gtk.HScale(adjustment=gtk.Adjustment(
value=100, lower=10, upper=200, step_incr=2))
box.pack_start(self.zoom, expand=False, fill=True, padding=10)
return box
def _draw(self, is_init=False):
# call the different draw steps
self._draw_image(is_init)
self._draw_selector()
self._draw_shadows()
def _draw_image(self, is_init=False):
"""
Takes care of drawing the image in the canvas in the current position.
is_init is used to tell if it is the first time to draw the image, this
is important since we want to store the position of the image the first
time.
"""
# remove the last image
if self.selected_item:
self.selected_item.remove()
# get the size of the canvas in order to place the img
self.canvas.set_bounds(0,0,self.x,self.x)
cont_left, cont_top, cont_right, cont_bottom = self.canvas.get_bounds()
img_w = self.pict.get_width()
img_h = self.pict.get_height()
img_left = (self.x - img_w)/2
img_top = (self.y - img_h)/2
if is_init:
self.image_x = img_left
self.image_y = img_top
self.selected_item = goocanvas.Image(parent=self.root, pixbuf=self.pict,
x=self.image_x,y=self.image_y)
def _draw_selector(self):
"""
Takes care of drwaing the rectangle that is used to select the
portion of the image that will be used.
"""
# remove the last selection square if present
if self.selection_rectangle:
self.selection_rectangle.remove()
self.select_left = (self.x - 120)/2
self.select_top = (self.y - 120)/2
self.selection_rectangle = goocanvas.Rect( parent=self.root,
width=120, height=120, stroke_color="black",
line_width=1.0, x=self.select_left, y=self.select_top)
def _draw_shadows(self):
"""
Takes care of drawing the sadows that will cover those parts of the
image that will not be taken into the final result.
"""
# remove shadows if fresent
if self.top_shadow:
self.top_shadow.remove()
if self.bottom_shadow:
self.bottom_shadow.remove()
if self.left_shadow:
self.left_shadow.remove()
if self.right_shadow:
self.right_shadow.remove()
# re-create the shadows
self.top_shadow = goocanvas.Rect( parent=self.root,
width=300, height=self.select_top, fill_pixbuf=self.shadow_pict,
line_width=0, x=0, y=0)
self.bottom_shadow = goocanvas.Rect( parent=self.root,
width=300, height=300 - (self.select_top + 120), fill_pixbuf=self.shadow_pict,
line_width=0, x=0, y=self.select_top+120)
self.left_shadow = goocanvas.Rect( parent=self.root,
width=self.select_left, height=120, fill_pixbuf=self.shadow_pict,
line_width=0, x=0, y=self.select_top)
self.right_shadow = goocanvas.Rect( parent=self.root,
width=300 - (self.select_left + 120), height=120,
fill_pixbuf=self.shadow_pict, line_width=0, x=self.select_left + 120,
y=self.select_top)
def _move(self, item, event):
if self.moving:
self.image_x -= (self.initial_x - event.x)
self.image_y -= (self.initial_y - event.y)
self.initial_x = event.x
self.initial_y = event.y
# re draw the images with the new locations
self._draw()
def _on_changed(self, widget):
value = widget.get_value()/100
aux_pict = gdk.pixbuf_new_from_file(self.pict_url)
# we scale the pixbuf according to the value of the slider
self.pict = aux_pict .scale_simple(
int(aux_pict.get_width() * value),
int(aux_pict.get_height() * value), gtk.gdk.INTERP_TILES)
self._draw()
def _mouse_down(self, item, event):
"""
Callback used when the mouse in pressed.
"""
self.moving = True
self.initial_x = event.x
self.initial_y = event.y
self.canvas.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.HAND1))
def _mouse_up(self, item, event):
"""
Callback used when the mouse is released.
"""
self.moving = False
self.canvas.window.set_cursor(None)
@property
def pixbuf(self):
"""
Returns a pixbuf of the selected area.
"""
# create a pixbuf with the original image
pict = gtk.gdk.Pixbuf(gdk.COLORSPACE_RGB, True, 8, 120, 120)
# scale by using the diff between the selector position and the image
# using the value of the zoom and the scale percentage
scale_factor = self.zoom.get_value()/100
x_pos = self.select_left - self.image_x
y_pos = self.select_top - self.image_y
sub_pict = self.pict.subpixbuf(x_pos, y_pos, 120, 120)
return sub_pict