# -*- coding: utf-8 -*- # # Copyright 2012 Manuel QuiƱones # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY 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, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA from gettext import gettext as _ import logging import gtk import gobject import cairo # This prevents the expose callback to draw a large stroke each time: STROKE_MAX_POINTS = 80 class Drawing(gtk.DrawingArea): __gsignals__ = {'width-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (gobject.TYPE_INT,)), 'color-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (gobject.TYPE_STRING,)), } def __init__(self, parent): super(Drawing, self).__init__() # Communication with the activity, for sharing: self._parent = parent # This are temporal points stored while the stroke is being # made. When the stroke is finished (release event) the # stroke is applied to the canvas. self._stroke_points = [] # Our drawing canvas: self._drawing_canvas = None # The settings of our brush: self._settings = { 'stroke color': (1.0, 0.0, 0.0, 0.3), 'stroke width': 8, } # Sharing? self.we_are_sharing = False # The masks to capture the events we are interested in: self.add_events(gtk.gdk.EXPOSURE_MASK | gtk.gdk.VISIBILITY_NOTIFY_MASK) self.add_events(gtk.gdk.BUTTON_PRESS_MASK | \ gtk.gdk.BUTTON_RELEASE_MASK | \ gtk.gdk.BUTTON1_MOTION_MASK) # Connect the callbacks: self.connect("expose-event", self._expose_cb) self.connect("button-press-event", self._press_cb) self.connect("motion-notify-event", self._motion_cb) self.connect("button-release-event", self._release_cb) def setup(self, width, height, clear=True): """Setup a blank canvas of specified size.""" logging.debug("drawing set up") if clear: self._drawing_canvas = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height) context = cairo.Context(self._drawing_canvas) context.rectangle(0, 0, width, height) context.set_source_rgb(1.0, 1.0, 1.0) context.fill() self.queue_draw() def set_sharing(self, share=True): """Set sharing True or False.""" self.we_are_sharing = share def get_size(self): """Return the size of the current canvas.""" width = self._drawing_canvas.get_width() height = self._drawing_canvas.get_height() return width, height def remote_stroke(self, stroke_points, settings): """Draw stroke from other player.""" context = cairo.Context(self._drawing_canvas) self._set_stroke_context(context, settings) self._paint_stroke(context, stroke_points) self.queue_draw() def set_stroke_color(self, color): if isinstance(color, unicode): color = gtk.gdk.Color(color) self._settings['stroke color'] = (color.red_float, color.green_float, color.blue_float, 0.3) self.emit('color-changed', self.get_stroke_color()) def get_stroke_color(self): components = self._settings['stroke color'][:3] return gtk.gdk.Color(*components).to_string() def set_stroke_width(self, width): self._settings['stroke width'] = width self.emit('width-changed', width) def get_stroke_width(self): return self._settings['stroke width'] def clear_drawing_canvas(self): width, height = self.get_size() self.setup(width, height) if self.we_are_sharing: self._parent.send_new_drawing() def _set_stroke_context(self, context, settings=None): """Set the settings of our brush to the Cairo context.""" if settings is None: settings = self._settings context.set_source_rgba(*settings['stroke color']) context.set_line_width(settings['stroke width']) def _paint_stroke(self, context, stroke_points): """Draw lines from the list of points to the Cairo context""" point_x, point_y = stroke_points[0] context.move_to(point_x, point_y) for point_x, point_y in stroke_points[1:]: context.line_to(point_x, point_y) context.stroke() def _send_stroke_to_drawing_canvas(self, continue_stroke=False): """Paint current stroke in the canvas, clean stroke points.""" # Return if there is no stroke to paint: if self._stroke_points == []: return # Get context from canvas and paint: context = cairo.Context(self._drawing_canvas) self._set_stroke_context(context) self._paint_stroke(context, self._stroke_points) if self.we_are_sharing: self._parent.send_stroke(self._stroke_points, self._settings) # Clean the list of points: if continue_stroke: self._stroke_points = self._stroke_points[-1:] else: self._stroke_points = [] def _press_cb(self, widget, event): mouse_x, mouse_y, state = event.window.get_pointer() # We paint pressing the BUTTON1, return otherwise: if not state and gtk.gdk.BUTTON1_MASK: return self._stroke_points.append((mouse_x, mouse_y)) def _motion_cb(self, widget, event): if event.is_hint: mouse_x, mouse_y, state = event.window.get_pointer() else: mouse_x = event.x mouse_y = event.y state = event.state # We paint pressing the BUTTON1, return otherwise: if not state and gtk.gdk.BUTTON1_MASK: return # Paint stroke in canvas if it gets too big: if len(self._stroke_points) > STROKE_MAX_POINTS: self._send_stroke_to_drawing_canvas(continue_stroke=True) self._stroke_points.append((mouse_x, mouse_y)) self.queue_draw() def _release_cb(self, widget, event): self._send_stroke_to_drawing_canvas() def _expose_cb(self, widget, event): context = self.window.cairo_create() # Paint the canvas in the widget: context.set_source_surface(self._drawing_canvas) context.paint() # Return if there is no stroke to paint: if self._stroke_points == []: return # Get context from widget and paint: self._set_stroke_context(context) self._paint_stroke(context, self._stroke_points) def load_png(self, file_path): logging.debug("reading from png") self._drawing_canvas = cairo.ImageSurface.create_from_png(file_path) self.setup(self._drawing_canvas.get_width(), self._drawing_canvas.get_height(), clear=False) def save_png(self, file_path): logging.debug("writing to png") self._drawing_canvas.write_to_png(file_path)