# -*- coding: UTF-8 -*- # Copyright 2007-2008 One Laptop Per Child # Copyright 2007 Gerard J. Cerchio # Copyright 2008 Andrés Ambrois # # 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 import gobject import gtk from math import pi as PI import logging logger = logging.getLogger('PlayGo.GoBoardWidget') class GoBoardWidget(gtk.Widget): ''' A Go Board Widget ''' __gsignals__ = { 'insert-requested': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (gobject.TYPE_INT, gobject.TYPE_INT)), } #TODO: this should default to DEFAULT_SIZE, not 19, but this is defined in activity.py not here def __init__(self, status, size=19): gtk.Widget.__init__(self) self.status = status self.size = size self.lastX = -1 self.lastY = -1 self.territories = None def do_realize(self): """Called when the widget should create all of its windowing resources. Create our gtk.gdk.Window and load our pixmaps.""" # First set an internal flag telling that we're realized self.set_flags(self.flags() | gtk.REALIZED) # Create our window and set the event masks we need self.window = gtk.gdk.Window( self.get_parent_window(), width=self.allocation.width, height=self.allocation.height, window_type=gtk.gdk.WINDOW_CHILD, wclass=gtk.gdk.INPUT_OUTPUT, event_mask=self.get_events() | gtk.gdk.EXPOSURE_MASK | gtk.gdk.BUTTON1_MOTION_MASK | gtk.gdk.BUTTON_PRESS_MASK | gtk.gdk.POINTER_MOTION_MASK | gtk.gdk.POINTER_MOTION_HINT_MASK | gtk.gdk.BUTTON_RELEASE_MASK) # Asociate ourselves with this window self.window.set_user_data(self) # Set this window's style self.style.attach(self.window) # The default color of the background should be what # the style (theme engine) tells us. self.style.set_background(self.window, gtk.STATE_NORMAL) self.window.move_resize(*self.allocation) # Load the board pixmap pixbuf = gtk.gdk.pixbuf_new_from_file("./images/board.gif") self.BoardPixmap, mask = pixbuf.render_pixmap_and_mask() del pixbuf # Load the white stone pixmap self.WhitePixbuf = gtk.gdk.pixbuf_new_from_file("./images/white.gif") # Load the black stone pixmap self.BlackPixbuf = gtk.gdk.pixbuf_new_from_file("./images/black.gif") self.gc = self.style.fg_gc[gtk.STATE_NORMAL] self.connect('button-release-event', self.button_release_cb) def draw_lines(self): ctx = self.window.cairo_create() # Single width black lines ctx.set_line_width(1) ctx.set_source_rgba(0, 0, 0, 1) # Horizontal lines for i in xrange(1, self.size + 1): ctx.move_to( self.unit, i * self.unit) ctx.line_to(self.size * self.unit, i * self.unit ) # Vertical lines for i in xrange(1, self.size + 1): ctx.move_to(i * self.unit, self.unit ) ctx.line_to(i * self.unit, self.size * self.unit) ctx.stroke() # star point coords per board size if self.size == 19 : seq = [ 4, 10, 16 ] elif self.size == 13 : seq = [ 4, 7, 10 ] elif self.size == 9 : seq = [ 3, 7 ] # set the middle singleton ctx.arc( self.unit * 5, self.unit * 5, 3, 0, PI * 2) ctx.fill_preserve() ctx.stroke() else : seq = [] # stroke in the star points #TODO: adjust size for teeny boards for x in seq : for y in seq : ctx.arc( self.unit * x, self.unit * y, 3, 0, PI * 2) ctx.fill_preserve() ctx.stroke() def do_unrealize(self): # The do_unrealized method is responsible for freeing the GDK resources # De-associate the window we created in do_realize with ourselves self.window.destroy() def do_size_request(self, requisition): """From Widget.py: The do_size_request method Gtk+ is calling on a widget to ask it the widget how large it wishes to be. It's not guaranteed that gtk+ will actually give this size to the widget. So we will send gtk+ an appropiate minimum size""" requisition.height = 500 requisition.width = 500 def do_size_allocate(self, allocation): """The do_size_allocate is called by when the actual size is known and the widget is told how much space could actually be allocated Save the allocated space self.allocation = allocation.""" logger.debug('Allocating %s x %s for widget', allocation.height, allocation.width) self.allocation = allocation if self.flags() & gtk.REALIZED: self.window.move_resize(*allocation) def do_expose_event(self, event=None): """This is where the widget must draw itself.""" #Scale everything self.unit = (min(self.allocation.height, self.allocation.width)+10)/(self.size + 1) self.ScaledBlackPixbuf = self.BlackPixbuf.scale_simple( int(self.unit), int(self.unit), gtk.gdk.INTERP_BILINEAR ) self.ScaledWhitePixbuf = self.WhitePixbuf.scale_simple( int(self.unit), int(self.unit), gtk.gdk.INTERP_BILINEAR ) #Draw the board self.window.draw_drawable(self.gc, self.BoardPixmap, 0, 0, 0, 0, self.allocation.width, self.allocation.height) #Draw the lines self.draw_lines() #Draw the stones self.draw_stones(self.status) #Draw scored terriotires if they exist (end of game) if self.territories: self.draw_scored_territories(self.territories) def get_mouse_event_xy(self, event): """ calculate the x and y position on the board given pixel address """ x0 = 0 #self.get_allocation().x y0 = 0 #self.get_allocation().y x = int(( ( event.x - x0 ) / self.unit ) - 0.5) y = int(( ( event.y - y0 ) / self.unit ) - 0.5) if x > self.size - 1: x = self.size - 1 if y > self.size - 1: y = self.size - 1 return x, y def draw_ghost_stone(self, x, y, color): x, y = self.get_pixel_from_coordinates(x, y) if x == self.lastX and y == self.lastY: return if self.lastX is not -1 : self.window.invalidate_rect(gtk.gdk.Rectangle(int(self.lastX - self.unit/2), int(self.lastY - self.unit/2), int(self.unit), int(self.unit)), False) self.lastX = x self.lastY = y ctx = self.window.cairo_create() if color is 'B': ctx.set_source_rgba(0, 0, 0, .5 ) else: ctx.set_source_rgba(0xff, 0xff, 0xff, .5 ) ctx.arc( self.lastX, self.lastY, self.unit/2 -4, 0, 2 * PI) ctx.fill_preserve() ctx.stroke() del ctx def button_release_cb(self, widget, event): x, y = self.get_mouse_event_xy(event) self.emit('insert-requested', x, y) def draw_stone(self, x, y, color, widget): """ paint a single stone on a point """ x = x + 1 y = y + 1 ctx = self.window.cairo_create() ct = gtk.gdk.CairoContext(ctx) if color == 'B': ct.set_source_pixbuf(self.ScaledBlackPixbuf, self.unit*x - self.unit/2, self.unit*y - self.unit/2 ) else: ct.set_source_pixbuf(self.ScaledWhitePixbuf, self.unit*x - self.unit/2, self.unit*y - self.unit/2 ) ctx.paint() def draw_stones(self, status): for x in status.keys(): self.draw_stone(x[0], x[1], status[x], self) #TODO: right now the stone image is aliased to the background color, so this ends up looking a bit funky #one fix would be to use non-aliased stone images, and let cairo alias them for us (can it?) def draw_scored_territory(self, x, y, color, widget): x = x + 1 y = y + 1 ctx = self.window.cairo_create() if color == 'B': ctx.set_source_rgba(0, 0, 0, 1) else: ctx.set_source_rgba(0xff, 0xff, 0xff, 1) ctx.set_line_width(4) # Horizontal mark ctx.move_to(self.unit*x - self.unit/4, self.unit*y) ctx.line_to(self.unit*x + self.unit/4, self.unit*y) # Vertical mark ctx.move_to(self.unit*x, self.unit*y - self.unit/4) ctx.line_to(self.unit*x, self.unit*y + self.unit/4) ctx.stroke() def draw_scored_territories(self, territories): for color in territories.keys(): for n in territories[color]: self.draw_scored_territory(n[0], n[1], color, self) def redraw_area(self, x, y): x, y = self.get_pixel_from_coordinates(x, y) self.window.invalidate_rect(gtk.gdk.Rectangle(int(x - self.unit/2), int(y - self.unit/2), int(self.unit), int(self.unit)), False) def get_pixel_from_coordinates(self, x, y): if x > self.size - 1: x = self.size - 1 if y > self.size - 1: y = self.size - 1 x = (x+1) * self.unit y = (y+1) * self.unit return x, y def clear(self): self.lastX = -1 self.lastY = -1 self.do_expose_event()