1
'''a module that permit to resize an image added as avatar'''
2
# -*- coding: utf-8 -*-
4
# This file is part of emesene.
6
# emesene is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 3 of the License, or
9
# (at your option) any later version.
11
# emesene is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
# GNU General Public License for more details.
16
# You should have received a copy of the GNU General Public License
17
# along with emesene; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
23
from sys import platform
25
if platform == 'darwin':
31
log = logging.getLogger('gtkui.ImageAreaSelector')
35
NORMALBACKGROUND = gtk.gdk.Color(65025,65025,46155)
37
class ImageAreaSelectorDialog(gtk.Dialog):
39
NAME = 'ImageAreaSelector'
40
DESCRIPTION = _('The widget that permits to resize an image added as avatar')
41
AUTHOR = 'Mariano Guerra'
42
WEBSITE = 'www.emesene.org'
44
def __init__(self, callback, pixbuf, title = _("Select area of image"),
46
gtk.Dialog.__init__(self, title, parent,
47
gtk.DIALOG_DESTROY_WITH_PARENT)
49
self.callback = callback
50
self.set_default_response(gtk.RESPONSE_CANCEL)
51
self.set_resizable(False)
52
self.button_accept = gtk.Button(stock=gtk.STOCK_OK)
53
self.button_accept.connect('clicked', lambda *args: self.response(gtk.RESPONSE_OK))
54
self.button_accept.set_sensitive(False)
55
self.button_cancel = gtk.Button(stock=gtk.STOCK_CANCEL)
56
self.button_cancel.connect('clicked', lambda *args: self.response(gtk.RESPONSE_CANCEL))
58
self.selector = ImageAreaSelector(self.button_accept)
59
self.selector.set_from_pixbuf(pixbuf)
60
self.selector.set_size_request(self.selector.pixbuf.get_height(), self.selector.pixbuf.get_width())
63
self.button_rcw = gtk.Button(_("Rotate"))
64
self.button_rcw.connect("clicked", self._on_rcw_clicked)
65
self.button_rccw = gtk.Button(_("Rotate"))
66
self.button_rccw.connect("clicked", self._on_rccw_clicked)
67
self.action_area.pack_start(self.button_rccw)
68
self.action_area.pack_end(self.button_rcw)
70
self.action_area.pack_end(self.button_cancel)
71
self.action_area.pack_end(self.button_accept)
74
self.eventBox = gtk.EventBox()
75
box = gtk.HBox(False, 0)
76
self.eventBox.set_size_request(0, 30)
77
self.eventBox.add(box)
78
self.eventBox.modify_bg(gtk.STATE_NORMAL, NORMALBACKGROUND)
79
self.label = gtk.Label(_('Draw a square to select an area'))
80
self.label.set_ellipsize(pango.ELLIPSIZE_END)
82
image.set_from_stock (gtk.STOCK_DIALOG_INFO, gtk.ICON_SIZE_LARGE_TOOLBAR)
83
box.pack_start(image, False, False, 5)
84
box.pack_start(self.label, True, True, 5)
86
self.set_position(gtk.WIN_POS_CENTER_ALWAYS)
87
self.vbox.pack_start(self.selector)
88
self.vbox.pack_end(self.eventBox)
92
response = gtk.Dialog.run(self)
93
if response == gtk.RESPONSE_OK:
94
pixbuf = self.selector.get_selected()
95
self.callback(gtk.RESPONSE_OK, pixbuf)
99
return self.callback(gtk.RESPONSE_CANCEL, None)
101
def _on_rcw_clicked(self, *args):
102
self.selector.rotate(1)
103
def _on_rccw_clicked(self, *args):
104
self.selector.rotate(-1)
106
class ImageAreaSelector(gtk.DrawingArea):
108
def __init__(self, button_accept):
109
gtk.DrawingArea.__init__(self)
110
self.connect("expose_event", self.expose)
113
self.button_accept = button_accept #handle sensitivity of accept button
114
self.connect("button_press_event", self.button_press)
115
self.connect("button_release_event", self.button_release)
116
self.connect("motion_notify_event", self.motion_notify)
117
self.connect("configure_event", self.configure_event)
118
self.add_events(gtk.gdk.BUTTON_PRESS_MASK |
119
gtk.gdk.BUTTON_RELEASE_MASK |
120
gtk.gdk.POINTER_MOTION_MASK)
122
self._pressed = False
124
self._state = gtk.gdk.LEFT_PTR
125
self._has_overlay = False
130
def get_selected(self):
131
angle = self.get_angle()
132
self._trans_pixbuf = self._trans_pixbuf.rotate_simple(angle)
133
# handle no selection
134
if self.selection == (0,0,0,0):
135
#this should never happens
137
#w = self.pixbuf.get_width()
138
#h = self.pixbuf.get_height()
142
#self.selection = (x1, y1, sw, sw)
144
return self._trans_pixbuf.subpixbuf(*self.selection)
146
def _get_selection(self):
147
return (self._x, self._y, self._width, self._height)
149
def _set_selection(self, (x, y, width, height)):
153
self._height = height
155
selection = property(_get_selection, _set_selection)
157
def _get_sel_coord(self):
160
x2 = x1 + self._width
161
y2 = y1 + self._height
162
if x1 != x2: #means there is a selection
163
self.button_accept.set_sensitive(True)
165
self.button_accept.set_sensitive(False)
166
return (x1, y1, x2, y2)
168
def _set_sel_coord(self, x1, y1, x2, y2):
169
if x1 < 0 or (x1 > 0 and x1 < self.dx) or y1 < 0 \
170
or (y1 > 0 and y1 < self.dy) or x2 > self.pixbuf.get_width() - self.dx \
171
or y2 > self.pixbuf.get_height() - self.dy:
178
self.selection = map(int, (x1, y1, x2-x1, y2-y1))
180
def reset_selection(self):
181
self.selection = (0,0,0,0)
182
self.button_accept.set_sensitive(False)
184
def button_release(self, *args):
185
self._pressed = False
186
if not self._moved and self._state == gtk.gdk.LEFT_PTR:
187
self.reset_selection()
190
def expose(self, widget, event):
191
if not self._has_overlay and not MAC:
192
self.create_overlay()
193
x , y, width, height = event.area
195
widget.window.draw_drawable(widget.get_style().fg_gc[gtk.STATE_NORMAL],
196
self.pixmap_shaded, 0, 0,
198
self.pixbuf.get_width(),
199
self.pixbuf.get_height())
201
widget.window.draw_pixbuf(None, pixbuf=self.pixbuf,
206
width=self._width, height=self._height,
207
dither=gtk.gdk.RGB_DITHER_NORMAL,
208
x_dither=0, y_dither=0)
213
def rotate(self, angle):
214
self._angle = (self._angle + angle) % 4
215
angle = self.get_angle()
216
self._init_pixbuf(angle, False)
220
angle = gtk.gdk.PIXBUF_ROTATE_CLOCKWISE
221
elif self._angle == 2:
223
elif self._angle == 3:
224
angle = gtk.gdk.PIXBUF_ROTATE_COUNTERCLOCKWISE
229
def update_selection(self, event):
231
dx = event.x - self._mouse_x
232
dy = event.y - self._mouse_y
234
x1, y1, x2, y2 = self._get_sel_coord()
235
if self._state == gtk.gdk.TOP_LEFT_CORNER:
241
elif self._state == gtk.gdk.TOP_RIGHT_CORNER:
247
elif self._state == gtk.gdk.BOTTOM_RIGHT_CORNER:
253
elif self._state == gtk.gdk.BOTTOM_LEFT_CORNER:
259
elif self._state == gtk.gdk.FLEUR:
265
# mouse pressed outside current selection. Create new selection
274
self._mouse_x = event.x
275
self._mouse_y = event.y
276
self._set_sel_coord(x1, y1, x2, y2)
278
def update_cursor(self, event):
279
fuzz = max((5, int(self._width * 0.05)))
280
if abs(event.y - self._y) < fuzz:
281
if abs(event.x -self._x) < fuzz:
282
self._state = gtk.gdk.TOP_LEFT_CORNER
283
elif abs(event.x - (self._x + self._width)) < fuzz:
284
self._state = gtk.gdk.TOP_RIGHT_CORNER
285
elif abs(event.y - (self._y + self._height)) < fuzz:
286
if abs(event.x - self._x) < fuzz:
287
self._state = gtk.gdk.BOTTOM_LEFT_CORNER
288
elif abs(event.x -(self._x + self._width)) < fuzz:
289
self._state = gtk.gdk.BOTTOM_RIGHT_CORNER
290
elif event.x > self._x and event.x < self._x + self._width \
291
and event.y > self._y and event.y < self._y + self._height:
292
self._state = gtk.gdk.FLEUR
294
self._state = gtk.gdk.LEFT_PTR
296
self.window.set_cursor(gtk.gdk.Cursor(self._state))
298
def motion_notify(self, widget, event):
301
self.update_selection(event)
303
self.update_cursor(event)
305
def button_press(self, widget, event):
308
self._mouse_x = event.x
309
self._mouse_y = event.y
310
self._press_x = event.x
311
self._press_y = event.y
312
if event.button == 3:
313
self.reset_selection()
315
def configure_event(self, widget, event):
316
x, y, width, height = widget.get_allocation()
318
self.pixmap = gtk.gdk.Pixmap(widget.window, width, height)
319
self.pixmap.draw_rectangle(widget.get_style().white_gc,
320
True, 0, 0, width, height)
322
self.pixmap_shaded = gtk.gdk.Pixmap(widget.window, width, height)
323
self.pixmap_shaded.draw_rectangle(widget.get_style().white_gc,
324
True, 0, 0, width, height)
325
self._has_overlay = False
328
def create_overlay(self):
329
context = self.pixmap_shaded.cairo_create()
330
width = self.pixbuf.get_width()
331
height = self.pixbuf.get_height()
333
target = context.get_target()
334
overlay = target.create_similar(cairo.CONTENT_COLOR_ALPHA, width, height)
335
punch = target.create_similar(cairo.CONTENT_ALPHA, width, height)
336
context.set_source_pixbuf(self.pixbuf, 0, 0)
340
# Draw a grey overlay
341
overlay_cr = cairo.Context (overlay)
342
overlay_cr.set_source_rgba (0.4, 0.4, 0.4, 0.6)
343
overlay_cr.rectangle(0, 0, width, height)
346
context.set_source_surface (overlay, 0, 0)
348
self._has_overlay = True
351
def redraw_canvas(self):
353
alloc = self.get_allocation()
354
self.queue_draw_area(alloc.x, alloc.y, alloc.width, alloc.height)
356
def do_size_request(self, requisition):
359
requisition.width = self.pixbuf.get_width()
360
requisition.height = self.pixbuf.get_height()
362
def set_from_pixbuf(self, pixbuf):
363
h = pixbuf.get_height()
364
w = pixbuf.get_width()
366
self._trans_pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, True, 8,
368
self._disp_pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, True, 8,
371
self._trans_pixbuf.fill(0xffffff00)
372
self._disp_pixbuf.fill(0x66666666)
374
self.dx = dx = (edge-w)/2
375
self.dy = dy = (edge-h)/2
377
pixbuf.composite(self._trans_pixbuf, dx, dy, w, h, dx, dy, 1, 1,
378
gtk.gdk.INTERP_BILINEAR, 255)
379
pixbuf.composite(self._disp_pixbuf, dx, dy, w, h, dx, dy, 1, 1,
380
gtk.gdk.INTERP_BILINEAR, 255)
382
maxw = int(0.75 * gtk.gdk.screen_width())
383
maxh = int(0.75 * gtk.gdk.screen_height())
384
if edge >= maxw or edge >= maxh:
385
wscale = float(maxw) / edge
386
hscale = float(maxh) / edge
387
scale = min(wscale, hscale)
389
edge = int(edge * scale)
391
self._trans_pixbuf = self._trans_pixbuf.scale_simple(edge, edge,
392
gtk.gdk.INTERP_BILINEAR )
393
self._disp_pixbuf = self._disp_pixbuf.scale_simple(edge, edge,
394
gtk.gdk.INTERP_BILINEAR )
400
def _init_pixbuf(self, angle=None, create_selection=True):
401
self.pixbuf = self._disp_pixbuf.copy()
404
self.pixbuf = self.pixbuf.rotate_simple(angle)
405
self.configure_event(self, None)
410
self._has_overlay = False
418
self.selection = (x1, y1, sw, sw)
420
self.selection = (0, 0, 0, 0)