~emesene-team/emesene/master

« back to all changes in this revision

Viewing changes to emesene/gui/gtkui/ImageAreaSelector.py

  • Committer: Riccardo (C10uD)
  • Date: 2012-07-01 16:54:34 UTC
  • Revision ID: git-v1:17ed298a3acb830f76aa2703351993cff749ed35
import2

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
'''a module that permit to resize an image added as avatar'''
 
2
# -*- coding: utf-8 -*-
 
3
 
 
4
#    This file is part of emesene.
 
5
#
 
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.
 
10
#
 
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.
 
15
#
 
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
 
19
 
 
20
import gtk
 
21
import cairo
 
22
import pango
 
23
from sys import platform
 
24
 
 
25
if platform == 'darwin':
 
26
    MAC = True
 
27
else:
 
28
    MAC = False
 
29
 
 
30
import logging
 
31
log = logging.getLogger('gtkui.ImageAreaSelector')
 
32
 
 
33
WIDTH=64
 
34
HEIGHT=64
 
35
NORMALBACKGROUND = gtk.gdk.Color(65025,65025,46155)
 
36
 
 
37
class ImageAreaSelectorDialog(gtk.Dialog):
 
38
 
 
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'
 
43
 
 
44
    def __init__(self, callback, pixbuf, title = _("Select area of image"),
 
45
                 parent = None):
 
46
        gtk.Dialog.__init__(self, title, parent,
 
47
                            gtk.DIALOG_DESTROY_WITH_PARENT)
 
48
 
 
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))
 
57
 
 
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())
 
61
 
 
62
        if not MAC:
 
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)
 
69
 
 
70
        self.action_area.pack_end(self.button_cancel)
 
71
        self.action_area.pack_end(self.button_accept)
 
72
 
 
73
        ##Draw the statusBar
 
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)
 
81
        image = gtk.Image()
 
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)
 
85
 
 
86
        self.set_position(gtk.WIN_POS_CENTER_ALWAYS)
 
87
        self.vbox.pack_start(self.selector)
 
88
        self.vbox.pack_end(self.eventBox)
 
89
        self.vbox.show_all()
 
90
 
 
91
    def run(self):
 
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)
 
96
            self.destroy()
 
97
        else:
 
98
            self.destroy()
 
99
            return self.callback(gtk.RESPONSE_CANCEL, None)
 
100
 
 
101
    def _on_rcw_clicked(self, *args):
 
102
        self.selector.rotate(1)
 
103
    def _on_rccw_clicked(self, *args):
 
104
        self.selector.rotate(-1)
 
105
 
 
106
class ImageAreaSelector(gtk.DrawingArea):
 
107
 
 
108
    def __init__(self, button_accept):
 
109
        gtk.DrawingArea.__init__(self)
 
110
        self.connect("expose_event", self.expose)
 
111
        self.image = None
 
112
        self.pixbuf = None
 
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)
 
121
 
 
122
        self._pressed = False
 
123
        self._moved = False
 
124
        self._state = gtk.gdk.LEFT_PTR
 
125
        self._has_overlay = False
 
126
        self._angle = 0
 
127
        self.dx = 0
 
128
        self.dy = 0
 
129
 
 
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
 
136
            return
 
137
            #w = self.pixbuf.get_width()
 
138
            #h = self.pixbuf.get_height()
 
139
            #sw = min((w, h))
 
140
            #x1 = int((w-sw)/2)
 
141
            #y1 = int((h-sw)/2)
 
142
            #self.selection = (x1, y1, sw, sw)
 
143
        
 
144
        return self._trans_pixbuf.subpixbuf(*self.selection)
 
145
 
 
146
    def _get_selection(self):
 
147
        return (self._x, self._y, self._width, self._height)
 
148
        
 
149
    def _set_selection(self, (x, y, width, height)):
 
150
        self._x = x
 
151
        self._y = y
 
152
        self._width = width
 
153
        self._height = height
 
154
        self.redraw_canvas()
 
155
    selection = property(_get_selection, _set_selection)
 
156
 
 
157
    def _get_sel_coord(self):
 
158
        x1 = self._x
 
159
        y1 = self._y
 
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)
 
164
        else:
 
165
            self.button_accept.set_sensitive(False)
 
166
        return (x1, y1, x2, y2)
 
167
 
 
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:
 
172
            return
 
173
        if x1 > x2:
 
174
            x2 = x1
 
175
        if y1 > y2:
 
176
            y2 = y1
 
177
 
 
178
        self.selection = map(int, (x1, y1, x2-x1, y2-y1))
 
179
 
 
180
    def reset_selection(self):
 
181
        self.selection = (0,0,0,0)
 
182
        self.button_accept.set_sensitive(False)
 
183
 
 
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()
 
188
 
 
189
 
 
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
 
194
 
 
195
        widget.window.draw_drawable(widget.get_style().fg_gc[gtk.STATE_NORMAL],
 
196
                                    self.pixmap_shaded, 0, 0,
 
197
                                    0, 0,
 
198
                                    self.pixbuf.get_width(),
 
199
                                    self.pixbuf.get_height())
 
200
 
 
201
        widget.window.draw_pixbuf(None, pixbuf=self.pixbuf,
 
202
                                  src_x=self._x,
 
203
                                  src_y=self._y,
 
204
                                  dest_x= self._x,
 
205
                                  dest_y=self._y ,
 
206
                                  width=self._width, height=self._height,
 
207
                                  dither=gtk.gdk.RGB_DITHER_NORMAL,
 
208
                                  x_dither=0, y_dither=0)
 
209
        return False
 
210
 
 
211
 
 
212
 
 
213
    def rotate(self, angle):
 
214
        self._angle = (self._angle + angle) % 4
 
215
        angle = self.get_angle()
 
216
        self._init_pixbuf(angle, False)
 
217
 
 
218
    def get_angle(self):
 
219
        if self._angle == 1:
 
220
            angle = gtk.gdk.PIXBUF_ROTATE_CLOCKWISE
 
221
        elif self._angle == 2:
 
222
            angle = 180
 
223
        elif self._angle == 3:
 
224
            angle = gtk.gdk.PIXBUF_ROTATE_COUNTERCLOCKWISE
 
225
        else:
 
226
            angle = 0
 
227
        return angle
 
228
 
 
229
    def update_selection(self, event):
 
230
        # track mouse delta
 
231
        dx = event.x - self._mouse_x
 
232
        dy = event.y - self._mouse_y
 
233
 
 
234
        x1, y1, x2, y2 = self._get_sel_coord()
 
235
        if self._state == gtk.gdk.TOP_LEFT_CORNER:
 
236
            w = x2 - event.x
 
237
            h = y2 - event.y
 
238
            delta = max((w, h))
 
239
            x1 = x2 - delta
 
240
            y1 = y2 - delta
 
241
        elif self._state == gtk.gdk.TOP_RIGHT_CORNER:
 
242
            w = event.x - x1
 
243
            h = y2 - event.y
 
244
            delta = max((w, h))
 
245
            x2 = x1 + delta
 
246
            y1 = y2 - delta
 
247
        elif self._state == gtk.gdk.BOTTOM_RIGHT_CORNER:
 
248
            w = (event.x-x1)
 
249
            h = (event.y-y1)
 
250
            delta = max((w, h))
 
251
            x2 = x1 + delta
 
252
            y2 = y1 + delta
 
253
        elif self._state == gtk.gdk.BOTTOM_LEFT_CORNER:
 
254
            w = x2 - event.x
 
255
            h = (event.y-y1)
 
256
            delta = max((w, h))
 
257
            x1 = x2 - delta
 
258
            y2 = y1 + delta
 
259
        elif self._state == gtk.gdk.FLEUR:
 
260
            x1 += dx
 
261
            y1 += dy
 
262
            x2 += dx
 
263
            y2 += dy
 
264
        else:
 
265
            # mouse pressed outside current selection. Create new selection
 
266
            x1 = self._press_x
 
267
            y1 = self._press_y
 
268
            w = event.x - x1
 
269
            h = event.y - y1
 
270
            delta = max((w, h))
 
271
            x2 = x1 + delta
 
272
            y2 = y1 + delta
 
273
 
 
274
        self._mouse_x = event.x
 
275
        self._mouse_y = event.y
 
276
        self._set_sel_coord(x1, y1, x2, y2)
 
277
 
 
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
 
293
        else:
 
294
            self._state = gtk.gdk.LEFT_PTR
 
295
 
 
296
        self.window.set_cursor(gtk.gdk.Cursor(self._state))
 
297
 
 
298
    def motion_notify(self, widget, event):
 
299
        if self._pressed:
 
300
            self._moved = True
 
301
            self.update_selection(event)
 
302
        else:
 
303
            self.update_cursor(event)
 
304
 
 
305
    def button_press(self, widget, event):
 
306
        self._pressed = True
 
307
        self._moved = False
 
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()
 
314
 
 
315
    def configure_event(self, widget, event):
 
316
        x, y, width, height = widget.get_allocation()
 
317
 
 
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)
 
321
 
 
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
 
326
        return True
 
327
 
 
328
    def create_overlay(self):
 
329
        context = self.pixmap_shaded.cairo_create()
 
330
        width = self.pixbuf.get_width()
 
331
        height = self.pixbuf.get_height()
 
332
 
 
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)
 
337
        context.fill()
 
338
        context.paint()
 
339
 
 
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)
 
344
        overlay_cr.fill()
 
345
 
 
346
        context.set_source_surface (overlay, 0, 0)
 
347
        context.paint()
 
348
        self._has_overlay = True
 
349
 
 
350
 
 
351
    def redraw_canvas(self):
 
352
        if self.window:
 
353
            alloc = self.get_allocation()
 
354
            self.queue_draw_area(alloc.x, alloc.y, alloc.width, alloc.height)
 
355
 
 
356
    def do_size_request(self, requisition):
 
357
        if not self.pixbuf:
 
358
            return
 
359
        requisition.width = self.pixbuf.get_width()
 
360
        requisition.height = self.pixbuf.get_height()
 
361
 
 
362
    def set_from_pixbuf(self, pixbuf):
 
363
        h = pixbuf.get_height()
 
364
        w = pixbuf.get_width()
 
365
        edge = max(w, h)
 
366
        self._trans_pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, True, 8,
 
367
                                            edge, edge)
 
368
        self._disp_pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, True, 8,
 
369
                                           edge, edge)
 
370
 
 
371
        self._trans_pixbuf.fill(0xffffff00)
 
372
        self._disp_pixbuf.fill(0x66666666)
 
373
 
 
374
        self.dx = dx = (edge-w)/2
 
375
        self.dy = dy = (edge-h)/2
 
376
 
 
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)
 
381
 
 
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)
 
388
 
 
389
            edge = int(edge * scale)
 
390
 
 
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 )
 
395
            self.dx *= scale 
 
396
            self.dy *= scale
 
397
 
 
398
        self._init_pixbuf()
 
399
 
 
400
    def _init_pixbuf(self, angle=None, create_selection=True):
 
401
        self.pixbuf = self._disp_pixbuf.copy()
 
402
 
 
403
        if angle:
 
404
            self.pixbuf = self.pixbuf.rotate_simple(angle)
 
405
            self.configure_event(self, None)
 
406
            tmp = self.dx
 
407
            self.dx = self.dy
 
408
            self.dy = tmp
 
409
 
 
410
        self._has_overlay = False
 
411
        w = 0
 
412
        h = 0
 
413
 
 
414
        if create_selection:
 
415
            sw = min((w, h))
 
416
            x1 = int((w-sw)/2)
 
417
            y1 = int((h-sw)/2)
 
418
            self.selection = (x1, y1, sw, sw)
 
419
        else:
 
420
            self.selection = (0, 0, 0, 0)