Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAaron Gordon <aaronsgordon@yahoo.com>2012-12-19 07:29:43 (GMT)
committer Aaron Gordon <aaronsgordon@yahoo.com>2012-12-19 07:29:43 (GMT)
commit421c2039856dae7b79882af5e0a50e2ab6a396cf (patch)
treec3a514046b52ea94893bae4934fe78d474926da9
First commit
-rw-r--r--.gitignore1
-rw-r--r--__init__.py18
-rw-r--r--activity/activity.info6
-rw-r--r--activity/activity.info~6
-rw-r--r--activity/calculate.svg18
-rw-r--r--activity/pyturtleicon.svg89
-rw-r--r--activity/pyturtleicon.svg~89
-rw-r--r--console.py148
-rw-r--r--customscrolledpanel.py42
-rw-r--r--homedirectory.py35
-rw-r--r--my_turtle.py52
-rw-r--r--pyshell.py686
-rw-r--r--pythonturtle.py174
-rw-r--r--setup.py3
-rw-r--r--smartsleep.py36
-rw-r--r--turtleprocess.py254
-rw-r--r--turtlewidget.py134
-rw-r--r--vector.py143
18 files changed, 1934 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..0d20b64
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+*.pyc
diff --git a/__init__.py b/__init__.py
new file mode 100644
index 0000000..f11b35a
--- /dev/null
+++ b/__init__.py
@@ -0,0 +1,18 @@
+"""
+An educational environment for learning Python, suitable for beginners
+and children. Inspired by LOGO.
+
+Call `run()` to run it.
+
+PythonTurtle aims to be the most low-threshold way for learning or
+teaching Python. The user is given command of an interactive Python
+shell, similar to IDLE, and is able to use Python functions to move a
+turtle that is displayed on the screen. An illustrated help screen
+demonstrates how to move the turtle and introduces the student to the
+basics of Python programming.
+"""
+
+from pythonturtle import run
+
+if __name__=="__main__":
+ run() \ No newline at end of file
diff --git a/activity/activity.info b/activity/activity.info
new file mode 100644
index 0000000..66246e4
--- /dev/null
+++ b/activity/activity.info
@@ -0,0 +1,6 @@
+[Activity]
+name = PythonTurtle
+bundle_id = org.laptop.PythonTurtle
+icon = pyturtleicon
+exec = sugar-activity pythonturtle.ApplicationWindow
+activity_version = 1
diff --git a/activity/activity.info~ b/activity/activity.info~
new file mode 100644
index 0000000..66246e4
--- /dev/null
+++ b/activity/activity.info~
@@ -0,0 +1,6 @@
+[Activity]
+name = PythonTurtle
+bundle_id = org.laptop.PythonTurtle
+icon = pyturtleicon
+exec = sugar-activity pythonturtle.ApplicationWindow
+activity_version = 1
diff --git a/activity/calculate.svg b/activity/calculate.svg
new file mode 100644
index 0000000..d338822
--- /dev/null
+++ b/activity/calculate.svg
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
+ <!ENTITY fill_color "#FFFFFF">
+ <!ENTITY stroke_color "#000000">
+]>
+<svg xmlns="http://www.w3.org/2000/svg" width="50" height="50">
+ <rect x="11" y="8" width="26" height="6" style="fill:&fill_color;;stroke:&stroke_color;;stroke-width:2"/>
+ <rect x="11" y="18" width="6" height="6" style="fill:&fill_color;;stroke:&stroke_color;;stroke-width:2"/>
+ <rect x="21" y="18" width="6" height="6" style="fill:&fill_color;;stroke:&stroke_color;;stroke-width:2"/>
+ <rect x="31" y="18" width="6" height="6" style="fill:&fill_color;;stroke:&stroke_color;;stroke-width:2"/>
+ <rect x="11" y="28" width="6" height="6" style="fill:&fill_color;;stroke:&stroke_color;;stroke-width:2"/>
+ <rect x="21" y="28" width="6" height="6" style="fill:&fill_color;;stroke:&stroke_color;;stroke-width:2"/>
+ <rect x="31" y="28" width="6" height="6" style="fill:&fill_color;;stroke:&stroke_color;;stroke-width:2"/>
+ <rect x="11" y="38" width="6" height="6" style="fill:&fill_color;;stroke:&stroke_color;;stroke-width:2"/>
+ <rect x="21" y="38" width="6" height="6" style="fill:&fill_color;;stroke:&stroke_color;;stroke-width:2"/>
+ <rect x="31" y="38" width="6" height="6" style="fill:&fill_color;;stroke:&stroke_color;;stroke-width:2"/>
+
+</svg>
diff --git a/activity/pyturtleicon.svg b/activity/pyturtleicon.svg
new file mode 100644
index 0000000..059521e
--- /dev/null
+++ b/activity/pyturtleicon.svg
@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
+ <!ENTITY fill_color "#FFFFFF">
+ <!ENTITY stroke_color "#000000">
+]>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="48px"
+ height="48px"
+ id="svg2993"
+ version="1.1"
+ inkscape:version="0.48.3.1 r9886"
+ sodipodi:docname="New document 3">
+ <defs
+ id="defs2995" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="7"
+ inkscape:cx="24"
+ inkscape:cy="24"
+ inkscape:current-layer="layer1"
+ showgrid="true"
+ inkscape:grid-bbox="true"
+ inkscape:document-units="px"
+ inkscape:window-width="851"
+ inkscape:window-height="671"
+ inkscape:window-x="0"
+ inkscape:window-y="27"
+ inkscape:window-maximized="0" />
+ <metadata
+ id="metadata2998">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="layer1"
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer">
+ <path
+ sodipodi:type="arc"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ id="path3771"
+ sodipodi:cx="18.214285"
+ sodipodi:cy="29"
+ sodipodi:rx="9.6428576"
+ sodipodi:ry="9.5714283"
+ d="M 27.857142,29 A 9.6428576,9.5714283 0 1 1 8.5714273,29 9.6428576,9.5714283 0 1 1 27.857142,29 z" />
+ <rect
+ style="fill:none;stroke:#000000;stroke-width:1.11250508;stroke-opacity:1"
+ id="rect3773"
+ width="22.428572"
+ height="19.449062"
+ x="17.571428"
+ y="10.408082" />
+ <path
+ sodipodi:type="arc"
+ style="fill:none;stroke:#000000;stroke-opacity:1"
+ id="path3775"
+ sodipodi:cx="22.428572"
+ sodipodi:cy="34.07143"
+ sodipodi:rx="9.5714283"
+ sodipodi:ry="9.6428576"
+ d="m 32,34.07143 a 9.5714283,9.6428576 0 1 1 -19.142857,0 9.5714283,9.6428576 0 1 1 19.142857,0 z"
+ transform="translate(2.7142856,2.2857142)" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 4.5714286,4.5714286 C 4,20.571429 4,21 4,21 l 0,0 10.428571,-10.714286 0.142858,0 z"
+ id="path3779"
+ inkscape:connector-curvature="0" />
+ </g>
+</svg>
diff --git a/activity/pyturtleicon.svg~ b/activity/pyturtleicon.svg~
new file mode 100644
index 0000000..72072d4
--- /dev/null
+++ b/activity/pyturtleicon.svg~
@@ -0,0 +1,89 @@
+<?xml version="1.0" ?>
+<!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN'
+ 'http:www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd [
+ <!ENTITY stroke_color "#000000">
+ <!ENTITY fill_color "#FFFFFF">
+]><svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="48px"
+ height="48px"
+ id="svg2993"
+ version="1.1"
+ inkscape:version="0.48.3.1 r9886"
+ sodipodi:docname="New document 3">
+ <defs
+ id="defs2995" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="7"
+ inkscape:cx="24"
+ inkscape:cy="24"
+ inkscape:current-layer="layer1"
+ showgrid="true"
+ inkscape:grid-bbox="true"
+ inkscape:document-units="px"
+ inkscape:window-width="851"
+ inkscape:window-height="671"
+ inkscape:window-x="0"
+ inkscape:window-y="27"
+ inkscape:window-maximized="0" />
+ <metadata
+ id="metadata2998">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="layer1"
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer">
+ <path
+ sodipodi:type="arc"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ id="path3771"
+ sodipodi:cx="18.214285"
+ sodipodi:cy="29"
+ sodipodi:rx="9.6428576"
+ sodipodi:ry="9.5714283"
+ d="M 27.857142,29 A 9.6428576,9.5714283 0 1 1 8.5714273,29 9.6428576,9.5714283 0 1 1 27.857142,29 z" />
+ <rect
+ style="fill:none;stroke:#000000;stroke-width:1.11250508;stroke-opacity:1"
+ id="rect3773"
+ width="22.428572"
+ height="19.449062"
+ x="17.571428"
+ y="10.408082" />
+ <path
+ sodipodi:type="arc"
+ style="fill:none;stroke:#000000;stroke-opacity:1"
+ id="path3775"
+ sodipodi:cx="22.428572"
+ sodipodi:cy="34.07143"
+ sodipodi:rx="9.5714283"
+ sodipodi:ry="9.6428576"
+ d="m 32,34.07143 a 9.5714283,9.6428576 0 1 1 -19.142857,0 9.5714283,9.6428576 0 1 1 19.142857,0 z"
+ transform="translate(2.7142856,2.2857142)" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 4.5714286,4.5714286 C 4,20.571429 4,21 4,21 l 0,0 10.428571,-10.714286 0.142858,0 z"
+ id="path3779"
+ inkscape:connector-curvature="0" />
+ </g>
+</svg>
diff --git a/console.py b/console.py
new file mode 100644
index 0000000..17b1162
--- /dev/null
+++ b/console.py
@@ -0,0 +1,148 @@
+"""
+See documentation for the class Console defined here.
+"""
+import sys
+import code
+import traceback
+import multiprocessing
+from cStringIO import StringIO
+
+
+class Console(code.InteractiveConsole):
+ """
+ Console based on code.InteractiveConsole. You are supposed to run this
+ console in a process you create with the `multiprocessing` package.
+ It requires a parameter `queue_pack`, which you should create with
+ `make_queue_pack()` in this package. You are supposed to feed the same
+ queue pack into the Shell you will create for the two to be connected
+ to each other.
+ """
+ def __init__(self,queue_pack,*args,**kwargs):
+ code.InteractiveConsole.__init__(self,*args,**kwargs)
+
+ self.input_queue, self.output_queue, \
+ self.runcode_finished_queue, self.runsource_return_queue = queue_pack
+
+ self.readfunc=self.input_queue.get
+ self.writefunc=self.output_queue.put
+
+ #self.stdin=PseudoFileIn(self.readfunc)
+ #self.stdout=PseudoFileOut(self.writefunc)
+ #self.stderr=PseudoFileErr(self.writefunc)
+
+
+
+ def raw_input(self,prompt=None):
+ if prompt: self.write(prompt)
+ return self.readfunc()
+
+ def write(self,output):
+ #self.log(output)
+ return self.writefunc(output)
+
+ def log(self,output):
+ print(output); sys.stdout.flush()
+
+ def push(self, command):
+ more = self.runsource(command, self.filename)
+ return more
+
+ def showsyntaxerror(self, filename=None):
+ type, value, sys.last_traceback = sys.exc_info()
+ sys.last_type = type
+ sys.last_value = value
+ if filename and type is SyntaxError:
+ # Work hard to stuff the correct filename in the exception
+ try:
+ msg, (dummy_filename, lineno, offset, line) = value
+ except:
+ # Not the format we expect; leave it alone
+ pass
+ else:
+ # Stuff in the right filename
+ value = SyntaxError(msg, (filename, lineno, offset, line))
+ sys.last_value = value
+ list = traceback.format_exception_only(type, value)
+
+ map(self.write, list)
+
+ def runsource(self, source, filename="<input>", symbol="single"):
+ try:
+ code = self.compile(source, filename, symbol)
+ except (OverflowError, SyntaxError, ValueError):
+ # Case 1
+ self.showsyntaxerror(filename)
+
+ self.runsource_return_queue.put(False)
+ self.runcode_finished_queue.put(None)
+ return False
+
+ if code is None:
+ # Case 2
+
+ self.runsource_return_queue.put(True)
+ self.runcode_finished_queue.put(None)
+ return True
+
+ # Case 3
+ self.runsource_return_queue.put(False)
+
+ #stdin, stdout, stderr = sys.stdin, sys.stdout, sys.stderr
+ #sys.stdin, sys.stdout, sys.stderr = self.stdin, self.stdout, self.stderr
+ result = StringIO()
+ sys.stdout = result
+
+ try:
+ self.runcode(code)
+ finally:
+ self.output_queue.put(result.getvalue())
+ sys.stdout = sys.__stdout__
+ # if sys.stdin==self.stdin: sys.stdin=stdin
+ # if sys.stdout==self.stdout: sys.stdout=stdout
+ # if sys.stderr==self.stderr: sys.stderr=stderr
+
+ self.runcode_finished_queue.put(None)
+ return False
+
+ def interact(self, banner=None):
+ try:
+ sys.ps1
+ except AttributeError:
+ sys.ps1 = ">>> "
+ try:
+ sys.ps2
+ except AttributeError:
+ sys.ps2 = "... "
+ cprt = 'Type "help", "copyright", "credits" or "license" for more information.'
+ """
+ if banner is None:
+ self.write("Python %s on %s\n%s\n(%s)\n" %
+ (sys.version, sys.platform, cprt,
+ self.__class__.__name__))
+ else:
+ self.write("%s\n" % str(banner))
+ """
+ more = 0
+ while 1:
+ try:
+
+ if more:
+ prompt = sys.ps2
+ else:
+ prompt = sys.ps1
+ try:
+ line = self.raw_input()#prompt)
+ # Can be None if sys.stdin was redefined
+ encoding = getattr(sys.stdin, "encoding", None)
+ if encoding and not isinstance(line, unicode):
+ line = line.decode(encoding)
+ except EOFError:
+ self.write("\n")
+ break
+ else:
+ #self.log(line.__repr__())
+ more = self.push(line)
+ except KeyboardInterrupt:
+ self.write("\nKeyboardInterrupt\n")
+ self.resetbuffer()
+ more = 0
diff --git a/customscrolledpanel.py b/customscrolledpanel.py
new file mode 100644
index 0000000..72951f6
--- /dev/null
+++ b/customscrolledpanel.py
@@ -0,0 +1,42 @@
+"""
+See documentation for CustomScrolledPanel which is defined in this module.
+"""
+
+import wx.lib.scrolledpanel
+
+class CustomScrolledPanel(wx.lib.scrolledpanel.ScrolledPanel):
+ """
+ A subclass of wx.lib.scrolledpanel.ScrolledPanel, which implements
+ usage of the Home and the End keys.
+ """
+ def __init__(self, *args, **kwargs):
+ wx.lib.scrolledpanel.ScrolledPanel.__init__(self, *args, **kwargs)
+ self.Bind(wx.EVT_KEY_DOWN, self.on_key_down)
+
+ def on_key_down(self, event):
+ """
+ Event handler for the key-down event.
+ """
+ key = event.GetKeyCode()
+ if key in (wx.WXK_HOME, wx.WXK_NUMPAD_HOME):
+ self.scroll_home()
+ return
+ elif key in (wx.WXK_END, wx.WXK_NUMPAD_END):
+ self.scroll_end()
+ return
+ else:
+ event.Skip()
+
+ def scroll_home(self):
+ """
+ Scrolls the panel to its top.
+ """
+ self.Scroll(-1, 0)
+
+ def scroll_end(self):
+ """
+ Scrolls the panel to its bottom.
+ """
+ bottom = self.GetVirtualSize()[1]
+ self.Scroll(-1, bottom)
+
diff --git a/homedirectory.py b/homedirectory.py
new file mode 100644
index 0000000..13d83ce
--- /dev/null
+++ b/homedirectory.py
@@ -0,0 +1,35 @@
+"""
+Module for finding out the directory of the program we are running,
+be it a Python script or an executable.
+Use homedirectory.do() to set this path as the current os path and to
+add it to sys.path.
+"""
+
+import os
+import sys
+
+
+def _are_we_frozen():
+ """Returns whether we are frozen via py2exe.
+ This will affect how we find out where we are located."""
+
+ return hasattr(sys, "frozen")
+
+
+def our_path():
+ """ This will get us the program's directory,
+ even if we are frozen using py2exe"""
+
+ if _are_we_frozen():
+ return os.path.dirname(unicode(sys.executable, sys.getfilesystemencoding( )))
+
+ return os.path.dirname(unicode(__file__, sys.getfilesystemencoding( )))
+
+def do():
+ """
+ Sets the directory containing our program as the current directory in os,
+ as well as adding it to sys.path.
+ """
+ path=our_path()
+ os.chdir(path)
+ sys.path.append(path) \ No newline at end of file
diff --git a/my_turtle.py b/my_turtle.py
new file mode 100644
index 0000000..db0868a
--- /dev/null
+++ b/my_turtle.py
@@ -0,0 +1,52 @@
+from vector import Vector
+from misc.angles import deg_to_rad, rad_to_deg
+
+
+# Size of the turtle canvas. We assume no user will have a screen
+# so big that the canvas will be bigger than this.
+BITMAP_SIZE = Vector((2000,1200))
+
+# Center of the canvas.
+origin = BITMAP_SIZE / 2.0
+
+
+# 4 lambda functions for transforming between the reference frame
+# that we prefer and the reference frame that wxPython prefers:
+
+to_my_angle = lambda angle: rad_to_deg(-angle)-180
+from_my_angle = lambda angle: deg_to_rad(-angle+180)
+
+from_my_pos = lambda pos: -pos+origin
+to_my_pos = lambda pos: -pos+origin
+
+##############
+
+class Turtle(object):
+ """
+ A Turtle object defines a turtle by its attributes, such as
+ position, orientation, color, etc. See source of __init__ for
+ a complete list.
+ """
+ def __init__(self):
+ self.pos = Vector((0,0))
+ self.orientation = 180
+ self.color = "red"
+ self.rgb_color = (255, 0, 0)
+ self.width = 3
+ self.visible = True
+ self.pen_down = True
+
+ # the `clear` attribute is only made True momentarily when
+ # the `clear()` function is called by the user to clear the screen.
+ self.clear = False
+
+ self.SPEED=400.0 # Pixels per second
+ self.ANGULAR_SPEED=360.0 # Degrees per second
+
+
+ def give_pen(self):
+ """
+ Gives a wxPython pen that corresponds to the color, width,
+ and pen_downity of the Turtle instance.
+ """
+ return wx.Pen(self.color,self.width,wx.SOLID if self.pen_down else wx.TRANSPARENT)
diff --git a/pyshell.py b/pyshell.py
new file mode 100644
index 0000000..d7c3243
--- /dev/null
+++ b/pyshell.py
@@ -0,0 +1,686 @@
+#
+# Py_Shell.py : inserts the python prompt in a gtk interface
+#
+
+import sys, code, os
+import __builtin__
+
+from gi.repository import GObject, Pango, Gdk, GObject
+from gi.repository import Gtk as gtk
+import Queue
+
+PS1=">>> "
+PS2="... "
+TAB_WIDTH=4
+SPACEBAR = 32
+
+BANNER="Python "+sys.version+"\n"
+
+
+
+class Completer:
+ """
+ Taken from rlcompleter, with readline references stripped, and a local dictionary to use.
+ """
+ def __init__(self,locals):
+ self.locals = locals
+
+ def complete(self, text, state):
+ """Return the next possible completion for 'text'.
+ This is called successively with state == 0, 1, 2, ... until it
+ returns None. The completion should begin with 'text'.
+
+ """
+ if state == 0:
+ if "." in text:
+ self.matches = self.attr_matches(text)
+ else:
+ self.matches = self.global_matches(text)
+ try:
+ return self.matches[state]
+ except IndexError:
+ return None
+
+ def global_matches(self, text):
+ """Compute matches when text is a simple name.
+
+ Return a list of all keywords, built-in functions and names
+ currently defines in __main__ that match.
+
+ """
+ import keyword
+ matches = []
+ n = len(text)
+ for list in [keyword.kwlist, __builtin__.__dict__.keys(), self.locals.keys()]:
+ for word in list:
+ if word[:n] == text and word != "__builtins__":
+ matches.append(word)
+ return matches
+
+ def attr_matches(self, text):
+ """Compute matches when text contains a dot.
+
+ Assuming the text is of the form NAME.NAME....[NAME], and is
+ evaluatable in the globals of __main__, it will be evaluated
+ and its attributes (as revealed by dir()) are used as possible
+ completions. (For class instances, class members are are also
+ considered.)
+
+ WARNING: this can still invoke arbitrary C code, if an object
+ with a __getattr__ hook is evaluated.
+
+ """
+ import re
+ m = re.match(r"(\w+(\.\w+)*)\.(\w*)", text)
+ if not m:
+ return
+ expr, attr = m.group(1, 3)
+ object = eval(expr, self.locals, self.locals)
+ words = dir(object)
+ if hasattr(object,'__class__'):
+ words.append('__class__')
+ words = words + get_class_members(object.__class__)
+ matches = []
+ n = len(attr)
+ for word in words:
+ if word[:n] == attr and word != "__builtins__":
+ matches.append("%s.%s" % (expr, word))
+ return matches
+
+def get_class_members(klass):
+ ret = dir(klass)
+ if hasattr(klass,'__bases__'):
+ for base in klass.__bases__:
+ ret = ret + get_class_members(base)
+ return ret
+
+
+
+
+
+
+
+class Dummy_File:
+
+ def __init__(self, buffer, tag):
+ """Implements a file-like object for redirect the stream to the buffer"""
+
+ self.buffer = buffer
+ self.tag = tag
+
+ def write(self, text):
+ """Write text into the buffer and apply self.tag"""
+ iter=self.buffer.get_end_iter()
+ self.buffer.insert_with_tags(iter,text,self.tag)
+
+ def writelines(self, l):
+ map(self.write, l)
+
+ def flush(self):
+ pass
+
+ def isatty(self):
+ return 1
+
+
+
+class PopUp:
+
+ def __init__(self, text_view, list, position):
+ self.text_view=text_view
+
+ #avoid duplicate items in list
+ tmp={}
+ n_chars=0
+ for item in list:
+ dim=len(item)
+ if dim>n_chars:
+ n_chars=dim
+ tmp[item]=None
+ list=tmp.keys()
+ list.sort()
+
+ self.list=list
+ self.position=position
+ self.popup=gtk.Window(gtk.WINDOW_POPUP)
+ frame=gtk.Frame()
+ sw=gtk.ScrolledWindow()
+ sw.set_policy(gtk.GTK_POLICY_AUTOMATIC, gtk.GTK_POLICY_AUTOMATIC)
+ model=gtk.ListStore(GObject.TYPE_STRING)
+ for item in self.list:
+ iter=model.append()
+ model.set(iter, 0, item)
+ self.list_view=gtk.TreeView(model)
+ self.list_view.connect("row-activated", self.hide)
+ self.list_view.set_property("headers-visible", False)
+ selection=self.list_view.get_selection()
+ selection.connect("changed",self.select_row)
+ selection.select_path((0,))
+ renderer=gtk.CellRendererText()
+ column=gtk.TreeViewColumn("",renderer,text=0)
+ self.list_view.append_column(column)
+ sw.add(self.list_view)
+ frame.add(sw)
+ self.popup.add(frame)
+
+ #set the width of the popup according with the length of the strings
+ contest=self.popup.get_pango_context()
+ desc=contest.get_font_description()
+ lang=contest.get_language()
+ metrics= contest.get_metrics(desc, lang)
+ width= Pango.PIXELS(metrics.get_approximate_char_width()* n_chars)
+ if width>80:
+ self.popup.set_size_request(width,90)
+ else:
+ self.popup.set_size_request(80,90)
+ self.show_popup()
+
+
+ def hide(self, *arg):
+ self.popup.hide()
+
+ def show_popup(self):
+ buffer=self.text_view.get_buffer()
+ iter=buffer.get_iter_at_mark(buffer.get_insert())
+
+ rectangle=self.text_view.get_iter_location(iter)
+ absX, absY=self.text_view.buffer_to_window_coords(gtk.TEXT_WINDOW_TEXT,
+ rectangle.x+rectangle.width+20 ,
+ rectangle.y+rectangle.height+50)
+ parent=self.text_view.get_parent()
+ self.popup.move(self.position[0]+absX, self.position[1]+absY)
+ self.popup.show_all()
+
+
+
+ def prev(self):
+ sel=self.list_view.get_selection()
+ model, iter=sel.get_selected()
+ newIter=model.get_path(iter)
+ if newIter!=None and newIter[0]>0:
+ path=(newIter[0]-1,)
+ self.list_view.set_cursor(path)
+
+
+ def next(self):
+ sel=self.list_view.get_selection()
+ model, iter=sel.get_selected()
+ newIter=model.iter_next(iter)
+ if newIter!=None:
+ path=model.get_path(newIter)
+ self.list_view.set_cursor(path)
+
+
+ def sel_confirmed(self):
+ sel=self.list_view.get_selection()
+ self.select_row(sel)
+ self.hide()
+
+
+ def select_row(self, selection):
+ model, iter= selection.get_selected()
+ name=model.get_value(iter,0)
+ buffer=self.text_view.get_buffer()
+ end=buffer.get_iter_at_mark(buffer.get_insert())
+ start=end.copy()
+ start.backward_char()
+ while start.get_char() not in " ,()[]":
+ start.backward_char()
+ start.forward_char()
+ buffer.delete(start,end)
+ iter=buffer.get_iter_at_mark(buffer.get_insert())
+ buffer.insert(iter,name)
+
+
+class Shell_Gui:
+
+ def __init__(self,with_window=1,banner=BANNER, label_text="Interactive Python Shell", namespace=None, queue_pack=None):
+
+ self.queue_pack = queue_pack
+ self.code_lines = []
+ self.banner=banner
+ box=gtk.HBox()
+ box.set_homogeneous(False)
+ box.set_border_width(4)
+ box.set_spacing(4)
+ sw=gtk.ScrolledWindow()
+
+ self.view=gtk.TextView()
+ self.buffer = self.view.get_buffer()
+ # creates three tags
+ tag_err=self.buffer.create_tag("error")
+ tag_err.set_property("foreground","red")
+ tag_err.set_property("font","monospace 10")
+
+ tag_out=self.buffer.create_tag("out_tag")
+ tag_out.set_property("foreground","blue")
+ tag_out.set_property("font","monospace 10")
+
+ tag_in=self.buffer.create_tag("in_tag")
+ tag_in.set_property("foreground","black")
+ tag_in.set_property("font","monospace 10")
+
+ tag_no_edit=self.buffer.create_tag("no_edit")
+ tag_no_edit.set_property("editable",False)
+ #add the banner
+ self.buffer.set_text(self.banner+PS1)
+ start,end=self.buffer.get_bounds()
+ self.buffer.apply_tag_by_name("out_tag", start, end)
+ self.buffer.apply_tag_by_name("no_edit", start, end)
+
+ self.view.connect("key_press_event", self.key_press)
+ self.view.connect("drag_data_received",self.drag_data_received)
+
+ GObject.timeout_add(30, self.on_idle)
+
+ self.view.set_wrap_mode(gtk.WrapMode.WORD_CHAR)
+ sw.add(self.view)
+ box.pack_start(sw, True, True, 0)
+
+ #creates two dummy files
+ self.dummy_out=Dummy_File(self.buffer,tag_out)
+ self.dummy_err=Dummy_File(self.buffer,tag_err)
+
+ #creates the console
+ if namespace is None: namespace = {}
+ self.core=code.InteractiveConsole(namespace)
+
+ #autocompletation capabilities
+ self.completer = Completer(self.core.locals)
+ self.popup=None
+
+ #creates history capabilities
+ self.history=[" "]
+ self.history_pos=0
+
+ #add buttons
+ #b_box=gtk.Toolbar()
+ #b_box.set_orientation(gtk.Orientation.VERTICAL)
+ #b_box.set_style(gtk.ToolbarStyle.ICONS)
+
+ #button1 = gtk.ToolButton(gtk.STOCK_SAVE)
+ #button2 = gtk.ToolButton(gtk.STOCK_CLEAR)
+ #button3 = gtk.ToolButton(gtk.STOCK_PREFERENCES)
+
+ #b_box.insert(gtk.STOCK_CLEAR,"Clear the output", None, self.clear_text, None,-1)
+ #b_box.insert(gtk.STOCK_SAVE,"Save the output", None, self.save_text, None,-1)
+ #b_box.insert(gtk.STOCK_PREFERENCES,"Preferences", None, None, None,-1)
+ #b_box.insert(button1, 0)
+ #b_box.insert(button2, 0)
+ #b_box.insert(button3, 0)
+
+ if with_window:
+ q_button = gtk.ToolButton(gtk.STOCK_QUIT)
+ #b_box.insert(q_button, 0)
+
+
+ #box.pack_start(b_box, False, True, 0)
+ frame=gtk.Frame()
+ frame.set_label(label_text)
+ frame.show_all()
+ frame.add(box)
+
+
+ if with_window:
+ self.gui=gtk.Window()
+ self.gui.add(frame)
+ self.gui.connect("delete-event",self.quit)
+ self.gui.set_default_size(520,200)
+ self.gui.show_all()
+ else:
+ self.gui=frame
+
+
+
+
+
+ def key_press(self, view, event):
+ if self.popup!=None:
+
+ if event.keyval == Gdk.KEY_Up:
+ self.popup.prev()
+ return True
+ elif event.keyval == Gdk.KEY_Down:
+ self.popup.next()
+ return True
+ elif event.keyval == Gdk.KEY_Return:
+ self.popup.sel_confirmed()
+ self.popup=None
+ return True
+ else:
+ self.popup.hide()
+ self.popup=None
+ else:
+ if event.keyval == Gdk.KEY_Up:
+
+ if self.history_pos>0:
+ # remove text into the line...
+ end=self.buffer.get_end_iter()
+ start=self.buffer.get_iter_at_line(end.get_line())
+ start.forward_chars(4)
+ self.buffer.delete(start,end)
+ #inset the new text
+ pos=self.buffer.get_end_iter()
+ self.buffer.insert(pos, self.history[self.history_pos])
+ self.history_pos-=1
+ else:
+ Gdk.beep()
+ self.view.emit_stop_by_name("key-press-event")
+ return True
+
+ elif event.keyval == Gdk.KEY_Down:
+
+ if self.history_pos<len(self.history)-1:
+ # remove text into the line...
+ end=self.buffer.get_end_iter()
+ start=self.buffer.get_iter_at_line(end.get_line())
+ start.forward_chars(4)
+ self.buffer.delete(start,end)
+ #inset the new text
+ pos=self.buffer.get_end_iter()
+ self.history_pos+=1
+ self.buffer.insert(pos, self.history[self.history_pos])
+
+ else:
+ Gdk.beep()
+ self.view.emit_stop_by_name("key-press-event")
+ return True
+
+ elif event.keyval == Gdk.KEY_Tab:
+ iter=self.buffer.get_iter_at_mark(self.buffer.get_insert())
+ self.buffer.insert(iter,TAB_WIDTH*" ")
+ return True
+
+ elif event.keyval == Gdk.KEY_Return:
+ command=self.get_line()
+ self.exec_code(command)
+ start,end=self.buffer.get_bounds()
+ self.buffer.apply_tag_by_name("no_edit",start,end)
+ self.buffer.place_cursor(end)
+ return True
+
+ #elif event.keyval == SPACEBAR: #and event.state & gtk.gdk.CONTROL_MASK:
+ # self.complete_text()
+ # return True
+
+
+
+ def clear_text(self,*widget):
+ dlg=gtk.Dialog("Clear")
+ dlg.add_button("Clear",1)
+ dlg.add_button("Reset",2)
+ dlg.add_button(gtk.STOCK_CLOSE,gtk.RESPONSE_CLOSE)
+ dlg.set_default_size(250,150)
+ hbox=gtk.HBox()
+ #add an image
+ img=gtk.Image()
+ img.set_from_stock(gtk.STOCK_CLEAR, gtk.ICON_SIZE_DIALOG)
+ hbox.pack_start(img, True, True, 0)
+
+ #add text
+ text="You have two options:\n"
+ text+=" -clear only the output window\n"
+ text+=" -reset the shell\n"
+ text+="\n What do you want to do?"
+ label=gtk.Label(text)
+ hbox.pack_start(label, True, True, 0)
+
+ hbox.show_all()
+ dlg.vbox.pack_start(hbox, True, True, 0)
+
+ ans=dlg.run()
+ dlg.hide()
+ if ans==1:
+ self.buffer.set_text(self.banner+PS1)
+ start,end=self.buffer.get_bounds()
+ self.buffer.apply_tag_by_name("out_tag",start,end)
+
+ elif ans==2:
+ self.buffer.set_text(self.banner+PS1)
+ start,end=self.buffer.get_bounds()
+ self.buffer.apply_tag_by_name("out_tag",start,end)
+ self.buffer.apply_tag_by_name("no_edit",start,end)
+
+ #creates the console
+ self.core=code.InteractiveConsole()
+ #reset history
+ self.history=[" "]
+ self.history_pos=0
+ self.view.grab_focus()
+
+
+ def save_text(self, *widget):
+ dlg=gtk.Dialog("Save to file")
+ dlg.add_button("Commands",1)
+ dlg.add_button("All",2)
+ dlg.add_button(gtk.STOCK_CLOSE,gtk.RESPONSE_CLOSE)
+ dlg.set_default_size(250,150)
+ hbox=gtk.HBox()
+ #add an image
+ img=gtk.Image()
+ img.set_from_stock(gtk.STOCK_SAVE, gtk.ICON_SIZE_DIALOG)
+ hbox.pack_start(img)
+
+ #add text
+ text="You have two options:\n"
+ text+=" -save only commands\n"
+ text+=" -save all\n"
+ text+="\n What do you want to save?"
+ label=gtk.Label(text)
+ hbox.pack_start(label)
+
+ hbox.show_all()
+ dlg.vbox.pack_start(hbox)
+
+ ans=dlg.run()
+ dlg.hide()
+ if ans==1 :
+ def ok_save(button, data=None):
+ win =button.get_toplevel()
+ win.hide()
+ name=win.get_filename()
+ if os.path.isfile(name):
+ box=gtk.MessageDialog(dlg,
+ gtk.DIALOG_DESTROY_WITH_PARENT,
+ gtk.MESSAGE_QUESTION,gtk.BUTTONS_YES_NO,
+ name+" already exists; do you want to overwrite it?"
+ )
+ ans=box.run()
+ box.hide()
+ if ans==gtk.RESPONSE_NO:
+ return
+ try:
+ file=open(name,'w')
+ for i in self.history:
+ file.write(i)
+ file.write("\n")
+ file.close()
+
+
+ except Exception, x:
+ box=gtk.MessageDialog(dlg,
+ gtk.DIALOG_DESTROY_WITH_PARENT,
+ gtk.MESSAGE_ERROR,gtk.BUTTONS_CLOSE,
+ "Unable to write \n"+
+ name+"\n on disk \n\n%s"%(x)
+ )
+ box.run()
+ box.hide()
+
+ def cancel_button(button):
+ win.get_toplevel()
+ win.hide()
+
+ win=gtk.FileSelection("Save Commands...")
+ win.ok_button.connect_object("clicked", ok_save,win.ok_button)
+ win.cancel_button.connect_object("clicked", cancel_button,win.cancel_button)
+ win.show()
+ elif ans==2:
+ def ok_save(button, data=None):
+ win =button.get_toplevel()
+ win.hide()
+ name=win.get_filename()
+ if os.path.isfile(name):
+ box=gtk.MessageDialog(dlg,
+ gtk.DIALOG_DESTROY_WITH_PARENT,
+ gtk.MESSAGE_QUESTION,gtk.BUTTONS_YES_NO,
+ name+" already exists; do you want to overwrite it?"
+ )
+ ans=box.run()
+ box.hide()
+ if ans==gtk.RESPONSE_NO:
+ return
+ try:
+ start,end=self.buffer.get_bounds()
+ text=self.buffer.get_text(start,end,0)
+ file=open(name,'w')
+ file.write(text)
+ file.close()
+
+ except Exception, x:
+ box=gtk.MessageDialog(dlg,
+ gtk.DIALOG_DESTROY_WITH_PARENT,
+ gtk.MESSAGE_ERROR,gtk.BUTTONS_CLOSE,
+ "Unable to write \n"+
+ name+"\n on disk \n\n%s"%(x)
+ )
+ box.run()
+ box.hide()
+
+ def cancel_button(button):
+ win.get_toplevel()
+ win.hide()
+
+ win=gtk.FileSelection("Save Log...")
+ win.ok_button.connect_object("clicked", ok_save,win.ok_button)
+ win.cancel_button.connect_object("clicked", cancel_button,win.cancel_button)
+ win.show()
+ dlg.destroy()
+ self.view.grab_focus()
+
+
+
+ def get_line(self):
+ iter=self.buffer.get_iter_at_mark(self.buffer.get_insert())
+ line=iter.get_line()
+ start=self.buffer.get_iter_at_line(line)
+ end=start.copy()
+ end.forward_line()
+ command=self.buffer.get_text(start,end,0)
+ if (command[:4]==PS1 or command[:4]==PS2):
+ command=command[4:]
+ return command
+
+
+ def complete_text(self):
+ end=self.buffer.get_iter_at_mark(self.buffer.get_insert())
+ start=end.copy()
+ start.backward_char()
+ while start.get_char() not in " ,()[]=":
+ start.backward_char()
+ start.forward_char()
+ token=self.buffer.get_text(start,end,0).strip()
+ completions = []
+ try:
+ p=self.completer.complete(token,len(completions))
+ while p != None:
+ completions.append(p)
+ p=self.completer.complete(token, len(completions))
+ except:
+ return
+ if len(completions)==1:
+ self.buffer.delete(start,end)
+ iter=self.buffer.get_iter_at_mark(self.buffer.get_insert())
+ self.buffer.insert(iter,completions[0])
+ elif len(completions)>1:
+ #show a popup
+ if isinstance(self.gui, gtk.Frame):
+ rect=self.gui.get_allocation()
+ app=self.gui.window.get_position()
+ position=(app[0]+rect.x,app[1]+rect.y)
+ else:
+ position=self.gui.get_position()
+
+ self.popup=PopUp(self.view, completions, position)
+
+
+ def replace_line(self, text):
+ iter=self.buffer.get_iter_at_mark(self.buffer.get_insert())
+ line=iter.get_line()
+ start=self.buffer.get_iter_at_line(line)
+ start.forward_chars(4)
+ end=start.copy()
+ end.forward_line()
+ self.buffer.delete(start,end)
+ iter=self.buffer.get_iter_at_mark(self.buffer.get_insert())
+ self.buffer.insert(iter, text)
+
+
+
+ def sdt2files(self):
+ """switch stdin stdout stderr to my dummy files"""
+ self.std_out_saved=sys.stdout
+ self.std_err_saved=sys.stderr
+
+ sys.stdout=self.dummy_out
+ sys.stderr=self.dummy_err
+
+
+
+
+
+
+ def files2sdt(self):
+ """switch my dummy files to stdin stdout stderr """
+ sys.stdout=self.std_out_saved
+ sys.stderr=self.std_err_saved
+
+
+
+
+ def drag_data_received(self, source, drag_context, n1, n2, selection_data, long1, long2):
+ print selection_data.data
+
+ def on_idle(self, e=None):
+ try:
+ result = self.queue_pack[1].get(False)
+ self.dummy_out.write(result)
+ self.dummy_out.write("%s" % (PS1))
+ return True
+ except Queue.Empty:
+ pass
+ return True
+
+ def exec_code(self, text):
+ """Execute text into the console and display the output into TextView"""
+
+ #update history
+ self.history.append(text)
+ self.history_pos=len(self.history)-1
+
+ self.dummy_out.write("\n")
+ self.code_lines.append(text)
+ cur_code = "\n".join(self.code_lines)
+ action = self.core.compile(cur_code)
+ if action:
+ # the line needs to be executed
+ self.code_lines = []
+ self.queue_pack[0].put(cur_code)
+ else:
+ self.dummy_out.write(PS2)
+
+ self.view.scroll_mark_onscreen(self.buffer.get_insert())
+
+
+
+ def quit(self,*args):
+ if __name__=='__main__':
+ gtk.main_quit()
+ else:
+ if self.popup!=None:
+ self.popup.hide()
+ self.gui.hide()
+
+if __name__ == "__main__":
+ Shell_Gui()
+ gtk.main()
diff --git a/pythonturtle.py b/pythonturtle.py
new file mode 100644
index 0000000..454840c
--- /dev/null
+++ b/pythonturtle.py
@@ -0,0 +1,174 @@
+"""
+Main module which defines ApplicationWindow,
+the main window of PythonTurtle.
+"""
+# Now porting from wxPython to Gtk3 to use in Sugar
+#from customscrolledpanel import CustomScrolledPanel
+#import shelltoprocess
+import sys
+import turtlewidget
+import turtleprocess
+import multiprocessing
+import homedirectory; homedirectory.do()
+from misc.fromresourcefolder import from_resource_folder
+
+from gi.repository import Gtk
+import pyshell
+
+from sugar3.activity import activity
+from sugar3.graphics.toolbarbox import ToolbarBox
+from sugar3.activity.widgets import StopButton
+
+class ApplicationWindow(activity.Activity):
+ """
+ The main window of PythonTurtle.
+ """
+ def __init__(self, handle):
+ activity.Activity.__init__(self, handle)
+
+ # create the toolbar
+ toolbar_box = ToolbarBox()
+ stop = StopButton(self)
+ toolbar_box.toolbar.insert(stop, -1)
+ stop.show()
+ self.set_toolbar_box(toolbar_box)
+ toolbar_box.show()
+
+ self.turtle_process = turtleprocess.TurtleProcess()
+ self.turtle_process.start()
+ self.turtle_queue = self.turtle_process.turtle_queue
+
+ #self.init_menu_bar()
+
+ #self.init_about_dialog_info()
+
+ #self.splitter = wx.SplitterWindow(self, style=wx.SP_LIVE_UPDATE)
+ self.box = Gtk.Paned(orientation=Gtk.Orientation.VERTICAL)
+ self.add(self.box)
+
+ self.turtle_widget = turtlewidget.TurtleWidget(self.turtle_queue)
+ self.box.add1(self.turtle_widget)
+
+ self.shell = pyshell.Shell_Gui(with_window=0, queue_pack=self.turtle_process.queue_pack)
+ self.box.add2(self.shell.gui)
+ self.box.set_position(100)
+ #self.connect("destroy", Gtk.main_quit)
+ self.set_canvas(self.box)
+ self.box.show_all()
+
+ #self.bottom_sizer_panel = wx.Panel(self.splitter)
+
+ #self.shell = \
+ # shelltoprocess.Shell(self.bottom_sizer_panel,
+ # queue_pack=self.turtle_process.queue_pack)
+
+
+ def init_help_screen(self):
+ """
+ Initializes the help screen.
+ """
+ self.help_screen = wx.Panel(parent=self, size=(-1,-1))
+
+ self.help_notebook = \
+ wx.aui.AuiNotebook(parent=self.help_screen, style=wx.aui.AUI_NB_TOP)
+
+
+ def give_focus_to_selected_page(event=None):
+ selected_page_number = self.help_notebook.GetSelection()
+ selected_page = self.help_notebook.GetPage(selected_page_number)
+ if self.FindFocus() != selected_page:
+ selected_page.SetFocus()
+
+ self.help_notebook.Bind(wx.EVT_SET_FOCUS, give_focus_to_selected_page)
+ self.help_notebook.Bind(wx.EVT_CHILD_FOCUS, give_focus_to_selected_page)
+
+ self.help_images_list=[["Level 1", from_resource_folder("help1.png")],
+ ["Level 2", from_resource_folder("help2.png")],
+ ["Level 3", from_resource_folder("help3.png")],
+ ["Level 4", from_resource_folder("help4.png")]]
+
+
+ self.help_pages=[HelpPage(parent=self.help_notebook, bitmap=wx.Bitmap(bitmap_file), caption=caption) \
+ for [caption, bitmap_file] in self.help_images_list]
+
+ for page in self.help_pages:
+ self.help_notebook.AddPage(page, caption=page.caption)
+
+ self.help_close_button_panel = wx.Panel(parent=self.help_screen)
+ self.help_screen_sizer = wx.BoxSizer(wx.VERTICAL)
+ self.help_screen_sizer.Add(self.help_notebook, 1, wx.EXPAND)
+ self.help_screen_sizer.Add(self.help_close_button_panel, 0, wx.EXPAND)
+ self.help_screen.SetSizer(self.help_screen_sizer)
+
+ help_close_button_bitmap=wx.Bitmap(from_resource_folder("lets_code.png"))
+ self.help_close_button = \
+ wx.lib.buttons.GenBitmapButton(self.help_close_button_panel, -1,
+ help_close_button_bitmap)
+ self.help_close_button_sizer = wx.BoxSizer(wx.HORIZONTAL)
+ self.help_close_button_sizer.Add(self.help_close_button, 1, wx.EXPAND | wx.ALL, 5)
+ self.help_close_button_panel.SetSizer(self.help_close_button_sizer)
+
+ self.Bind(wx.EVT_BUTTON, self.hide_help, self.help_close_button)
+
+
+ def show_help(self, event=None):
+ self.help_shown=True
+ self.help_menu_item.Check()
+ self.sizer.Show(self.help_screen)
+ self.sizer.Hide(self.splitter)
+ self.help_notebook.SetFocus()
+ self.sizer.Layout()
+
+ def hide_help(self, event=None):
+ self.help_shown=False
+ self.help_menu_item.Check(False)
+ self.sizer.Hide(self.help_screen)
+ self.sizer.Show(self.splitter)
+ self.shell.setFocus()
+ self.sizer.Layout()
+
+ def toggle_help(self, event=None):
+ if self.help_shown:
+ self.hide_help()
+ else:
+ self.show_help()
+
+ def on_exit(self, event=None):
+ return self.Close()
+
+ def init_about_dialog_info(self):
+ info = self.about_dialog_info = \
+ wx.AboutDialogInfo()
+
+ description="""\
+An educational environment for learning Python, suitable for beginners and children.
+Inspired by LOGO.
+
+Runs on Python 2.6, using wxPython, Psyco and py2exe. Thanks go to the developers
+responsible for these projects, as well as to the helpful folks at the user groups
+of these projects, and at StackOverflow.com, who have helped solved many problems
+that came up in the making of this program."""
+
+ info.SetCopyright("MIT License, (C) 2009 Ram Rachum (\"cool-RR\")")
+ info.SetDescription(description)
+ info.SetName("PythonTurtle")
+ info.SetVersion("0.1.2009.8.2.1")
+ info.SetWebSite("http://pythonturtle.com")
+
+
+ def on_about(self, event=None):
+ about_dialog = wx.AboutBox(self.about_dialog_info)
+
+
+def run():
+ multiprocessing.freeze_support()
+ app = wx.PySimpleApp()
+ my_app_win = ApplicationWindow(None,-1,"PythonTurtle",size=(600,600))
+ #import cProfile; cProfile.run("app.MainLoop()")
+ app.MainLoop()
+
+#if __name__=="__main__":
+ #run()
+ #w = ApplicationWindow()
+ #w.resize(600, 600)
+ #Gtk.main()
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..73889e5
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,3 @@
+from sugar3.activity import bundlebuilder
+
+bundlebuilder.start()
diff --git a/smartsleep.py b/smartsleep.py
new file mode 100644
index 0000000..293c1d9
--- /dev/null
+++ b/smartsleep.py
@@ -0,0 +1,36 @@
+"""
+A short module for implementing `Sleeper`. See its documentation.
+"""
+
+import time
+import sys
+
+#def log(text): print(text); sys.stdout.flush()
+
+class Sleeper(object):
+ """
+ A smarter way to use `time.sleep()`, implemented as a context manager.
+ Use it like this:
+
+ with smartsleep.Sleeper(7):
+ do_stuff()
+
+ The Sleeper instance will ensure that at least 7 second have passed before
+ control flows to the next lines of code.
+ """
+ def __init__(self,interval):
+ self.interval=interval
+
+ def __enter__(self,*args,**kwargs):
+ self.starting_time=time.time()
+
+ def __exit__(self,*args,**kwargs):
+ #global log
+ time_now=time.time()
+ interval_gone=time_now-self.starting_time
+ if interval_gone>=self.interval:
+ #log("didn't sleep")
+ return
+ else:
+ #log("slept")
+ time.sleep(self.interval-interval_gone) \ No newline at end of file
diff --git a/turtleprocess.py b/turtleprocess.py
new file mode 100644
index 0000000..e4af0f8
--- /dev/null
+++ b/turtleprocess.py
@@ -0,0 +1,254 @@
+import sys
+import multiprocessing
+import copy
+import math
+import time
+
+import smartsleep
+import misc.angles as angles
+import pyshell
+import console
+from my_turtle import *
+from vector import Vector
+
+def log(x):
+ print(x)
+ sys.stdout.flush()
+
+class TurtleProcess(multiprocessing.Process):
+ """
+ A TurtleProcess is a subclass of multiprocessing.Process.
+ It is the process from which the user of PythonTurtle works;
+ It defines all the turtle commands (i.e. go, turn, width, etc.).
+ Then it runs a shelltoprocess.Console which connects to the shell
+ in the main application window, allowing the user to control
+ this process.
+ """
+ def __init__(self,*args,**kwargs):
+ multiprocessing.Process.__init__(self,*args,**kwargs)
+
+ self.daemon=True
+
+
+ self.turtle_queue=multiprocessing.Queue()
+ self.queue_pack= [multiprocessing.Queue() for i in range(4)]
+
+
+ """
+ Constants:
+ """
+ self.FPS=25
+ self.FRAME_TIME=1/float(self.FPS)
+
+
+
+ def send_report(self):
+ """
+ Sends a "turtle report" to the TurtleWidget.
+ By sending turtle reports every time the turtle does anything,
+ the TurtleWidget can always know where the turtle is going
+ and draw graphics accordingly.
+ """
+ #self.turtle.fingerprint = random.randint(0,10000)
+ self.turtle_queue.put(self.turtle)
+ #log(self.turtle.__dict__)
+
+ def run(self):
+
+ self.turtle=Turtle()
+
+ def go(distance):
+ """
+ Makes the turtle walk the specified distance. Use a negative number
+ to walk backwards.
+ """
+ if distance==0: return
+ sign=1 if distance>0 else -1
+ distance=copy.copy(abs(distance))
+ distance_gone=0
+ distance_per_frame=self.FRAME_TIME*self.turtle.SPEED
+ steps=int(math.ceil(distance/float(distance_per_frame)))
+ angle=from_my_angle(self.turtle.orientation)
+ unit_vector=Vector((math.sin(angle),math.cos(angle)))*sign
+ step=distance_per_frame*unit_vector
+ for i in range(steps-1):
+ with smartsleep.Sleeper(self.FRAME_TIME):
+ self.turtle.pos+=step
+ self.send_report()
+ distance_gone+=distance_per_frame
+
+ last_distance=distance-distance_gone
+ last_sleep=last_distance/float(self.turtle.SPEED)
+ with smartsleep.Sleeper(last_sleep):
+ last_step=unit_vector*last_distance
+ self.turtle.pos+=last_step
+ self.send_report()
+
+ def turn(angle):
+ """
+ Makes the turtle turn. Specify angle in degrees. A positive
+ number turns clockwise, a negative number turns counter-clockwise.
+ """
+ if angle==0: return
+ sign=1 if angle>0 else -1
+ angle=copy.copy(abs(angle))
+ angle_gone=0
+ angle_per_frame=self.FRAME_TIME*self.turtle.ANGULAR_SPEED
+ steps=int(math.ceil(angle/float(angle_per_frame)))
+ step=angle_per_frame*sign
+ for i in range(steps-1):
+ with smartsleep.Sleeper(self.FRAME_TIME):
+ self.turtle.orientation+=step
+ self.send_report()
+ angle_gone+=angle_per_frame
+
+ last_angle=angle-angle_gone
+ last_sleep=last_angle/float(self.turtle.ANGULAR_SPEED)
+ with smartsleep.Sleeper(last_sleep):
+ last_step=last_angle*sign
+ self.turtle.orientation+=last_step
+ self.send_report()
+
+ def color(color):
+ """
+ Sets the color of the turtle's pen. Specify a color as a string.
+
+ Examples:
+ color("white")
+ color("green")
+ color("#00FFCC")
+ """
+ #if not valid_color(color):
+ # raise StandardError(color+" is not a valid color.")
+ self.turtle.color=color
+ self.send_report()
+
+ def width(width):
+ """
+ Sets the width of the turtle's pen. Width must be a positive number.
+ """
+ #assert 0 < width
+ self.turtle.width = width
+ self.send_report()
+
+ def visible(visible=True):
+ """
+ By default, makes the turtle visible. You may specify a boolean
+ value, e.g. visible(False) will make the turtle invisible.
+ """
+ self.turtle.visible = visible
+ self.send_report()
+
+ def invisible():
+ """
+ Makes the turtle invisible.
+ """
+ self.turtle.visible=False
+ self.send_report()
+
+ def pen_down(pen_down=True):
+ """
+ By default, puts the pen in the "down" position, making the turtle
+ leave a trail when walking. You may specify a boolean value, e.g.
+ pen_down(False) will put the pen in the "up" position.
+ """
+ self.turtle.pen_down = pen_down
+ self.send_report()
+
+ def pen_up():
+ """
+ Puts the pen in the "up" position, making the turtle not leave a
+ trail when walking.
+ """
+ self.turtle.pen_down = False
+ self.send_report()
+
+ def is_visible():
+ """
+ Returns whether the turtle is visible.
+ """
+ return self.turtle.visible
+
+ def is_pen_down():
+ """
+ Returns whether the pen is in the "down" position.
+ """
+ return self.turtle.pen_down
+
+ def sin(angle):
+ """
+ Calculates sine, with the angle specified in degrees.
+ """
+ return math.sin(angles.deg_to_rad(angle))
+
+ def cos(angle):
+ """
+ Calculates cosine, with the angle specified in degrees.
+ """
+ return math.cos(angles.deg_to_rad(angle))
+
+ def clear():
+ """
+ Clears the screen, making it all black again.
+ """
+ self.turtle.clear=True
+ self.send_report()
+ time.sleep(0.1)
+ self.turtle.clear=False
+ self.send_report()
+
+ """
+ Had trouble implementing `home`.
+ I couldn't control when the turtle would actually draw a line home.
+
+ def home():
+ #\"""
+ Places the turtle at the center of the screen, facing upwards.
+ #\"""
+ old_pen_down = self.turtle.pen_down
+ pen_up() # Sends a report as well
+ self.send_report()
+ self.turtle.pos = Vector((0, 0))
+ self.turtle.orientation = 180
+ self.send_report()
+ time.sleep(3)
+ pen_down(old_pen_down)
+ """
+
+ def reset():
+ """
+ Resets all the turtle's properties and clears the screen.
+ """
+ self.turtle = Turtle()
+ clear()
+
+ #go(200)
+ #turn(90)
+ #go(200)
+ #turn(90)
+ #go(200)
+ #turn(90)
+ #go(200)
+ #turn(90)
+
+ locals_for_console={"go": go, "turn": turn, "color": color,
+ "width": width, "visible": visible,
+ "invisible": invisible, "pen_down": pen_down,
+ "pen_up": pen_up, "is_visible": is_visible,
+ "is_pen_down": is_pen_down, "sin": sin, "cos": cos,
+ "turtle": self.turtle, "clear": clear,
+ "reset": reset}
+
+
+ """
+ A little thing I tried doing for checking if a color is
+ valid before setting it to the turtle. Didn't work.
+ import wx; app=wx.App();
+ def valid_color(color):
+ return not wx.Pen(color).GetColour()==wx.Pen("malformed").GetColour()
+ """
+
+ self.console = console.Console(queue_pack=self.queue_pack,locals=locals_for_console)
+ #import cProfile; cProfile.runctx("console.interact()", globals(), locals())
+ self.console.interact()
+ #sys.stdout.flush()
diff --git a/turtlewidget.py b/turtlewidget.py
new file mode 100644
index 0000000..4bcf0eb
--- /dev/null
+++ b/turtlewidget.py
@@ -0,0 +1,134 @@
+"""
+TurtleWidget is defined in this module, see its documentation.
+"""
+
+import time
+import Queue
+import math
+
+from gi.repository import Gtk, Gdk, GObject
+import cairo
+
+from vector import Vector
+from my_turtle import *
+import misc.dumpqueue as dumpqueue
+from misc.fromresourcefolder import from_resource_folder
+
+BITMAP_X = 1200
+BITMAP_Y = 900
+
+class TurtleWidget(Gtk.DrawingArea):
+ """
+ A Gtk Drawing Area widget to display the turtle and all the drawings that
+ it made.
+ """
+ def __init__(self,turtle_queue):
+ Gtk.DrawingArea.__init__(self)
+
+ #self.BACKGROUND_COLOR = wx.Colour(212,208,200)
+ self.TURTLE_IMAGE = cairo.ImageSurface.create_from_png(from_resource_folder("turtle.png"))
+ self.turtle_w = self.TURTLE_IMAGE.get_width()
+ self.turtle_h = self.TURTLE_IMAGE.get_height()
+ self.turtle = Turtle()
+
+ self.count = 0
+ #bitmap=self.bitmap=wx.EmptyBitmapRGBA(2000,1200,BACKGROUND_COLOR[0],BACKGROUND_COLOR[1],BACKGROUND_COLOR[2],255) # todo: Change to something smarter?
+ #self.bitmap = wx.EmptyBitmap(*BITMAP_SIZE)
+ self.bitmap = cairo.ImageSurface(cairo.FORMAT_RGB24, BITMAP_X, BITMAP_Y)
+ self.connect("draw", self.on_paint)
+ GObject.timeout_add(30, self.on_idle)
+ #GObject.idle_add(self.on_idle)
+
+ #self.Bind(wx.EVT_SIZE,self.on_size)
+
+ self.turtle_queue = turtle_queue
+
+ self.idle_block = False
+
+ def on_paint(self, widget, cr):
+ """
+ Paint event handler. Reads the turtle reports and draws graphics
+ accordingly.
+ """
+ # Translate to (0, 0) is in the center
+ w = self.get_allocation().width
+ h = self.get_allocation().height
+
+ cxt = cairo.Context(self.bitmap) # The drawing context for the bitmap
+ cxt.translate(w/2, h/2)
+
+ turtle_reports=dumpqueue.dump_queue(self.turtle_queue)
+ for turtle_report in turtle_reports:
+ if turtle_report.pen_down is True:
+ cxt.set_source_rgb(*self.turtle.rgb_color)
+ cxt.set_line_width(self.turtle.width)
+ cxt.move_to(*(-self.turtle.pos))
+ cxt.line_to(*(-turtle_report.pos))
+ #if turtle_report.clear is True:
+ # brush=wx.Brush("black")
+ # dc.SetBackground(brush)
+ # dc.Clear()
+ self.turtle = turtle_report
+ cxt.stroke()
+
+ if len(turtle_reports) > 0:
+ pass
+ #self.queue_draw()
+
+ # Draw the bitmap
+ cr.set_source_surface(self.bitmap, 0, 0)
+ cr.paint()
+
+ if self.turtle.visible:
+ p = -self.turtle.pos
+ x = p[0] + w/2
+ y = p[1] + h/2
+ cr.translate(x, y)
+ cr.rotate(math.radians(self.turtle.orientation - 180))
+ cr.translate(-x, -y)
+ cr.set_source_surface(self.TURTLE_IMAGE, x - self.turtle_w/2, y - self.turtle_h/2)
+ cr.paint()
+
+
+ # Draw the turtle:
+ #if self.turtle.visible:
+ # new_pos = top_left_corner + from_my_pos(self.turtle.pos)
+ # draw_bitmap_to_dc_rotated(dc, self.TURTLE_IMAGE, from_my_angle(self.turtle.orientation), new_pos)
+ #dc.Destroy()
+
+
+
+ def on_idle(self,e=None):
+ """
+ Idle event handler. Checks whether there are any
+ pending turtle reports, and if there are tells the widget
+ to process them.
+ """
+ if self.idle_block==True: return True
+
+ if not self.turtle_queue.empty(): self.queue_draw()
+ return True
+ #wx.CallLater(30,self._clear_idle_block_and_do) # Should make the delay customizable?
+ #self.idle_block=True
+
+
+ def _clear_idle_block_and_do(self):
+ self.idle_block=False
+ event=wx.PyEvent()
+ event.SetEventType(wx.wxEVT_IDLE)
+ wx.PostEvent(self,event)
+
+ def on_size(self,e=None):
+ self.Refresh()
+
+
+
+def draw_bitmap_to_dc_rotated( dc, bitmap, angle , point):
+ """
+ Rotate a bitmap and write it to the supplied device context.
+ """
+ img = bitmap.ConvertToImage()
+ img_centre = wx.Point( img.GetWidth()/2.0, img.GetHeight()/2.0 )
+ img = img.Rotate( angle, img_centre , interpolating=True)
+ new_point=Vector(point)-Vector(img.GetSize())/2
+ dc.DrawBitmapPoint( img.ConvertToBitmap(), new_point,useMask=True )
diff --git a/vector.py b/vector.py
new file mode 100644
index 0000000..d205077
--- /dev/null
+++ b/vector.py
@@ -0,0 +1,143 @@
+"""
+Implements mainly the Vector class. See its documentation.
+"""
+class VectorError(Exception):
+ """
+ An exception to use with Vector
+ """
+ def __init__(self, msg):
+ self.msg = msg
+
+ def __str__(self):
+ return repr(self.value)
+
+
+class Vector(tuple):
+ """
+ A vector.
+ """
+ def __init__(self,seq):
+ tuple.__init__(seq)
+
+ def __add__(self, other):
+ if not(isinstance(other,Vector)):
+ raise VectorError("right hand side is not a Vector")
+ return Vector(map(lambda x,y: x+y, self, other))
+
+ def __neg__(self):
+ return Vector(map(lambda x: -x, self))
+
+ def __pos__(self):
+ return self
+
+ def __sub__(self, other):
+ return Vector(map(lambda x,y: x-y, self, other))
+
+ def __mul__(self, other):
+ if not(isinstance(other,int) or isinstance(other,float)):
+ raise VectorError("right hand side is illegal")
+ return Vector(map(lambda x: x*other, self))
+
+
+ def __rmul__(self, other):
+ return (self*other)
+
+ def __div__(self, other):
+ if not(isinstance(other,int) or isinstance(other,float)):
+ raise VectorError("right hand side is illegal")
+ return Vector(map(lambda x: x/other, self))
+
+ def __rdiv__(self, other):
+ raise VectorError("you sick pervert! you tried to divide something by a vector!")
+
+ def __and__(self,other):
+ """
+ this is a dot product, done like this: a&b
+ must use () around it because of fucked up operator precedence.
+ """
+ if not(isinstance(other,Vector)):
+ raise VectorError("trying to do dot product of Vector with non-Vector")
+ """
+ if self.dim()!=other.dim():
+ raise("trying to do dot product of Vectors of unequal dimension!")
+ """
+ d=self.dim()
+ s=0.
+ for i in range(d):
+ s+=self[i]*other[i]
+ return s
+
+ def __rand__(self,other):
+ return self&other
+
+ def __or__(self,other):
+ """
+ cross product, defined only for 3D Vectors. goes like this: a|b
+ don't try this on non-3d Vectors. must use () around it because of fucked up operator precedence.
+ """
+ a=self
+ b=other
+ return Vector([a[1]*b[2]-a[2]*b[1],a[2]*b[0]-a[0]*b[2],a[0]*b[1]-a[1]*b[0]])
+
+ def __ror__(self,other):
+ return -(self|other)
+
+ def __abs__(self):
+ s=0.
+ for x in self:
+ s+=x**2
+ return s**(1.0/2)
+
+ def __iadd__(self,other):
+ self=self+other
+ return self
+
+ def __isub__(self,other):
+ self=self-other
+ return self
+
+ def __imul__(self,other):
+ self=self*other
+ return self
+
+ def __idiv__(self,other):
+ self=self/other
+ return self
+
+ def __iand__(self,other):
+ raise VectorError("please don't do &= with my Vectors, it confuses me")
+
+ def __ior__(self,other):
+ self=self|other
+ return self
+
+ def __repr__(self):
+ return "Vector("+tuple.__repr__(self)+")"
+
+ def norm(self):
+ """
+ gives the Vector, normalized
+ """
+ return self/abs(self)
+ def dim(self):
+ return len(self)
+
+ def copy(self):
+ return Vector(self)
+
+
+
+
+################################################################################################
+
+def zeros(n):
+ """
+ Returns a zero Vector of length n.
+ """
+ return Vector(map(lambda x: 0., range(n)))
+
+def ones(n):
+ """
+ Returns a Vector of length n with all ones.
+ """
+ return Vector(map(lambda x: 1., range(n)))