1. Brendan Howell
  2. PyCessing

Commits

Jon Nordby  committed 2886ca3

Move pycessing code into a Python module

  • Participants
  • Parent commits d3b2f52
  • Branches master

Comments (0)

Files changed (18)

File drawing_cairo.py

-"""
-    drawing.py
-    Copyright 2012 Brendan Howell (brendan@howell-ersatz.com)
-
-    This file is part of PyCessing.
-
-    PyCessing 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 3 of the License, or
-    (at your option) any later version.
-
-    PyCessing 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 PyCessing.  If not, see <http://www.gnu.org/licenses/>.
-"""
-
-
-import pygame
-import cairo
-import pangocairo
-import pango
-import numpy
-import math
-import array
-import sys
-
-
-class Drawing:
-    def __init__(self):
-        self.sdl_surface = None
-        self.surface = None
-        self.ctx = None
-        self.stroke = False
-        self.stroke_color = (1, 0, 0, 1)
-        self.stroke_width = 0
-        self.fill = True
-        self.fill_color = (0.5, 0, 0.5, 1)
-        if sys.platform == "darwin":
-            self.machack = True  # check if we are on a mac
-        else:
-            self.machack = False
-        
-    #set up cairo and pango contexts
-    def setSurface(self, sdl_surface):
-        self.sdl_surface = sdl_surface
-        width = sdl_surface.get_width()
-        height = sdl_surface.get_height()
-
-        #TODO: try this on the mac with surface.convert instead
-        # of reversing the array - make sure to check platform here
-        #r,g,b,a = subsurf.get_shifts()
-        #print r,g,b,a
-        #rm,gm,bm,am = subsurf.get_masks()
-        #print rm,gm,bm,am
-        #subsurf.set_shifts((a, r, g, b))
-        #subsurf.set_masks((am, rm, gm, bm))
-
-        
-        self.surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
-        self.ctx = cairo.Context(self.surface)
-        self.pangoctx = pangocairo.CairoContext(self.ctx)
-        self.font_map = pangocairo.cairo_font_map_get_default()
-        self.families = self.font_map.list_families()
-        
-    def setBackground(self, red, green=None, blue=None):
-        if not(green):
-            self.ctx.set_source_rgba(red/255.0, red/255.0, red/255.0, 1)
-        else:
-            self.ctx.set_source_rgba(red/255.0, blue/255.0, green/255.0, 1)
-        self.ctx.paint()
-        
-    def setStroke(self, red, green=None, blue=None, alpha=255, width=1):
-        self.setStrokeWidth(width)
-        self.setStrokeColor(red, green, blue, alpha)
-        
-    def setStrokeColor(self, red, green=None, blue=None, alpha=255):
-        #assume greyscale if only 1 parameter is specified
-        if not(green):
-            self.stroke_color = (red/255.0, red/255.0, red/255.0, alpha/255.0)
-        else:
-            self.stroke_color = (red/255.0, green/255.0, blue/255.0, alpha/255.0)
-    
-    def setStrokeWidth(self, width):
-        self.stroke_width = width
-        
-    def setFillColor(self, red, green=None, blue=None, alpha=255):
-        #assume greyscale if only 1 parameter is specified, assume you want fill
-        if not(green):
-            self.fill_color = (red/255.0, red/255.0, red/255.0, alpha/255.0)
-        else:
-            self.fill_color = (red/255.0, green/255.0, blue/255.0, alpha/255.0)
-        self.fill = True
-        
-    def setFillState(self, fill):
-        self.fill = fill
-        
-    def rect(self, x, y, width, height): 
-        self.ctx.rectangle(x, y, width, height)
-        self._fillAndStroke()
-    
-    def circle(self, cx, cy, radius):
-        self.ctx.arc(cx, cy, radius, 0, 2.0 * math.pi)
-        self._fillAndStroke()
-    
-    def ellipse(self, cx, cy, width, height):
-        self.ctx.save()
-        self.ctx.translate(cx, cy)
-        self.ctx.scale(width / 2.0, height / 2.0)
-        self.ctx.arc(0.0, 0.0, 1.0, 0.0, 2.0 * math.pi)
-        self.ctx.restore()
-        self._fillAndStroke()
-    
-    def arc(self, cx, cy, radius, startAngle, endAngle):
-        self.ctx.arc(cx, cy, radius, math.radians(startAngle), math.radians(endAngle))
-        self._fillAndStroke()
-        
-    def line(self, x1, y1, x2, y2):
-        self.ctx.move_to(x1, y1)
-        self.ctx.line_to(x2, y2)
-        self._fillAndStroke()
-    
-    def polygon(self, pointlist):
-        x, y = pointlist[0]
-        self.ctx.move_to(x, y)
-        for point in pointlist[1:]:
-            x, y = point
-            self.ctx.line_to(x, y)
-        self.ctx.close_path()
-        self._fillAndStroke()
-                
-    def curve(self, x1, y1, cx1, cy1, cx2, cy2, x2, y2):
-        self.ctx.move_to(x1, y1)
-        self.ctx.curve_to(cx1, cy1, cx2, cy2, x2, y2)
-        self._fillAndStroke()
-
-    def screenGrab(self, fileName):
-        pygame.image.save(self.sdl_surface, fileName)
-        
-    def drawText(self, x, y, txt):
-        self.ctx.save()
-        self.ctx.translate(x,y)
-        self.ctx.set_source_rgb(0, 0, 0)
-        layout = self.pangoctx.create_layout()
-        #print dir(self.families[0])
-        print self.families[0].get_name()
-        layout.set_font_description(pango.FontDescription(self.families[0].get_name() + " 25"))
-        layout.set_width(300)
-        layout.set_text(txt)
-        self.pangoctx.update_layout(layout)
-        print('ctx.POS: %s %s'%self.ctx.get_current_point())
-        self.pangoctx.show_layout(layout)
-        print('F => %s %s'%layout.get_pixel_size())
-        self.ctx.restore()
-        
-    def _fillAndStroke(self):
-        if(self.fill):
-            self.ctx.set_source_rgba(self.fill_color[0], self.fill_color[1], self.fill_color[2], self.fill_color[3])
-            self.ctx.fill_preserve()
-        if(self.stroke_width > 0):
-            self.ctx.set_line_width(self.stroke_width)
-            self.ctx.set_source_rgba(self.stroke_color[0], self.stroke_color[1], self.stroke_color[2], self.stroke_color[3])
-            self.ctx.stroke()
-        else:
-            self.ctx.new_path()
-
-    #TODO: finish and test PDF output
-    def renderToPDF(self, fileName):
-    	self.surface = cairo.PDFSurface(fileName, self.sdl_surface.get_width(), self.sdl_surface.get_height())
-    	
-
-    def _blitToScreen(self):  
-        dest = pygame.surfarray.pixels2d(self.sdl_surface)
-        if self.machack:
-            dest.data[:] = self.surface.get_data()[::-1]
-        else:
-            dest.data[:] = self.surface.get_data()
-        #tmp = pygame.transform.flip(self.sdl_surface, True, True)
-        #self.sdl_surface.fill((0,0,0))
-        #del dest
-        #self.sdl_surface.blit(tmp, (0,0)) 
-        #dest = pygame.surfarray.pixels2d(self.sdl_surface)
-        #dest.data[:] = self.surface.get_data()
-        #destR = pygame.surfarray.pixels_red(self.sdl_surface)
-        #destR.data[:] = self.surface.get_data()[1::4]
-        #destG = pygame.surfarray.pixels_green(self.sdl_surface)
-        #destG.data[:] = self.surface.get_data()[2::4]
-        #destB = pygame.surfarray.pixels_blue(self.sdl_surface)
-        #destB.data[:] = self.surface.get_data()[3::4]
-        #destA = pygame.surfarray.pixels_alpha(self.sdl_surface)
-        #destA.data[:] = self.surface.get_data()[::4]
-        
-    	

File images.py

-"""
-    images.py
-    Copyright 2012 Brendan Howell (brendan@howell-ersatz.com)
-
-    This file is part of PyCessing.
-
-    PyCessing 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 3 of the License, or
-    (at your option) any later version.
-
-    PyCessing 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 PyCessing.  If not, see <http://www.gnu.org/licenses/>.
-"""
-
-import pygame
-import os
-import io
-import cairo
-import math
-
-SCREEN = None
-PROJECT_DIR = None
-CTX = None
-
-#TODO: write a generic transform stack to manage arbitrary sequences of 
-#      transformations:
-#       ctx.save()
-#       
-
-class Image2:
-    """All-purpose class for cairo-based pixel images in pycessing"""
-    def __init__(self, file_name):
-        self.file_name = file_name
-        self.loaded = False
-        self.rotation = 0
-        self.scalex = 1.0
-        self.scaley = 1.0
-        
-        
-    def _loadSurface_(self):
-        """load image from file or file_obj"""
-
-        try:
-            imagelike = io.BytesIO(self.file_name.read())
-        except AttributeError:
-            imagelike = os.path.join(PROJECT_DIR, self.file_name)
-        surface = pygame.image.load(imagelike).convert_alpha()
-        
-        self.width = surface.get_width()
-        self.height = surface.get_height()    
-        ar = pygame.surfarray.array2d(surface)
-        stride = surface.get_width() * 4
-        self.surface = cairo.ImageSurface.create_for_data(ar,
-                           cairo.FORMAT_ARGB32, surface.get_width(), 
-                           surface.get_height(), stride)
-        self.loaded = True
-
-    def draw(self, x, y, sx=None, sy=None):
-        #draws to the cairo context
-        if(not(self.loaded)):
-            self._loadSurface_()
-        if(sx and sy):
-            if(width and height):
-                area = (sx, sy, width, height)
-            else:
-                area = (sx, sy, self.surface.get_width(), 
-						self.surface.get_height())
-        else:
-            area = None
-            
-        CTX.save()
-
-        CTX.translate(x, y)
-        CTX.rotate(self.rotation)
-        CTX.scale(self.scalex, self.scaley)
-
-        CTX.set_source_surface(self.surface, 0, 0)
-        CTX.paint()
-        CTX.restore()
-        
-    def rotate(self, angle):
-        self.rotation = math.pi * angle / 180.0
-        
-    def scale(self, pixelsx, pixelsy):
-        if (pixelsx < 1) or (pixelsy < 1):
-            print "image must be at least 1px wide and 1px high!"
-            return
-        if(not(self.loaded)):
-            self._loadSurface_()
-        self.scalex = float(pixelsx) / self.width
-        self.scaley = float(pixelsy) / self.height
-        
-    def get_width(self):
-        if(not(self.loaded)):
-            self._loadSurface_()
-        if self.scalex:
-            return self.scalex
-        else:
-            return self.width
-    
-    def get_height(self):
-        if(not(self.loaded)):
-            self._loadSurface_()
-        dx, dy = self.matrix.transform_distance(0, self.height)
-        if self.scaley:
-            return self.scaley
-        else:
-            return self.height
-
-class Image:
-    """All-purpose class for pixel images in pycessing"""
-    def __init__(self, file_name):
-        self.file_name = file_name
-        self.loaded = False
-        self.transformed = False
-
-    def _loadSurface_(self):
-        """load image from file or file_obj"""
-
-        try:
-            imagelike = io.BytesIO(self.file_name.read())
-        except AttributeError:
-            imagelike = os.path.join(PROJECT_DIR, self.file_name)
-        self.surface = pygame.image.load(imagelike).convert_alpha()
-
-        self.original_surface = self.surface.copy()
-        self.loaded = True
-    
-    def draw(self, x, y, sx=None, sy=None, width=None, height=None):
-        if(not(self.loaded)):
-            self._loadSurface_()
-        if(sx and sy):
-            if(width and height):
-                area = (sx, sy, width, height)
-            else:
-                area = (sx, sy, self.surface.get_width(), 
-						self.surface.get_height())
-        else:
-            area = None
-        SCREEN.unlock()
-        SCREEN.blit(self.surface, (x, y), area)
-        self.transformed = False
-
-    def scale(self,width,height):
-        if not(self.loaded):
-            self._loadSurface_()
-        if not(self.transformed):
-            img = self.original_surface
-            self.transformed = True
-        else:
-            img = self.surface
-        self.surface = pygame.transform.smoothscale(img, (width, height))
-
-    def flip(self,flipx=True,flipy=True):
-        if not(self.loaded):
-           self._loadSurface_()
-        self.surface = pygame.transform.flip(self.surface, flipx, flipy)
-
-    def rotate(self, angle):
-        if not(self.loaded):
-            self._loadSurface_()
-        if not(self.transformed):
-            img = self.original_surface
-            self.transformed = True
-        else:
-            img = self.surface
-        self.surface = pygame.transform.rotate(img, angle)
-
-    def getWidth(self):
-        if(not(self.loaded)):
-            self._loadSurface_()
-        return self.surface.get_width()
-
-    def getHeight(self):
-        if(not(self.loaded)):
-            self._loadSurface_()
-        return self.surface.get_height()
-
-    def getPixel(self, x, y):
-        if(not(self.loaded)):
-            self._loadSurface_()
-        return self.surface.get_at((x,y))
-
-    def setPixel(self, x, y, r, g, b, a=None):
-        if(not(self.loaded)):
-            self._loadSurface_()
-        if (a):
-            self.surface.set_at((x, y), (r, g, b, a))
-        else:
-            self.surface.set_at((x, y), (r, g, b))
-        

File mainwindow.py

-"""
-    mainwindow.py
-    Copyright 2011 Brendan Howell (brendan@howell-ersatz.com)
-
-    This file is part of PyCessing.
-
-    PyCessing 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 3 of the License, or
-    (at your option) any later version.
-
-    PyCessing 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 PyCessing.  If not, see <http://www.gnu.org/licenses/>.
-"""
-
-
-#!/usr/bin/python
-
-#mainwindow.py - main window IDE for pycessing
-#TODO: version header from git
-#TODO: wrap strings in TR()
-#TODO: handle file dirty in header
-#TODO: cut and paste from help browser
-
-from PyQt4 import QtGui, QtCore, Qsci, QtWebKit
-import os, subprocess, signal, tempfile, sys, glob
-from pkg_resources import resource_string, resource_stream, resource_listdir, resource_filename
-
-class MainWindow(QtGui.QMainWindow):
-    def __init__(self):
-        QtGui.QMainWindow.__init__(self)
-        self.running = False
-        tmp, self.tempFile = tempfile.mkstemp(".cess")
-        os.close(tmp)
-        tmp, self.logFile = tempfile.mkstemp()
-        os.close(tmp)
-
-        self.resize(1024, 500)
-        self.setWindowTitle('PyCessing')
-        self.fileName = None
-        self.cwd = os.path.dirname(os.path.abspath(sys.argv[0]))
-        #print self.cwd
-        
-        self.splitter = QtGui.QSplitter(QtCore.Qt.Vertical)
-        self.setCentralWidget(self.splitter)
-        
-        self.setupToolbar()
-        self.setupEditor()
-        self.setupMessageBox()
-        self.statusBar()
-        
-        self.setupFileMenu()
-        self.setupEditMenu()
-        self.setupProgramMenu()
-        self.setupHelpMenu()
-        self.sketchPID = None
-
-        
-    def about(self):
-        QtGui.QMessageBox.about(self,"About Pycessing","Copyright 2012 <br />Brendan Howell<br />Licensed Under the GPL3.")
-        
-    #TODO: this should open a new tab (or window)
-    def newFile(self):
-        response = None
-        
-        if self.textEdit.isModified():
-            #options = QtGui.QMessageBox.Ok + QtGui.QMessageBox.NoButton
-            response = QtGui.QMessageBox.critical(self,"New Program","Current file is not saved.  Are you sure you want to discard your changes?", QtGui.QMessageBox.Discard, QtGui.QMessageBox.Cancel, QtGui.QMessageBox.Save)
-        
-        if response == QtGui.QMessageBox.Cancel:
-            return False
-        elif response == QtGui.QMessageBox.Save:
-            self.saveFile()
-        
-        self.textEdit.clear()
-        self.fileName = None
-        self.setWindowTitle('PyCessing')
-        return True
-        
-    def openFile(self):
-        if not(self.newFile()):
-            return
-        self.fileName = QtGui.QFileDialog.getOpenFileName(self, "Open File", "", "Pycessing Files (*.cess)")
-        if(self.fileName):
-            newFile = open(self.fileName)
-            self.textEdit.setText(newFile.read())
-            newFile.close()
-            shortname = os.path.basename(str(self.fileName))
-            self.setWindowTitle('PyCessing - ' + shortname)
-            
-    def openExample(self, filename):
-        if not(self.newFile()):
-            return
-        examplecode = resource_string(__name__,'examples/' + filename + '.cess')
-        self.textEdit.setText(examplecode)
-        self.setWindowTitle('PyCessing - ' + filename)
-            
-    def setupEditor(self):
-        font = QtGui.QFont()
-        font.setFamily("Droid Sans Mono")
-        if font.family() != "Droid Sans Mono":
-            droidfile = resource_filename(__name__,'fonts/DroidSansMono.ttf')
-            err = QtGui.QFontDatabase.addApplicationFont(droidfile)
-            font.setFamily("Droid Sans Mono")
-            if err == -1:
-                print "could not load Droid Fonts"
-        font.setFixedPitch(True)
-        font.setPointSize(13)
-        font.setItalic(False)
-        
-        fontMetrics = QtGui.QFontMetrics(font)
-        
-        self.textEdit = Qsci.QsciScintilla(self)
-        lexer = Qsci.QsciLexerPython()
-        lexer.setFont(font)
-        self.textEdit.setLexer(lexer)
-        
-        #TODO: make style 1 - python comments - italic
-        #self.textEdit.SendScintilla(Qsci.QsciScintilla.SCI_STYLESETFONT, 1, 'Courier')
-        
-        self.textEdit.setAutoIndent(True)
-        self.textEdit.setIndentationWidth(4)
-        self.textEdit.setIndentationsUseTabs(False)
-        self.textEdit.setMarginsFont(font)
-        self.textEdit.setMarginWidth(0, fontMetrics.width("00000") + 5)
-        self.textEdit.setMarginLineNumbers(0,True)
-        
-        self.textEdit.setCaretLineVisible(True)
-        self.textEdit.setCaretLineBackgroundColor(QtGui.QColor("#ffe4e4"))
-        
-        self.textEdit.setEdgeMode(Qsci.QsciScintilla.EdgeLine)
-        self.textEdit.setEdgeColumn(80)
-        self.textEdit.setWrapMode(Qsci.QsciScintilla.WrapNone)
-        self.textEdit.setBraceMatching(Qsci.QsciScintilla.SloppyBraceMatch)
-        
-        self.splitter.addWidget(self.textEdit)
-        #size = self.textEdit.sizeHint()
-        #size.setWidth(8)
-        #if size.height() < 300:
-        #    size.setHeight(300)
-        #print "size: " + str(size.width()) + " x " + str(size.height())
-        self.textEdit.SendScintilla(Qsci.QsciScintilla.SCI_SETHSCROLLBAR, 0)
-        self.textEdit.setMinimumSize(600,450)
-
-        self.textEdit.resize(800,500)
-        
-    def setupMessageBox(self):
-    	self.messageBox = QtGui.QTextEdit(self)
-    	self.messageBox.setReadOnly(True)
-    	self.splitter.addWidget(self.messageBox)
-    	#self.messageBox.setAutoFormatting(QtGui.QTextBrowser.LogText)
-        
-    def setupFileMenu(self):
-        iconfile = resource_filename(__name__,'icons/exit.png')
-        exit = QtGui.QAction(QtGui.QIcon(iconfile), '&Quit', self)
-        exit.setShortcut('Ctrl+Q')
-        exit.setStatusTip('Exit application')
-        self.connect(exit, QtCore.SIGNAL('triggered()'), QtCore.SLOT('close()'))
-               
-        menubar = self.menuBar()
-        fileMenu = menubar.addMenu('&File')
-        
-        #TODO: ctrl-W should close the tab
-        fileMenu.addAction("&New", self.newFile, QtGui.QKeySequence("Ctrl+N"))
-        exampleMenu = fileMenu.addMenu("Examples")
-        fileMenu.addAction("&Open", self.openFile, QtGui.QKeySequence("Ctrl+O"))
-        fileMenu.addAction("&Save", self.saveFile, QtGui.QKeySequence("Ctrl+S"))
-        fileMenu.addAction("Save &As", self.saveFileAs, QtGui.QKeySequence("Shift+Ctrl+S"))
-        fileMenu.addAction("&Print", self.printFile, QtGui.QKeySequence("Ctrl+P"))
-        fileMenu.addAction(exit)
-        
-        exampleDir = resource_filename(__name__,"examples/")
-        examples = glob.glob(exampleDir + '*.cess')
-        #glob examples
-        #TODO: glob sub-directories
-        for example in examples:
-            example = os.path.basename(example)
-            example = example.replace('.cess','')
-            exMenuItem = exampleMenu.addAction(example)
-            receiver = lambda example=example: self.openExample(example)
-            self.connect(exMenuItem, QtCore.SIGNAL('triggered()'), receiver)
-
-    def setupToolbar(self):
-        toolbar = self.addToolBar('Toolbar')
-        iconfile = resource_filename(__name__,'icons/play.png')
-        self.playToggle = QtGui.QAction(QtGui.QIcon(iconfile), 'Run Program', toolbar)
-        self.connect(self.playToggle, QtCore.SIGNAL('triggered()'), self.togglePlaying)
-        toolbar.addAction(self.playToggle)
-       
-	    #add spacer
-        spacer = QtGui.QWidget(self)
-        spacer.setSizePolicy(QtGui.QSizePolicy.MinimumExpanding, QtGui.QSizePolicy.Preferred)
-        toolbar.addWidget(spacer)
-        
-        #add find widget
-        self.findEdit = QtGui.QLineEdit(self)
-        self.findEdit.setFixedSize(QtCore.QSize(200, self.findEdit.height()))
-        toolbar.addWidget(self.findEdit)
-        
-        #add expandable spacer
-        
-        # find button
-        iconfile = resource_filename(__name__,"icons/Edit-find.png")
-        self.findbutton = QtGui.QAction(QtGui.QIcon(iconfile), 'Find in Program', toolbar)
-        self.connect(self.findbutton, QtCore.SIGNAL('triggered()'), self.findNext)
-        toolbar.addAction(self.findbutton)
-
-        #add help browser toggle        
-        iconfile = resource_filename(__name__,"icons/help-browser.png")
-        self.helptoggle = QtGui.QAction(QtGui.QIcon(iconfile), 'Show / Hide Help', toolbar)
-        self.connect(self.helptoggle, QtCore.SIGNAL('triggered()'), self.toggleHelpBrowser)
-        #help hide / show action
-        toolbar.addAction(self.helptoggle)
-        
-    def setupHelpMenu(self):
-        iconfile = resource_filename(__name__,'icons/about.png')
-        aboutItem = QtGui.QAction(QtGui.QIcon('icons/about.png'), 'About', self)
-        self.connect(aboutItem, QtCore.SIGNAL('triggered()'), self.about)
-    
-        menubar = self.menuBar()
-        helpMenu = menubar.addMenu('&Help')
-        helpMenu.addAction(aboutItem)
-        
-        self.helpwindow = QtGui.QDockWidget("Doc Browser")
-        self.helpwindow.setAllowedAreas(QtCore.Qt.RightDockWidgetArea)
-        #self.helpwindow.setMinimumWidth(400)
-        self.helpwindow.setFeatures(QtGui.QDockWidget.DockWidgetClosable | QtGui.QDockWidget.DockWidgetFloatable)
-        helpAction = self.helpwindow.toggleViewAction()
-        helpAction.setText("&Help Browser")
-        helpAction.setShortcut(QtGui.QKeySequence("F1"))
-        helpMenu.addAction(helpAction)
- 
-        self.addDockWidget(QtCore.Qt.RightDockWidgetArea, self.helpwindow)
-        #self.helpwindow.resize(400, self.height())       
-        
-        helpbrowser = QtWebKit.QWebView(self.helpwindow)
-        indexpath = resource_filename(__name__,"help/index.html")
-        url = QtCore.QUrl("file://" + indexpath)
-        helpbrowser.load(url)
-        helpbrowser.show()
-        helpbrowser.updateGeometry()
-        helpbrowser.update()
-        
-        helpbrowser.setMinimumWidth(400)
-        helpbrowser.setMinimumHeight( self.helpwindow.height())
-        self.helpwindow.setWidget(helpbrowser)
-
-        #sizepolicy = self.helpwindow.sizePolicy()
-        #sizepolicy.setVerticalStretch(QtGui.QSizePolicy.Expanding)
-        
-
-        
-##        helpbrowser = QtGui.QTextBrowser(self.helpwindow)
-##        csspath = resource_filename(__name__,"help/media/css/style.css")
-##        csstxt = open(csspath).read()
-##        helpbrowser.document().setDefaultStyleSheet(csstxt)
-##        helpbrowser.setOpenExternalLinks(True)
-##        
-##        indexpath = resource_filename(__name__,"help/index.html")
-##        url = QtCore.QUrl("file://" + indexpath)
-##        helpbrowser.setSource(url)
-##        helpbrowser.resize(400, self.helpwindow.height())
-
-        
-    def setupEditMenu(self):
-        menubar = self.menuBar()
-        editMenu = menubar.addMenu('&Edit')
-        
-        editMenu.addAction('&Undo', self.textEdit.undo, QtGui.QKeySequence("Ctrl+Z"))
-        editMenu.addAction('&Redo', self.textEdit.redo, QtGui.QKeySequence("Shift+Ctrl+Z"))
-        
-        editMenu.addSeparator()
-        
-        editMenu.addAction('Cu&t', self.textEdit.cut, QtGui.QKeySequence("Ctrl+X"))
-        editMenu.addAction('&Copy', self.textEdit.copy, QtGui.QKeySequence("Ctrl+C"))
-        editMenu.addAction('&Paste', self.textEdit.paste, QtGui.QKeySequence("Ctrl+V"))
-        #editMenu.addAction('&Delete', self.textEdit.delete, QtGui.QKeySequence("Del"))
-        
-        editMenu.addSeparator()
-        
-        editMenu.addAction('&Indent', self.doIndent, QtGui.QKeySequence("Ctrl+T"))
-        editMenu.addAction('&Un-indent', self.doUnIndent, QtGui.QKeySequence("Shift+Ctrl+T"))
-        
-    def setupProgramMenu(self):
-        menubar = self.menuBar()
-        progMenu = menubar.addMenu("&Program")
-        
-        progMenu.addAction('&Run / Stop', self.togglePlaying, QtGui.QKeySequence("Ctrl+R"))
-    
-    #TODO: catch file exceptions    
-    def saveFile(self):
-        if not(self.fileName):
-            self.saveFileAs()
-        fp = open(self.fileName, "w")
-        fp.write(self.textEdit.text())
-        fp.close()
-        
-    def saveFileAs(self):
-        self.fileName = QtGui.QFileDialog.getSaveFileName(self, "Save File", "", "Pycessing program (*.cess)")
-        if not(self.fileName.endsWith(".cess")):
-            self.fileName += ".cess"
-        self.saveFile()
-        shortname = os.path.basename(str(self.fileName))
-        self.setWindowTitle('PyCessing - ' + shortname)
-        
-    def printFile(self):
-        pass 
-        
-    def helpContents(self):
-        QtGui.QDesktopServices.openUrl(QtCore.QUrl("help/index.html"))
-        
-    def doIndent(self):
-        (lineFrom, indexFrom, lineTo, indexTo) = self.textEdit.getSelection()
-        if(lineFrom == -1):
-            line, index = self.textEdit.getCursorPosition()
-            self.textEdit.indent(line)
-        else:
-            for curLine in range(lineFrom, lineTo+1):
-                self.textEdit.indent(curLine)
-                
-    def doUnIndent(self):
-        (lineFrom, indexFrom, lineTo, indexTo) = self.textEdit.getSelection()
-        if(lineFrom == -1):
-            line, index = self.textEdit.getCursorPosition()
-            self.textEdit.unindent(line)
-        else:
-            for curLine in range(lineFrom, lineTo+1):
-                self.textEdit.unindent(curLine)
-    
-    def _saveTmp(self):
-        #FIXME: this might be kinda insecure
-        if self.textEdit.isModified():
-            fp = open(self.tempFile, "w")
-            fp.write(self.textEdit.text())
-            fp.close()
-        
-    def togglePlaying(self):
-        if(self.running):         
-            self.sketchPID.kill()
-            
-        else:
-            self.messageBox.clear()
-            self.messageBox.append("starting...")            
-            print "starting program"
-            
-            #fork the child process
-            self._saveTmp()
-            #app = "/opt/local/bin/python"
-            app = sys.executable
-            if self.fileName != None:
-                progdir = os.path.dirname(str(self.fileName))
-            else:
-                progdir = resource_filename(__name__,'examples/')
-            run_py = resource_filename(__name__,"run.py")
-            args = ["-u", run_py, self.tempFile, progdir]
-            self.sketchPID = QtCore.QProcess(self)
-                              
-            self.connect(self.sketchPID, QtCore.SIGNAL('finished(int,QProcess::ExitStatus)'), self._progStopped)
-            self.connect(self.sketchPID, QtCore.SIGNAL('readyReadStandardOutput()'), self._outMessage)
-            self.connect(self.sketchPID, QtCore.SIGNAL('readyReadStandardError()'), self._errorMessage)
-            self.sketchPID.start(app, args)
-            self.sketchPID.waitForStarted()
-            
-            self.running = True
-            iconfile = resource_filename(__name__,'icons/Process-stop.png')
-            self.playToggle.setIcon(QtGui.QIcon(iconfile))
-            self.playToggle.setToolTip("Stop running program")
-            self.statusBar().showMessage("Program Running...")
-            
-    def toggleHelpBrowser(self):
-        self.helpwindow.setVisible(not(self.helpwindow.isVisible()))
-        
-        
-    def findNext(self):
-    	searchString = self.findEdit.text()
-    	self.textEdit.findFirst(searchString, False, False, False, True)
-            
-    def _progStopped(self, exitcode, exitstatus):
-        print "program stopped:"
-        print exitstatus
-        #Normal exit actually means python errored out, crash means it was stopped by user
-        if exitstatus == QtCore.QProcess.CrashExit:
-	        self.statusBar().showMessage("Program Stopped")
-        else:
-            self.statusBar().showMessage("Oops! Program Crashed")
-        self.running = False
-        iconfile = resource_filename(__name__,'icons/play.png')
-        self.playToggle.setIcon(QtGui.QIcon(iconfile))
-        self.playToggle.setToolTip("Run program")
-    	
-    def _errorMessage(self):
-        self.sketchPID.setReadChannel(QtCore.QProcess.StandardError)
-        while(self.sketchPID.canReadLine()):
-            self.messageBox.append(QtCore.QString(self.sketchPID.readLine()))
-    
-    def _outMessage(self):
-        self.sketchPID.setReadChannel(QtCore.QProcess.StandardOutput)
-        while(self.sketchPID.canReadLine()):
-            self.messageBox.append(QtCore.QString(self.sketchPID.readLine()))
-
-          
-

File pycess.py

-"""
-    pycess.py
-    Copyright 2009 Brendan Howell (brendan@howell-ersatz.com)
-
-    This file is part of PyCessing.
-
-    PyCessing 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 3 of the License, or
-    (at your option) any later version.
-
-    PyCessing 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 PyCessing.  If not, see <http://www.gnu.org/licenses/>.
-"""
-
-
-# this is the main wrapper class for pycessing
-# it stores all of the global settings as well as provides 
-# utilities for basic window, sound and display settings.
-
-# TODO: icon on mac & pc
-
-import pygame
-import os
-import platform
-import sys
-
-class Store:
-    def __init__(self):
-        return
-
-class Pycess:
-    def __init__(self, filename=""):
-        self.framerate = 60
-        self.noLoop = False
-        self.screen = None
-        self.fullscreen = False
-        
-        self.display_w = 320
-        self.display_h = 240
-        self.clock = pygame.time.Clock()
-        self._setup_done = False
-        self.keyPressed = False
-        self._numKeysPressed = 0
-        self.key = None
-        self.filename = filename
-        
-    def size(self, x=320, y=240, fullscreen=False, flags=pygame.OPENGL):
-        print "setting display size"
-        self.display_w = x
-        self.display_h = y
-        
-        if fullscreen:
-            flags = pygame.FULLSCREEN
-        else:
-            flags = 0  # disable openGL for now
-        
-        self.screen = pygame.display.set_mode((x,y), flags, 0)
-        pygame.display.set_caption('PyCessing - ' + self.filename, "PyCessing")
-        module_location = os.path.dirname(__file__)
-        
-        plat = platform.system()
-        if plat == "Linux":
-            icon = pygame.image.load(os.path.join(module_location,"icons", "app-icon.ico"))
-            pygame.display.set_icon(icon)
-        #else:
-            #icon = pygame.image.load(os.path.join(module_location,"icons", "app-icon.icns"))
-        #pygame.display.set_icon(icon)
-        self._setup_done = True
-
-        
-    def doEvents(self, eventlist):
-        for event in eventlist:
-            try:
-                if event.type == pygame.QUIT:
-                    sys.exit(0)
-                elif event.type == pygame.KEYDOWN:
-                    self._numKeysPressed += 1
-                    self.keyPressed = True
-                    self.key = event.unicode
-                    self.onKeyDown(event)
-                elif event.type == pygame.KEYUP:
-                    self._numKeysPressed -= 1
-                    if self._numKeysPressed < 1:
-                        self.keyPressed = False
-                    self.onKeyUp(event)
-                elif event.type == pygame.MOUSEBUTTONDOWN:
-                    self.onMousePressed()
-                elif event.type == pygame.MOUSEBUTTONUP:
-                    self.onMouseReleased()
-                elif event.type == pygame.JOYBUTTONDOWN:
-                    self.onJoyButtonPressed()
-                elif event.type == pygame.JOYBUTTONUP:
-                    self.onJoyButtonReleased()
-                
-            except AttributeError:
-                pass
-        
-    def isMousePressed(self):
-        button1, button2, button3 = pygame.mouse.get_pressed()
-        return button1 or button2 or button3
-        
-    def getMouseX(self):
-        return pygame.mouse.get_pos()[0]
-        
-    def getMouseY(self):
-        return pygame.mouse.get_pos()[1]
-        
-    def getWidth(self):
-        return self.display_w
-
-    def getHeight(self):
-        return self.display_h 
-
-    def getFPS(self):
-        return self.clock.get_fps()

File pycessing.py

-#!/usr/bin/python
-"""
-    pycessing.py
-    Copyright 2009 Brendan Howell (brendan@howell-ersatz.com)
-
-    This file is part of PyCessing.
-
-    PyCessing 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 3 of the License, or
-    (at your option) any later version.
-
-    PyCessing 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 PyCessing.  If not, see <http://www.gnu.org/licenses/>.
-"""
-
-import sys
-from PyQt4 import QtGui
-import mainwindow
-
-def main():
-    """Runs the main IDE for processing"""
-    app = QtGui.QApplication(sys.argv)
-    mainwin = mainwindow.MainWindow()
-    mainwin.show()
-    mainwin.raise_()
-    sys.exit(app.exec_())
-
-if __name__ == '__main__':
-    main()

File pycessing/__init__.py

Empty file added.

File pycessing/drawing_cairo.py

View file
+"""
+    drawing.py
+    Copyright 2012 Brendan Howell (brendan@howell-ersatz.com)
+
+    This file is part of PyCessing.
+
+    PyCessing 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 3 of the License, or
+    (at your option) any later version.
+
+    PyCessing 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 PyCessing.  If not, see <http://www.gnu.org/licenses/>.
+"""
+
+
+import pygame
+import cairo
+import pangocairo
+import pango
+import numpy
+import math
+import array
+import sys
+
+
+class Drawing:
+    def __init__(self):
+        self.sdl_surface = None
+        self.surface = None
+        self.ctx = None
+        self.stroke = False
+        self.stroke_color = (1, 0, 0, 1)
+        self.stroke_width = 0
+        self.fill = True
+        self.fill_color = (0.5, 0, 0.5, 1)
+        if sys.platform == "darwin":
+            self.machack = True  # check if we are on a mac
+        else:
+            self.machack = False
+        
+    #set up cairo and pango contexts
+    def setSurface(self, sdl_surface):
+        self.sdl_surface = sdl_surface
+        width = sdl_surface.get_width()
+        height = sdl_surface.get_height()
+
+        #TODO: try this on the mac with surface.convert instead
+        # of reversing the array - make sure to check platform here
+        #r,g,b,a = subsurf.get_shifts()
+        #print r,g,b,a
+        #rm,gm,bm,am = subsurf.get_masks()
+        #print rm,gm,bm,am
+        #subsurf.set_shifts((a, r, g, b))
+        #subsurf.set_masks((am, rm, gm, bm))
+
+        
+        self.surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
+        self.ctx = cairo.Context(self.surface)
+        self.pangoctx = pangocairo.CairoContext(self.ctx)
+        self.font_map = pangocairo.cairo_font_map_get_default()
+        self.families = self.font_map.list_families()
+        
+    def setBackground(self, red, green=None, blue=None):
+        if not(green):
+            self.ctx.set_source_rgba(red/255.0, red/255.0, red/255.0, 1)
+        else:
+            self.ctx.set_source_rgba(red/255.0, blue/255.0, green/255.0, 1)
+        self.ctx.paint()
+        
+    def setStroke(self, red, green=None, blue=None, alpha=255, width=1):
+        self.setStrokeWidth(width)
+        self.setStrokeColor(red, green, blue, alpha)
+        
+    def setStrokeColor(self, red, green=None, blue=None, alpha=255):
+        #assume greyscale if only 1 parameter is specified
+        if not(green):
+            self.stroke_color = (red/255.0, red/255.0, red/255.0, alpha/255.0)
+        else:
+            self.stroke_color = (red/255.0, green/255.0, blue/255.0, alpha/255.0)
+    
+    def setStrokeWidth(self, width):
+        self.stroke_width = width
+        
+    def setFillColor(self, red, green=None, blue=None, alpha=255):
+        #assume greyscale if only 1 parameter is specified, assume you want fill
+        if not(green):
+            self.fill_color = (red/255.0, red/255.0, red/255.0, alpha/255.0)
+        else:
+            self.fill_color = (red/255.0, green/255.0, blue/255.0, alpha/255.0)
+        self.fill = True
+        
+    def setFillState(self, fill):
+        self.fill = fill
+        
+    def rect(self, x, y, width, height): 
+        self.ctx.rectangle(x, y, width, height)
+        self._fillAndStroke()
+    
+    def circle(self, cx, cy, radius):
+        self.ctx.arc(cx, cy, radius, 0, 2.0 * math.pi)
+        self._fillAndStroke()
+    
+    def ellipse(self, cx, cy, width, height):
+        self.ctx.save()
+        self.ctx.translate(cx, cy)
+        self.ctx.scale(width / 2.0, height / 2.0)
+        self.ctx.arc(0.0, 0.0, 1.0, 0.0, 2.0 * math.pi)
+        self.ctx.restore()
+        self._fillAndStroke()
+    
+    def arc(self, cx, cy, radius, startAngle, endAngle):
+        self.ctx.arc(cx, cy, radius, math.radians(startAngle), math.radians(endAngle))
+        self._fillAndStroke()
+        
+    def line(self, x1, y1, x2, y2):
+        self.ctx.move_to(x1, y1)
+        self.ctx.line_to(x2, y2)
+        self._fillAndStroke()
+    
+    def polygon(self, pointlist):
+        x, y = pointlist[0]
+        self.ctx.move_to(x, y)
+        for point in pointlist[1:]:
+            x, y = point
+            self.ctx.line_to(x, y)
+        self.ctx.close_path()
+        self._fillAndStroke()
+                
+    def curve(self, x1, y1, cx1, cy1, cx2, cy2, x2, y2):
+        self.ctx.move_to(x1, y1)
+        self.ctx.curve_to(cx1, cy1, cx2, cy2, x2, y2)
+        self._fillAndStroke()
+
+    def screenGrab(self, fileName):
+        pygame.image.save(self.sdl_surface, fileName)
+        
+    def drawText(self, x, y, txt):
+        self.ctx.save()
+        self.ctx.translate(x,y)
+        self.ctx.set_source_rgb(0, 0, 0)
+        layout = self.pangoctx.create_layout()
+        #print dir(self.families[0])
+        print self.families[0].get_name()
+        layout.set_font_description(pango.FontDescription(self.families[0].get_name() + " 25"))
+        layout.set_width(300)
+        layout.set_text(txt)
+        self.pangoctx.update_layout(layout)
+        print('ctx.POS: %s %s'%self.ctx.get_current_point())
+        self.pangoctx.show_layout(layout)
+        print('F => %s %s'%layout.get_pixel_size())
+        self.ctx.restore()
+        
+    def _fillAndStroke(self):
+        if(self.fill):
+            self.ctx.set_source_rgba(self.fill_color[0], self.fill_color[1], self.fill_color[2], self.fill_color[3])
+            self.ctx.fill_preserve()
+        if(self.stroke_width > 0):
+            self.ctx.set_line_width(self.stroke_width)
+            self.ctx.set_source_rgba(self.stroke_color[0], self.stroke_color[1], self.stroke_color[2], self.stroke_color[3])
+            self.ctx.stroke()
+        else:
+            self.ctx.new_path()
+
+    #TODO: finish and test PDF output
+    def renderToPDF(self, fileName):
+    	self.surface = cairo.PDFSurface(fileName, self.sdl_surface.get_width(), self.sdl_surface.get_height())
+    	
+
+    def _blitToScreen(self):  
+        dest = pygame.surfarray.pixels2d(self.sdl_surface)
+        if self.machack:
+            dest.data[:] = self.surface.get_data()[::-1]
+        else:
+            dest.data[:] = self.surface.get_data()
+        #tmp = pygame.transform.flip(self.sdl_surface, True, True)
+        #self.sdl_surface.fill((0,0,0))
+        #del dest
+        #self.sdl_surface.blit(tmp, (0,0)) 
+        #dest = pygame.surfarray.pixels2d(self.sdl_surface)
+        #dest.data[:] = self.surface.get_data()
+        #destR = pygame.surfarray.pixels_red(self.sdl_surface)
+        #destR.data[:] = self.surface.get_data()[1::4]
+        #destG = pygame.surfarray.pixels_green(self.sdl_surface)
+        #destG.data[:] = self.surface.get_data()[2::4]
+        #destB = pygame.surfarray.pixels_blue(self.sdl_surface)
+        #destB.data[:] = self.surface.get_data()[3::4]
+        #destA = pygame.surfarray.pixels_alpha(self.sdl_surface)
+        #destA.data[:] = self.surface.get_data()[::4]
+        
+    	

File pycessing/images.py

View file
+"""
+    images.py
+    Copyright 2012 Brendan Howell (brendan@howell-ersatz.com)
+
+    This file is part of PyCessing.
+
+    PyCessing 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 3 of the License, or
+    (at your option) any later version.
+
+    PyCessing 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 PyCessing.  If not, see <http://www.gnu.org/licenses/>.
+"""
+
+import pygame
+import os
+import io
+import cairo
+import math
+
+SCREEN = None
+PROJECT_DIR = None
+CTX = None
+
+#TODO: write a generic transform stack to manage arbitrary sequences of 
+#      transformations:
+#       ctx.save()
+#       
+
+class Image2:
+    """All-purpose class for cairo-based pixel images in pycessing"""
+    def __init__(self, file_name):
+        self.file_name = file_name
+        self.loaded = False
+        self.rotation = 0
+        self.scalex = 1.0
+        self.scaley = 1.0
+        
+        
+    def _loadSurface_(self):
+        """load image from file or file_obj"""
+
+        try:
+            imagelike = io.BytesIO(self.file_name.read())
+        except AttributeError:
+            imagelike = os.path.join(PROJECT_DIR, self.file_name)
+        surface = pygame.image.load(imagelike).convert_alpha()
+        
+        self.width = surface.get_width()
+        self.height = surface.get_height()    
+        ar = pygame.surfarray.array2d(surface)
+        stride = surface.get_width() * 4
+        self.surface = cairo.ImageSurface.create_for_data(ar,
+                           cairo.FORMAT_ARGB32, surface.get_width(), 
+                           surface.get_height(), stride)
+        self.loaded = True
+
+    def draw(self, x, y, sx=None, sy=None):
+        #draws to the cairo context
+        if(not(self.loaded)):
+            self._loadSurface_()
+        if(sx and sy):
+            if(width and height):
+                area = (sx, sy, width, height)
+            else:
+                area = (sx, sy, self.surface.get_width(), 
+						self.surface.get_height())
+        else:
+            area = None
+            
+        CTX.save()
+
+        CTX.translate(x, y)
+        CTX.rotate(self.rotation)
+        CTX.scale(self.scalex, self.scaley)
+
+        CTX.set_source_surface(self.surface, 0, 0)
+        CTX.paint()
+        CTX.restore()
+        
+    def rotate(self, angle):
+        self.rotation = math.pi * angle / 180.0
+        
+    def scale(self, pixelsx, pixelsy):
+        if (pixelsx < 1) or (pixelsy < 1):
+            print "image must be at least 1px wide and 1px high!"
+            return
+        if(not(self.loaded)):
+            self._loadSurface_()
+        self.scalex = float(pixelsx) / self.width
+        self.scaley = float(pixelsy) / self.height
+        
+    def get_width(self):
+        if(not(self.loaded)):
+            self._loadSurface_()
+        if self.scalex:
+            return self.scalex
+        else:
+            return self.width
+    
+    def get_height(self):
+        if(not(self.loaded)):
+            self._loadSurface_()
+        dx, dy = self.matrix.transform_distance(0, self.height)
+        if self.scaley:
+            return self.scaley
+        else:
+            return self.height
+
+class Image:
+    """All-purpose class for pixel images in pycessing"""
+    def __init__(self, file_name):
+        self.file_name = file_name
+        self.loaded = False
+        self.transformed = False
+
+    def _loadSurface_(self):
+        """load image from file or file_obj"""
+
+        try:
+            imagelike = io.BytesIO(self.file_name.read())
+        except AttributeError:
+            imagelike = os.path.join(PROJECT_DIR, self.file_name)
+        self.surface = pygame.image.load(imagelike).convert_alpha()
+
+        self.original_surface = self.surface.copy()
+        self.loaded = True
+    
+    def draw(self, x, y, sx=None, sy=None, width=None, height=None):
+        if(not(self.loaded)):
+            self._loadSurface_()
+        if(sx and sy):
+            if(width and height):
+                area = (sx, sy, width, height)
+            else:
+                area = (sx, sy, self.surface.get_width(), 
+						self.surface.get_height())
+        else:
+            area = None
+        SCREEN.unlock()
+        SCREEN.blit(self.surface, (x, y), area)
+        self.transformed = False
+
+    def scale(self,width,height):
+        if not(self.loaded):
+            self._loadSurface_()
+        if not(self.transformed):
+            img = self.original_surface
+            self.transformed = True
+        else:
+            img = self.surface
+        self.surface = pygame.transform.smoothscale(img, (width, height))
+
+    def flip(self,flipx=True,flipy=True):
+        if not(self.loaded):
+           self._loadSurface_()
+        self.surface = pygame.transform.flip(self.surface, flipx, flipy)
+
+    def rotate(self, angle):
+        if not(self.loaded):
+            self._loadSurface_()
+        if not(self.transformed):
+            img = self.original_surface
+            self.transformed = True
+        else:
+            img = self.surface
+        self.surface = pygame.transform.rotate(img, angle)
+
+    def getWidth(self):
+        if(not(self.loaded)):
+            self._loadSurface_()
+        return self.surface.get_width()
+
+    def getHeight(self):
+        if(not(self.loaded)):
+            self._loadSurface_()
+        return self.surface.get_height()
+
+    def getPixel(self, x, y):
+        if(not(self.loaded)):
+            self._loadSurface_()
+        return self.surface.get_at((x,y))
+
+    def setPixel(self, x, y, r, g, b, a=None):
+        if(not(self.loaded)):
+            self._loadSurface_()
+        if (a):
+            self.surface.set_at((x, y), (r, g, b, a))
+        else:
+            self.surface.set_at((x, y), (r, g, b))
+        

File pycessing/mainwindow.py

View file
+"""
+    mainwindow.py
+    Copyright 2011 Brendan Howell (brendan@howell-ersatz.com)
+
+    This file is part of PyCessing.
+
+    PyCessing 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 3 of the License, or
+    (at your option) any later version.
+
+    PyCessing 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 PyCessing.  If not, see <http://www.gnu.org/licenses/>.
+"""
+
+
+#!/usr/bin/python
+
+#mainwindow.py - main window IDE for pycessing
+#TODO: version header from git
+#TODO: wrap strings in TR()
+#TODO: handle file dirty in header
+#TODO: cut and paste from help browser
+
+from PyQt4 import QtGui, QtCore, Qsci, QtWebKit
+import os, subprocess, signal, tempfile, sys, glob
+from pkg_resources import resource_string, resource_stream, resource_listdir, resource_filename
+
+class MainWindow(QtGui.QMainWindow):
+    def __init__(self):
+        QtGui.QMainWindow.__init__(self)
+        self.running = False
+        tmp, self.tempFile = tempfile.mkstemp(".cess")
+        os.close(tmp)
+        tmp, self.logFile = tempfile.mkstemp()
+        os.close(tmp)
+
+        self.resize(1024, 500)
+        self.setWindowTitle('PyCessing')
+        self.fileName = None
+        
+        self.splitter = QtGui.QSplitter(QtCore.Qt.Vertical)
+        self.setCentralWidget(self.splitter)
+        
+        self.setupToolbar()
+        self.setupEditor()
+        self.setupMessageBox()
+        self.statusBar()
+        
+        self.setupFileMenu()
+        self.setupEditMenu()
+        self.setupProgramMenu()
+        self.setupHelpMenu()
+        self.sketchPID = None
+
+        
+    def about(self):
+        QtGui.QMessageBox.about(self,"About Pycessing","Copyright 2012 <br />Brendan Howell<br />Licensed Under the GPL3.")
+        
+    #TODO: this should open a new tab (or window)
+    def newFile(self):
+        response = None
+        
+        if self.textEdit.isModified():
+            #options = QtGui.QMessageBox.Ok + QtGui.QMessageBox.NoButton
+            response = QtGui.QMessageBox.critical(self,"New Program","Current file is not saved.  Are you sure you want to discard your changes?", QtGui.QMessageBox.Discard, QtGui.QMessageBox.Cancel, QtGui.QMessageBox.Save)
+        
+        if response == QtGui.QMessageBox.Cancel:
+            return False
+        elif response == QtGui.QMessageBox.Save:
+            self.saveFile()
+        
+        self.textEdit.clear()
+        self.fileName = None
+        self.setWindowTitle('PyCessing')
+        return True
+        
+    def openFile(self):
+        if not(self.newFile()):
+            return
+        self.fileName = QtGui.QFileDialog.getOpenFileName(self, "Open File", "", "Pycessing Files (*.cess)")
+        if(self.fileName):
+            newFile = open(self.fileName)
+            self.textEdit.setText(newFile.read())
+            newFile.close()
+            shortname = os.path.basename(str(self.fileName))
+            self.setWindowTitle('PyCessing - ' + shortname)
+            
+    def openExample(self, filename):
+        if not(self.newFile()):
+            return
+        examplecode = resource_string(__name__,'examples/' + filename + '.cess')
+        self.textEdit.setText(examplecode)
+        self.setWindowTitle('PyCessing - ' + filename)
+            
+    def setupEditor(self):
+        font = QtGui.QFont()
+        font.setFamily("Droid Sans Mono")
+        if font.family() != "Droid Sans Mono":
+            droidfile = resource_filename(__name__,'fonts/DroidSansMono.ttf')
+            err = QtGui.QFontDatabase.addApplicationFont(droidfile)
+            font.setFamily("Droid Sans Mono")
+            if err == -1:
+                print "could not load Droid Fonts"
+        font.setFixedPitch(True)
+        font.setPointSize(13)
+        font.setItalic(False)
+        
+        fontMetrics = QtGui.QFontMetrics(font)
+        
+        self.textEdit = Qsci.QsciScintilla(self)
+        lexer = Qsci.QsciLexerPython()
+        lexer.setFont(font)
+        self.textEdit.setLexer(lexer)
+        
+        #TODO: make style 1 - python comments - italic
+        #self.textEdit.SendScintilla(Qsci.QsciScintilla.SCI_STYLESETFONT, 1, 'Courier')
+        
+        self.textEdit.setAutoIndent(True)
+        self.textEdit.setIndentationWidth(4)
+        self.textEdit.setIndentationsUseTabs(False)
+        self.textEdit.setMarginsFont(font)
+        self.textEdit.setMarginWidth(0, fontMetrics.width("00000") + 5)
+        self.textEdit.setMarginLineNumbers(0,True)
+        
+        self.textEdit.setCaretLineVisible(True)
+        self.textEdit.setCaretLineBackgroundColor(QtGui.QColor("#ffe4e4"))
+        
+        self.textEdit.setEdgeMode(Qsci.QsciScintilla.EdgeLine)
+        self.textEdit.setEdgeColumn(80)
+        self.textEdit.setWrapMode(Qsci.QsciScintilla.WrapNone)
+        self.textEdit.setBraceMatching(Qsci.QsciScintilla.SloppyBraceMatch)
+        
+        self.splitter.addWidget(self.textEdit)
+        #size = self.textEdit.sizeHint()
+        #size.setWidth(8)
+        #if size.height() < 300:
+        #    size.setHeight(300)
+        #print "size: " + str(size.width()) + " x " + str(size.height())
+        self.textEdit.SendScintilla(Qsci.QsciScintilla.SCI_SETHSCROLLBAR, 0)
+        self.textEdit.setMinimumSize(600,450)
+
+        self.textEdit.resize(800,500)
+        
+    def setupMessageBox(self):
+    	self.messageBox = QtGui.QTextEdit(self)
+    	self.messageBox.setReadOnly(True)
+    	self.splitter.addWidget(self.messageBox)
+    	#self.messageBox.setAutoFormatting(QtGui.QTextBrowser.LogText)
+        
+    def setupFileMenu(self):
+        iconfile = resource_filename(__name__,'icons/exit.png')
+        exit = QtGui.QAction(QtGui.QIcon(iconfile), '&Quit', self)
+        exit.setShortcut('Ctrl+Q')
+        exit.setStatusTip('Exit application')
+        self.connect(exit, QtCore.SIGNAL('triggered()'), QtCore.SLOT('close()'))
+               
+        menubar = self.menuBar()
+        fileMenu = menubar.addMenu('&File')
+        
+        #TODO: ctrl-W should close the tab
+        fileMenu.addAction("&New", self.newFile, QtGui.QKeySequence("Ctrl+N"))
+        exampleMenu = fileMenu.addMenu("Examples")
+        fileMenu.addAction("&Open", self.openFile, QtGui.QKeySequence("Ctrl+O"))
+        fileMenu.addAction("&Save", self.saveFile, QtGui.QKeySequence("Ctrl+S"))
+        fileMenu.addAction("Save &As", self.saveFileAs, QtGui.QKeySequence("Shift+Ctrl+S"))
+        fileMenu.addAction("&Print", self.printFile, QtGui.QKeySequence("Ctrl+P"))
+        fileMenu.addAction(exit)
+        
+        exampleDir = resource_filename(__name__,"examples/")
+        examples = glob.glob(exampleDir + '*.cess')
+        #glob examples
+        #TODO: glob sub-directories
+        for example in examples:
+            example = os.path.basename(example)
+            example = example.replace('.cess','')
+            exMenuItem = exampleMenu.addAction(example)
+            receiver = lambda example=example: self.openExample(example)
+            self.connect(exMenuItem, QtCore.SIGNAL('triggered()'), receiver)
+
+    def setupToolbar(self):
+        toolbar = self.addToolBar('Toolbar')
+        iconfile = resource_filename(__name__,'icons/play.png')
+        self.playToggle = QtGui.QAction(QtGui.QIcon(iconfile), 'Run Program', toolbar)
+        self.connect(self.playToggle, QtCore.SIGNAL('triggered()'), self.togglePlaying)
+        toolbar.addAction(self.playToggle)
+       
+	    #add spacer
+        spacer = QtGui.QWidget(self)
+        spacer.setSizePolicy(QtGui.QSizePolicy.MinimumExpanding, QtGui.QSizePolicy.Preferred)
+        toolbar.addWidget(spacer)
+        
+        #add find widget
+        self.findEdit = QtGui.QLineEdit(self)
+        self.findEdit.setFixedSize(QtCore.QSize(200, self.findEdit.height()))
+        toolbar.addWidget(self.findEdit)
+        
+        #add expandable spacer
+        
+        # find button
+        iconfile = resource_filename(__name__,"icons/Edit-find.png")
+        self.findbutton = QtGui.QAction(QtGui.QIcon(iconfile), 'Find in Program', toolbar)
+        self.connect(self.findbutton, QtCore.SIGNAL('triggered()'), self.findNext)
+        toolbar.addAction(self.findbutton)
+
+        #add help browser toggle        
+        iconfile = resource_filename(__name__,"icons/help-browser.png")
+        self.helptoggle = QtGui.QAction(QtGui.QIcon(iconfile), 'Show / Hide Help', toolbar)
+        self.connect(self.helptoggle, QtCore.SIGNAL('triggered()'), self.toggleHelpBrowser)
+        #help hide / show action
+        toolbar.addAction(self.helptoggle)
+        
+    def setupHelpMenu(self):
+        iconfile = resource_filename(__name__,'icons/about.png')
+        aboutItem = QtGui.QAction(QtGui.QIcon('icons/about.png'), 'About', self)
+        self.connect(aboutItem, QtCore.SIGNAL('triggered()'), self.about)
+    
+        menubar = self.menuBar()
+        helpMenu = menubar.addMenu('&Help')
+        helpMenu.addAction(aboutItem)
+        
+        self.helpwindow = QtGui.QDockWidget("Doc Browser")
+        self.helpwindow.setAllowedAreas(QtCore.Qt.RightDockWidgetArea)
+        #self.helpwindow.setMinimumWidth(400)
+        self.helpwindow.setFeatures(QtGui.QDockWidget.DockWidgetClosable | QtGui.QDockWidget.DockWidgetFloatable)
+        helpAction = self.helpwindow.toggleViewAction()
+        helpAction.setText("&Help Browser")
+        helpAction.setShortcut(QtGui.QKeySequence("F1"))
+        helpMenu.addAction(helpAction)
+ 
+        self.addDockWidget(QtCore.Qt.RightDockWidgetArea, self.helpwindow)
+        #self.helpwindow.resize(400, self.height())       
+        
+        helpbrowser = QtWebKit.QWebView(self.helpwindow)
+        indexpath = resource_filename(__name__,"help/index.html")
+        url = QtCore.QUrl("file://" + indexpath)
+        helpbrowser.load(url)
+        helpbrowser.show()
+        helpbrowser.updateGeometry()
+        helpbrowser.update()
+        
+        helpbrowser.setMinimumWidth(400)
+        helpbrowser.setMinimumHeight( self.helpwindow.height())
+        self.helpwindow.setWidget(helpbrowser)
+
+        #sizepolicy = self.helpwindow.sizePolicy()
+        #sizepolicy.setVerticalStretch(QtGui.QSizePolicy.Expanding)
+        
+
+        
+##        helpbrowser = QtGui.QTextBrowser(self.helpwindow)
+##        csspath = resource_filename(__name__,"help/media/css/style.css")
+##        csstxt = open(csspath).read()
+##        helpbrowser.document().setDefaultStyleSheet(csstxt)
+##        helpbrowser.setOpenExternalLinks(True)
+##        
+##        indexpath = resource_filename(__name__,"help/index.html")
+##        url = QtCore.QUrl("file://" + indexpath)
+##        helpbrowser.setSource(url)
+##        helpbrowser.resize(400, self.helpwindow.height())
+
+        
+    def setupEditMenu(self):
+        menubar = self.menuBar()
+        editMenu = menubar.addMenu('&Edit')
+        
+        editMenu.addAction('&Undo', self.textEdit.undo, QtGui.QKeySequence("Ctrl+Z"))
+        editMenu.addAction('&Redo', self.textEdit.redo, QtGui.QKeySequence("Shift+Ctrl+Z"))
+        
+        editMenu.addSeparator()
+        
+        editMenu.addAction('Cu&t', self.textEdit.cut, QtGui.QKeySequence("Ctrl+X"))
+        editMenu.addAction('&Copy', self.textEdit.copy, QtGui.QKeySequence("Ctrl+C"))
+        editMenu.addAction('&Paste', self.textEdit.paste, QtGui.QKeySequence("Ctrl+V"))
+        #editMenu.addAction('&Delete', self.textEdit.delete, QtGui.QKeySequence("Del"))
+        
+        editMenu.addSeparator()
+        
+        editMenu.addAction('&Indent', self.doIndent, QtGui.QKeySequence("Ctrl+T"))
+        editMenu.addAction('&Un-indent', self.doUnIndent, QtGui.QKeySequence("Shift+Ctrl+T"))
+        
+    def setupProgramMenu(self):
+        menubar = self.menuBar()
+        progMenu = menubar.addMenu("&Program")
+        
+        progMenu.addAction('&Run / Stop', self.togglePlaying, QtGui.QKeySequence("Ctrl+R"))
+    
+    #TODO: catch file exceptions    
+    def saveFile(self):
+        if not(self.fileName):
+            self.saveFileAs()
+        fp = open(self.fileName, "w")
+        fp.write(self.textEdit.text())
+        fp.close()
+        
+    def saveFileAs(self):
+        self.fileName = QtGui.QFileDialog.getSaveFileName(self, "Save File", "", "Pycessing program (*.cess)")
+        if not(self.fileName.endsWith(".cess")):
+            self.fileName += ".cess"
+        self.saveFile()
+        shortname = os.path.basename(str(self.fileName))
+        self.setWindowTitle('PyCessing - ' + shortname)
+        
+    def printFile(self):
+        pass 
+        
+    def helpContents(self):
+        QtGui.QDesktopServices.openUrl(QtCore.QUrl("help/index.html"))
+        
+    def doIndent(self):
+        (lineFrom, indexFrom, lineTo, indexTo) = self.textEdit.getSelection()
+        if(lineFrom == -1):
+            line, index = self.textEdit.getCursorPosition()
+            self.textEdit.indent(line)
+        else:
+            for curLine in range(lineFrom, lineTo+1):
+                self.textEdit.indent(curLine)
+                
+    def doUnIndent(self):
+        (lineFrom, indexFrom, lineTo, indexTo) = self.textEdit.getSelection()
+        if(lineFrom == -1):
+            line, index = self.textEdit.getCursorPosition()
+            self.textEdit.unindent(line)
+        else:
+            for curLine in range(lineFrom, lineTo+1):
+                self.textEdit.unindent(curLine)
+    
+    def _saveTmp(self):
+        #FIXME: this might be kinda insecure
+        if self.textEdit.isModified():
+            fp = open(self.tempFile, "w")
+            fp.write(self.textEdit.text())
+            fp.close()
+        
+    def togglePlaying(self):
+        if(self.running):         
+            self.sketchPID.kill()
+            
+        else:
+            self.messageBox.clear()
+            self.messageBox.append("starting...")            
+            print "starting program"
+            
+            #fork the child process
+            self._saveTmp()
+            #app = "/opt/local/bin/python"
+            app = sys.executable
+            if self.fileName != None:
+                progdir = os.path.dirname(str(self.fileName))
+            else:
+                progdir = resource_filename(__name__,'examples/')
+            run_py = resource_filename(__name__,"run.py")
+            args = ["-u", run_py, self.tempFile, progdir]
+            self.sketchPID = QtCore.QProcess(self)
+                              
+            self.connect(self.sketchPID, QtCore.SIGNAL('finished(int,QProcess::ExitStatus)'), self._progStopped)
+            self.connect(self.sketchPID, QtCore.SIGNAL('readyReadStandardOutput()'), self._outMessage)
+            self.connect(self.sketchPID, QtCore.SIGNAL('readyReadStandardError()'), self._errorMessage)
+            self.sketchPID.start(app, args)
+            self.sketchPID.waitForStarted()
+            
+            self.running = True
+            iconfile = resource_filename(__name__,'icons/Process-stop.png')
+            self.playToggle.setIcon(QtGui.QIcon(iconfile))
+            self.playToggle.setToolTip("Stop running program")
+            self.statusBar().showMessage("Program Running...")
+            
+    def toggleHelpBrowser(self):
+        self.helpwindow.setVisible(not(self.helpwindow.isVisible()))
+        
+        
+    def findNext(self):
+    	searchString = self.findEdit.text()
+    	self.textEdit.findFirst(searchString, False, False, False, True)
+            
+    def _progStopped(self, exitcode, exitstatus):
+        print "program stopped:"
+        print exitstatus
+        #Normal exit actually means python errored out, crash means it was stopped by user
+        if exitstatus == QtCore.QProcess.CrashExit:
+	        self.statusBar().showMessage("Program Stopped")
+        else:
+            self.statusBar().showMessage("Oops! Program Crashed")
+        self.running = False
+        iconfile = resource_filename(__name__,'icons/play.png')
+        self.playToggle.setIcon(QtGui.QIcon(iconfile))
+        self.playToggle.setToolTip("Run program")
+    	
+    def _errorMessage(self):
+        self.sketchPID.setReadChannel(QtCore.QProcess.StandardError)
+        while(self.sketchPID.canReadLine()):
+            self.messageBox.append(QtCore.QString(self.sketchPID.readLine()))
+    
+    def _outMessage(self):
+        self.sketchPID.setReadChannel(QtCore.QProcess.StandardOutput)
+        while(self.sketchPID.canReadLine()):
+            self.messageBox.append(QtCore.QString(self.sketchPID.readLine()))
+
+          
+

File pycessing/pycess.py

View file
+"""
+    pycess.py
+    Copyright 2009 Brendan Howell (brendan@howell-ersatz.com)
+
+    This file is part of PyCessing.
+
+    PyCessing 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 3 of the License, or
+    (at your option) any later version.
+
+    PyCessing 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 PyCessing.  If not, see <http://www.gnu.org/licenses/>.
+"""
+
+
+# this is the main wrapper class for pycessing
+# it stores all of the global settings as well as provides 
+# utilities for basic window, sound and display settings.
+
+# TODO: icon on mac & pc
+
+import pygame
+import os
+import platform
+import sys
+
+class Store:
+    def __init__(self):
+        return
+
+class Pycess:
+    def __init__(self, filename=""):
+        self.framerate = 60
+        self.noLoop = False
+        self.screen = None
+        self.fullscreen = False
+        
+        self.display_w = 320
+        self.display_h = 240
+        self.clock = pygame.time.Clock()
+        self._setup_done = False
+        self.keyPressed = False
+        self._numKeysPressed = 0
+        self.key = None
+        self.filename = filename
+        
+    def size(self, x=320, y=240, fullscreen=False, flags=pygame.OPENGL):
+        print "setting display size"
+        self.display_w = x
+        self.display_h = y
+        
+        if fullscreen:
+            flags = pygame.FULLSCREEN
+        else:
+            flags = 0  # disable openGL for now
+        
+        self.screen = pygame.display.set_mode((x,y), flags, 0)
+        pygame.display.set_caption('PyCessing - ' + self.filename, "PyCessing")
+        module_location = os.path.dirname(__file__)
+        
+        plat = platform.system()
+        if plat == "Linux":
+            icon = pygame.image.load(os.path.join(module_location,"icons", "app-icon.ico"))
+            pygame.display.set_icon(icon)
+        #else:
+            #icon = pygame.image.load(os.path.join(module_location,"icons", "app-icon.icns"))
+        #pygame.display.set_icon(icon)
+        self._setup_done = True
+
+        
+    def doEvents(self, eventlist):
+        for event in eventlist:
+            try:
+                if event.type == pygame.QUIT:
+                    sys.exit(0)
+                elif event.type == pygame.KEYDOWN:
+                    self._numKeysPressed += 1
+                    self.keyPressed = True
+                    self.key = event.unicode
+                    self.onKeyDown(event)
+                elif event.type == pygame.KEYUP:
+                    self._numKeysPressed -= 1
+                    if self._numKeysPressed < 1:
+                        self.keyPressed = False
+                    self.onKeyUp(event)
+                elif event.type == pygame.MOUSEBUTTONDOWN:
+                    self.onMousePressed()
+                elif event.type == pygame.MOUSEBUTTONUP:
+                    self.onMouseReleased()
+                elif event.type == pygame.JOYBUTTONDOWN:
+                    self.onJoyButtonPressed()
+                elif event.type == pygame.JOYBUTTONUP:
+                    self.onJoyButtonReleased()
+                
+            except AttributeError:
+                pass
+        
+    def isMousePressed(self):
+        button1, button2, button3 = pygame.mouse.get_pressed()
+        return button1 or button2 or button3
+        
+    def getMouseX(self):
+        return pygame.mouse.get_pos()[0]
+        
+    def getMouseY(self):
+        return pygame.mouse.get_pos()[1]
+        
+    def getWidth(self):
+        return self.display_w
+
+    def getHeight(self):
+        return self.display_h 
+
+    def getFPS(self):
+        return self.clock.get_fps()

File pycessing/run.py

View file
+"""
+    run.py
+    Copyright 2012 Brendan Howell (brendan@howell-ersatz.com)
+
+    This file is part of PyCessing.
+
+    PyCessing 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 3 of the License, or
+    (at your option) any later version.
+
+    PyCessing 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 PyCessing.  If not, see <http://www.gnu.org/licenses/>.
+"""
+
+import pygame, sys, os
+
+from pycess import *
+
+from drawing_cairo import *
+import images
+import typo
+from images import Image2 as Image
+from sounds import Sound
+from typo import ScreenText
+
+infile = open(sys.argv[1])
+filename = os.path.basename(sys.argv[1])
+
+pygame.init()
+pygame.fastevent.init()
+
+pycessing = Pycess(filename)
+drawing = Drawing()
+
+project_dir = sys.argv[2].strip()
+if len(project_dir) > 0:
+    os.chdir(project_dir)
+else:
+	project_dir = os.path.dirname(sys.argv[1])
+
+sys.path.append(project_dir)
+
+def setup():
+    pass
+    
+def draw():
+    pass
+
+
+#TODO: move these functions into drawing modules
+
+def circle(x, y, radius):
+    drawing.circle(x, y, radius)
+
+def rect(x, y, width, height):
+    drawing.rect(x, y, width, height)
+
+def ellipse(cx, cy, width, height):
+    drawing.ellipse(cx, cy, width, height)
+
+def arc(cx, cy, radius, startAngle, endAngle):
+    drawing.arc(cx, cy, radius, startAngle, endAngle)
+
+def line(x1, y1, x2, y2):
+    drawing.line(x1, y1, x2, y2)
+
+def polygon(pointlist):
+    drawing.polygon(pointlist)
+
+def curve(x1, y1, cx1, cy1, cx2, cy2, x2, y2):
+    drawing.curve(x1, y1, cx1, cy1, cx2, cy2, x2, y2)
+
+def setBackground(red, green=None, blue=None):
+    drawing.setBackground(red, green, blue)
+
+def setStroke(red, green=None, blue=None, alpha=255, width=1):
+    drawing.setStroke(red, green, blue, alpha, width)
+
+def setStrokeColor(red, green=None, blue=None, alpha=255):
+    drawing.setStrokeColor(red, green, blue, alpha)
+
+def setStrokeWidth(width):
+    drawing.setStrokeWidth(width)
+
+def setFillColor(red, green=None, blue=None, alpha=255):
+    drawing.setFillColor(red, green, blue, alpha)
+
+def setFillState(fillState):
+    drawing.setFillState(fillState)
+    
+def setFillOn():
+    drawing.setFillOn()
+    
+def setFillOff():
+    drawing.setFillOff()
+
+def screenGrab(fileName):
+    drawing.screenGrab(fileName)
+	
+def getPixel(x, y):
+    return pycessing.screen.get_at((x, y))
+
+def setPixel(x, y, r, g, b, a=None):
+    if (a):
+        pycessing.screen.set_at((x, y), (r, g, b, a))
+    else:
+        pycessing.screen.set_at((x, y), (r, g, b))
+
+
+images.PROJECT_DIR = project_dir
+typo.PROJECT_DIR = project_dir
+
+exec(infile)
+
+setup()
+
+if not(pycessing._setup_done):
+    pycessing.size()
+
+drawing.setSurface(pycessing.screen)
+drawing.setBackground(127, 127, 127)
+images.SCREEN = pycessing.screen
+images.CTX = drawing.ctx
+typo.SCREEN = pycessing.screen
+
+while 1:
+    pycessing.doEvents(pygame.fastevent.get())
+    draw()
+    pycessing.clock.tick(pycessing.framerate)
+    drawing._blitToScreen()
+    pygame.display.flip()
+    if(pycessing.noLoop):
+        while 1:
+            events = pygame.event.get()
+            for event in events:
+                if event.type == pygame.QUIT:
+                    sys.exit(0)
+            pycessing.clock.tick(30)

File pycessing/sounds.py

View file
+"""
+    sounds.py
+    Copyright 2009 Brendan Howell (brendan@howell-ersatz.com)
+
+    This file is part of PyCessing.
+
+    PyCessing 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 3 of the License, or
+    (at your option) any later version.
+
+    PyCessing 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 PyCessing.  If not, see <http://www.gnu.org/licenses/>.
+"""
+
+import pygame
+
+class Sound:
+    def __init__(self, fileName):
+	self.fileName = fileName
+	self.loaded = False
+
+    def _loadSound(self):
+        self.sound = pygame.mixer.Sound(self.fileName)
+        self.loaded = True
+    
+    def play(self, loops=0, maxtime=0, fade_ms=0):
+	if(not(self.loaded)):
+            self._loadSound()
+        return self.sound.play(loops, maxtime, fade_ms)
+
+    def setVolume(self, volume):
+        self.sound.set_volume(volume)
+
+    def stop(self):
+        self.sound.stop()
+
+    def fadeout(self, time):
+        self.sound.fadeout(time)
+
+    def getBuffer(self):
+        return self.sound.get_buffer()
+
+    def getLength(self):
+        return self.sound.get_length()
+  
+    def getVolume(self):
+        return self.sound.get_volume()
+
+    def getNum_channels(self):
+        return self.sound.get_num_channels()

File pycessing/typo.py

View file
+"""
+    typo.py
+    Copyright 2012 Brendan Howell (brendan@howell-ersatz.com)
+
+    This file is part of PyCessing.
+
+    PyCessing 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 3 of the License, or
+    (at your option) any later version.
+
+    PyCessing 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 PyCessing.  If not, see <http://www.gnu.org/licenses/>.
+"""
+
+import pygame
+import os
+from itertools import chain
+
+SCREEN = None
+PROJECT_DIR = None
+ 
+def truncline(text, font, maxwidth):
+    """wraps a line to fit the font and spits out the rest"""
+    real=len(text)       
+    stext=text           
+    l=font.size(text)[0]
+    cut=0
+    a=0                  
+    done=1
+    old = None
+    while l > maxwidth:
+        a=a+1
+        n=text.rsplit(None, a)[0]
+        if stext == n:
+            cut += 1
+            stext= n[:-cut]
+        else:
+            stext = n
+        l=font.size(stext)[0]
+        real=len(stext)               
+        done=0                        
+    return real, done, stext             
+        
+def wrapline(text, font, maxwidth): 
+    done=0                      
+    wrapped=[]                  
+                               
+    while not done:             
+        nl, done, stext = truncline(text, font, maxwidth) 
+        wrapped.append(stext.strip())                  
+        text=text[nl:]                                 
+    return wrapped 
+ 
+def wrap_multi_line(text, font, maxwidth):
+    """ returns text taking new lines into account."""
+    lines = chain(*(wrapline(line, font, maxwidth) for line in text.splitlines()))
+    return list(lines)
+
+class ScreenText:
+    def __init__(self, text=None, fontName=None, size=30, color=(255,255,255)):
+        self.text=text
+        self.fontName = fontName
+        self.surface = None