# TheDrawingAreaEventBox.py Manages the drawing component # # Copyright (C) 2011 Laurent BERNABE # # 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 pygtk pygtk.require('2.0') import gtk import cairo import gobject class TheDrawingAreaEventBox(gtk.EventBox): ''' A gtk.EventBox wrapping the DrawingArea (or canvas) of the application We use an EventBox in order to pass events to the contained DrawingArea ''' __DARK_GREEN = (27.0/255.0, 103.0/255.0,46.0/255.0) __WHITE = (1.0, 1.0, 1.0) def __init__(self): ''' Simple constructor without argument ''' gtk.EventBox.__init__(self) self.__setFields() self.__setConnections() def __setFields(self): ''' Gives values to fields ''' self.__drawingArea = gtk.DrawingArea() self.add(self.__drawingArea) self.__figures = [] self.__lineThickness = 1 # number of steps (for the figures overall) currently drawn # only used in reading mode (which is the starting mode) self.__drawn_steps = -1 # __overall_maximum_steps stores the steps of all the figures # only used in reading mode (which is the starting mode) self.__overall_maximum_steps = -1 # __current_maximum_steps stores the maximum steps the updater thread # is allowing to draw. In each cycle of the updater thread, this value # is incremented, giving this impression of a tape reading # only used in reading mode (which is the starting mode) self.__current_maximum_steps = -1 # the updater delay in milliseconds # => at which intervals, in reading mode, # the redraws should be done self.__updater_delay_milliseconds = -1 self.__mousePressed = False self.__readingMode = False def __setConnections(self): ''' Define the events connections ''' self.__drawingArea.connect("expose-event", self.__paintSurface) self.connect("motion-notify-event", self.__manageMouseMovedEvent) self.connect("button-press-event", self.__manageButtonPressedEvent) self.connect("button-release-event", self.__manageButtonReleasedEvent) def setRecordingMode(self, line_thickness): ''' Switch drawing area management to recording mode => setRecordingMode(line_thickness) where line_thickness is an integer ''' self.__readingMode = False self.__figures = [] self.__drawingArea.queue_draw() self.__startNewFigure() self.__setLineThickness(line_thickness) def setReadingMode(self, intervals_between_steps_milliseconds): ''' Switch drawing area management to reading mode => intervals_between_steps argument is an integer saying delays in millisecond between steps => setReadingMode(intervals_between_steps_milliseconds) => intervals_between_steps_milliseconds : integer ''' self.__updater_delay_milliseconds = intervals_between_steps_milliseconds self.__drawn_steps = 0 self.__current_maximum_steps = 0 self.__computeOverallMaximumSteps() self.__drawingArea.queue_draw() self.__readingMode = True self.__regularlyUpdatePaintForReadingMode() def __setLineThickness(self, line_thickness): ''' Sets lines thickness. Only integers greater than 0 will be taken in consideration. => __setLineThickness(line_thickness) where line_thickness is an integer ''' if isinstance(line_thickness, int) and line_thickness > 0: self.__lineThickness = line_thickness def __paintSurface(self, widget, event): ''' Paint the whole drawing area => __paintSurface(widget, event) ''' cairoContext = self.__drawingArea.window.cairo_create() self.__drawBackground(cairoContext) self.__drawFigures(cairoContext) return True def __drawBackground(self, cairoContext): ''' Paints the background => __drawBackground(cairoContext) where cairoContext is a cairo context of the DrawingArea ''' cairoContext.set_source_rgb(*TheDrawingAreaEventBox.__DARK_GREEN) cairoContext.rectangle( 0,0, *(self.window.get_size()) ) cairoContext.fill() def __drawFigures(self, cairoContext): ''' Draws the figures => __drawFigures(cairoContext) where cairoContext is a cairo context of the DrawingArea ''' cairoContext.set_source_rgb(*TheDrawingAreaEventBox.__WHITE) cairoContext.set_line_width(self.__lineThickness) cairoContext.set_line_join(cairo.LINE_JOIN_ROUND) cairoContext.set_line_cap(cairo.LINE_CAP_ROUND) if self.__readingMode: self.__drawn_steps = 0 for current_figure in self.__figures: if len(current_figure) > 0 : startPoint = current_figure[0] cairoContext.move_to(*startPoint) if self.__readingMode: self.__drawn_steps += 1 for pointIndex in range(1, len(current_figure)): if self.__readingMode and self.__drawn_steps >= self.__current_maximum_steps : cairoContext.stroke() return endPoint = current_figure[pointIndex] cairoContext.line_to(*endPoint) startPoint = endPoint if self.__readingMode : self.__drawn_steps += 1 cairoContext.stroke() def __startNewFigure(self): ''' Starts a new figure ''' self.__figures += [[]] def __addPointToCurrentFigure(self, pointTuple): ''' Adds a point to the current figure ==> __addPointToCurrentFigure(pointTuple) where pointTuple is a tuple of the point coordinates ''' self.__figures[len(self.__figures)-1] += [pointTuple] def __computeOverallMaximumSteps(self): ''' Computes the number of steps of all the Figures to draw ''' self.__overall_maximum_steps = 0 for current_figure in self.__figures : self.__overall_maximum_steps += len(current_figure) def __manageMouseMovedEvent(self, widget, event): ''' Manage mouse move event => __manageMouseMovedEvent(widget, event) ''' if self.__mousePressed: self.__addPointToCurrentFigure((event.x, event.y)) self.__drawingArea.queue_draw() # repaint the drawing area return True # we don't need further handling for this event def __manageButtonPressedEvent(self, widget, event): ''' Manage button pressed event => __manageButtonPressedEvent(widget, event) ''' self.__startNewFigure() self.__mousePressed = True return True # we don't need further handling for this event def __manageButtonReleasedEvent(self, widget, event): ''' Manage button released event => __manageButtonReleasedEvent(widget, event) ''' self.__mousePressed = False return True # we don't need further handling for this event def __regularlyUpdatePaintForReadingMode(self): ''' Increases the current maximum drawn steps and repaint all, at regular delays. (For reading mode) => __regularlyUpdatePaintForReadingMode(regular_delay_milliseconds) regular_delay_milliseconds : integer ''' if self.__readingMode: self.__current_maximum_steps += 1 self.__drawingArea.queue_draw() if self.__current_maximum_steps < self.__overall_maximum_steps : gobject.timeout_add( self.__updater_delay_milliseconds, self.__regularlyUpdatePaintForReadingMode ) def saveFiguresInTextFile(self, text_file): ''' Save the points in a file opened with function 'open' in write mode. Be careful !!! Do not pass a file opened in binary mode !!! And also, remember to close the file after this method call !!! => saveFiguresInTextFile(text_file) ''' for current_figure in self.__figures : if current_figure: text_file.write("New\n") for point_coordinates in current_figure: if point_coordinates: text_file.write("%d#%d " % point_coordinates) text_file.write("\n") def readFiguresFromTextFile(self, text_file): ''' Read the points from a file opened with function 'open' in read mode. Be careful !!! Do not pass a filed opened in a binary mode !!! And also, remember to close the file after this method call !!! => readFiguresFromTextFile(text_file) ''' new_figures_array = [] for current_line in text_file.readlines(): if current_line != "New" : new_figure = [] for current_point_string in current_line.split(' '): if current_point_string: current_point_string_parts = current_point_string.split('#') new_figure.append((int(current_point_string_parts[0]), int(current_point_string_parts[1]))) if new_figure: new_figures_array.append(new_figure) self.__figures = new_figures_array self.__drawingArea.queue_draw()