• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2
3"""
4  ----------------------------------------------
5      turtleDemo - Help
6  ----------------------------------------------
7
8  This document has two sections:
9
10  (1) How to use the demo viewer
11  (2) How to add your own demos to the demo repository
12
13
14  (1) How to use the demo viewer.
15
16  Select a demoscript from the example menu.
17  The (syntax colored) source code appears in the left
18  source code window. IT CANNOT BE EDITED, but ONLY VIEWED!
19
20  The demo viewer windows can be resized. The divider between text
21  and canvas can be moved by grabbing it with the mouse. The text font
22  size can be changed from the menu and with Control/Command '-'/'+'.
23  It can also be changed on most systems with Control-mousewheel
24  when the mouse is over the text.
25
26  Press START button to start the demo.
27  Stop execution by pressing the STOP button.
28  Clear screen by pressing the CLEAR button.
29  Restart by pressing the START button again.
30
31  SPECIAL demos, such as clock.py are those which run EVENTDRIVEN.
32
33      Press START button to start the demo.
34
35      - Until the EVENTLOOP is entered everything works
36      as in an ordinary demo script.
37
38      - When the EVENTLOOP is entered, you control the
39      application by using the mouse and/or keys (or it's
40      controlled by some timer events)
41      To stop it you can and must press the STOP button.
42
43      While the EVENTLOOP is running, the examples menu is disabled.
44
45      - Only after having pressed the STOP button, you may
46      restart it or choose another example script.
47
48   * * * * * * * *
49   In some rare situations there may occur interferences/conflicts
50   between events concerning the demo script and those concerning the
51   demo-viewer. (They run in the same process.) Strange behaviour may be
52   the consequence and in the worst case you must close and restart the
53   viewer.
54   * * * * * * * *
55
56
57   (2) How to add your own demos to the demo repository
58
59   - Place the file in the same directory as turtledemo/__main__.py
60     IMPORTANT! When imported, the demo should not modify the system
61     by calling functions in other modules, such as sys, tkinter, or
62     turtle. Global variables should be initialized in main().
63
64   - The code must contain a main() function which will
65     be executed by the viewer (see provided example scripts).
66     It may return a string which will be displayed in the Label below
67     the source code window (when execution has finished.)
68
69   - In order to run mydemo.py by itself, such as during development,
70     add the following at the end of the file:
71
72    if __name__ == '__main__':
73        main()
74        mainloop()  # keep window open
75
76    python -m turtledemo.mydemo  # will then run it
77
78   - If the demo is EVENT DRIVEN, main must return the string
79     "EVENTLOOP". This informs the demo viewer that the script is
80     still running and must be stopped by the user!
81
82     If an "EVENTLOOP" demo runs by itself, as with clock, which uses
83     ontimer, or minimal_hanoi, which loops by recursion, then the
84     code should catch the turtle.Terminator exception that will be
85     raised when the user presses the STOP button.  (Paint is not such
86     a demo; it only acts in response to mouse clicks and movements.)
87"""
88import sys
89import os
90
91from tkinter import *
92from idlelib.colorizer import ColorDelegator, color_config
93from idlelib.percolator import Percolator
94from idlelib.textview import view_text
95from turtledemo import __doc__ as about_turtledemo
96
97import turtle
98
99demo_dir = os.path.dirname(os.path.abspath(__file__))
100darwin = sys.platform == 'darwin'
101
102STARTUP = 1
103READY = 2
104RUNNING = 3
105DONE = 4
106EVENTDRIVEN = 5
107
108menufont = ("Arial", 12, NORMAL)
109btnfont = ("Arial", 12, 'bold')
110txtfont = ['Lucida Console', 10, 'normal']
111
112MINIMUM_FONT_SIZE = 6
113MAXIMUM_FONT_SIZE = 100
114font_sizes = [8, 9, 10, 11, 12, 14, 18, 20, 22, 24, 30]
115
116def getExampleEntries():
117    return [entry[:-3] for entry in os.listdir(demo_dir) if
118            entry.endswith(".py") and entry[0] != '_']
119
120help_entries = (  # (help_label,  help_doc)
121    ('Turtledemo help', __doc__),
122    ('About turtledemo', about_turtledemo),
123    ('About turtle module', turtle.__doc__),
124    )
125
126
127class DemoWindow(object):
128
129    def __init__(self, filename=None):
130        self.root = root = turtle._root = Tk()
131        root.title('Python turtle-graphics examples')
132        root.wm_protocol("WM_DELETE_WINDOW", self._destroy)
133
134        if darwin:
135            import subprocess
136            # Make sure we are the currently activated OS X application
137            # so that our menu bar appears.
138            subprocess.run(
139                    [
140                        'osascript',
141                        '-e', 'tell application "System Events"',
142                        '-e', 'set frontmost of the first process whose '
143                              'unix id is {} to true'.format(os.getpid()),
144                        '-e', 'end tell',
145                    ],
146                    stderr=subprocess.DEVNULL,
147                    stdout=subprocess.DEVNULL,)
148
149        root.grid_rowconfigure(0, weight=1)
150        root.grid_columnconfigure(0, weight=1)
151        root.grid_columnconfigure(1, minsize=90, weight=1)
152        root.grid_columnconfigure(2, minsize=90, weight=1)
153        root.grid_columnconfigure(3, minsize=90, weight=1)
154
155        self.mBar = Menu(root, relief=RAISED, borderwidth=2)
156        self.mBar.add_cascade(menu=self.makeLoadDemoMenu(self.mBar),
157                              label='Examples', underline=0)
158        self.mBar.add_cascade(menu=self.makeFontMenu(self.mBar),
159                              label='Fontsize', underline=0)
160        self.mBar.add_cascade(menu=self.makeHelpMenu(self.mBar),
161                              label='Help', underline=0)
162        root['menu'] = self.mBar
163
164        pane = PanedWindow(orient=HORIZONTAL, sashwidth=5,
165                           sashrelief=SOLID, bg='#ddd')
166        pane.add(self.makeTextFrame(pane))
167        pane.add(self.makeGraphFrame(pane))
168        pane.grid(row=0, columnspan=4, sticky='news')
169
170        self.output_lbl = Label(root, height= 1, text=" --- ", bg="#ddf",
171                                font=("Arial", 16, 'normal'), borderwidth=2,
172                                relief=RIDGE)
173        if darwin:  # Leave Mac button colors alone - #44254.
174            self.start_btn = Button(root, text=" START ", font=btnfont,
175                                    fg='#00cc22', command=self.startDemo)
176            self.stop_btn = Button(root, text=" STOP ", font=btnfont,
177                                   fg='#00cc22', command=self.stopIt)
178            self.clear_btn = Button(root, text=" CLEAR ", font=btnfont,
179                                    fg='#00cc22', command = self.clearCanvas)
180        else:
181            self.start_btn = Button(root, text=" START ", font=btnfont,
182                                    fg="white", disabledforeground = "#fed",
183                                    command=self.startDemo)
184            self.stop_btn = Button(root, text=" STOP ", font=btnfont,
185                                   fg="white", disabledforeground = "#fed",
186                                   command=self.stopIt)
187            self.clear_btn = Button(root, text=" CLEAR ", font=btnfont,
188                                    fg="white", disabledforeground="#fed",
189                                    command = self.clearCanvas)
190        self.output_lbl.grid(row=1, column=0, sticky='news', padx=(0,5))
191        self.start_btn.grid(row=1, column=1, sticky='ew')
192        self.stop_btn.grid(row=1, column=2, sticky='ew')
193        self.clear_btn.grid(row=1, column=3, sticky='ew')
194
195        Percolator(self.text).insertfilter(ColorDelegator())
196        self.dirty = False
197        self.exitflag = False
198        if filename:
199            self.loadfile(filename)
200        self.configGUI(DISABLED, DISABLED, DISABLED,
201                       "Choose example from menu", "black")
202        self.state = STARTUP
203
204
205    def onResize(self, event):
206        cwidth = self._canvas.winfo_width()
207        cheight = self._canvas.winfo_height()
208        self._canvas.xview_moveto(0.5*(self.canvwidth-cwidth)/self.canvwidth)
209        self._canvas.yview_moveto(0.5*(self.canvheight-cheight)/self.canvheight)
210
211    def makeTextFrame(self, root):
212        self.text_frame = text_frame = Frame(root)
213        self.text = text = Text(text_frame, name='text', padx=5,
214                                wrap='none', width=45)
215        color_config(text)
216
217        self.vbar = vbar = Scrollbar(text_frame, name='vbar')
218        vbar['command'] = text.yview
219        vbar.pack(side=LEFT, fill=Y)
220        self.hbar = hbar = Scrollbar(text_frame, name='hbar', orient=HORIZONTAL)
221        hbar['command'] = text.xview
222        hbar.pack(side=BOTTOM, fill=X)
223        text['yscrollcommand'] = vbar.set
224        text['xscrollcommand'] = hbar.set
225
226        text['font'] = tuple(txtfont)
227        shortcut = 'Command' if darwin else 'Control'
228        text.bind_all('<%s-minus>' % shortcut, self.decrease_size)
229        text.bind_all('<%s-underscore>' % shortcut, self.decrease_size)
230        text.bind_all('<%s-equal>' % shortcut, self.increase_size)
231        text.bind_all('<%s-plus>' % shortcut, self.increase_size)
232        text.bind('<Control-MouseWheel>', self.update_mousewheel)
233        text.bind('<Control-Button-4>', self.increase_size)
234        text.bind('<Control-Button-5>', self.decrease_size)
235
236        text.pack(side=LEFT, fill=BOTH, expand=1)
237        return text_frame
238
239    def makeGraphFrame(self, root):
240        turtle._Screen._root = root
241        self.canvwidth = 1000
242        self.canvheight = 800
243        turtle._Screen._canvas = self._canvas = canvas = turtle.ScrolledCanvas(
244                root, 800, 600, self.canvwidth, self.canvheight)
245        canvas.adjustScrolls()
246        canvas._rootwindow.bind('<Configure>', self.onResize)
247        canvas._canvas['borderwidth'] = 0
248
249        self.screen = _s_ = turtle.Screen()
250        turtle.TurtleScreen.__init__(_s_, _s_._canvas)
251        self.scanvas = _s_._canvas
252        turtle.RawTurtle.screens = [_s_]
253        return canvas
254
255    def set_txtsize(self, size):
256        txtfont[1] = size
257        self.text['font'] = tuple(txtfont)
258        self.output_lbl['text'] = 'Font size %d' % size
259
260    def decrease_size(self, dummy=None):
261        self.set_txtsize(max(txtfont[1] - 1, MINIMUM_FONT_SIZE))
262        return 'break'
263
264    def increase_size(self, dummy=None):
265        self.set_txtsize(min(txtfont[1] + 1, MAXIMUM_FONT_SIZE))
266        return 'break'
267
268    def update_mousewheel(self, event):
269        # For wheel up, event.delta = 120 on Windows, -1 on darwin.
270        # X-11 sends Control-Button-4 event instead.
271        if (event.delta < 0) == (not darwin):
272            return self.decrease_size()
273        else:
274            return self.increase_size()
275
276    def configGUI(self, start, stop, clear, txt="", color="blue"):
277        if darwin:  # Leave Mac button colors alone - #44254.
278            self.start_btn.config(state=start)
279            self.stop_btn.config(state=stop)
280            self.clear_btn.config(state=clear)
281        else:
282            self.start_btn.config(state=start,
283                                  bg="#d00" if start == NORMAL else "#fca")
284            self.stop_btn.config(state=stop,
285                                 bg="#d00" if stop == NORMAL else "#fca")
286            self.clear_btn.config(state=clear,
287                                  bg="#d00" if clear == NORMAL else "#fca")
288        self.output_lbl.config(text=txt, fg=color)
289
290    def makeLoadDemoMenu(self, master):
291        menu = Menu(master)
292
293        for entry in getExampleEntries():
294            def load(entry=entry):
295                self.loadfile(entry)
296            menu.add_command(label=entry, underline=0,
297                             font=menufont, command=load)
298        return menu
299
300    def makeFontMenu(self, master):
301        menu = Menu(master)
302        menu.add_command(label="Decrease (C-'-')", command=self.decrease_size,
303                         font=menufont)
304        menu.add_command(label="Increase (C-'+')", command=self.increase_size,
305                         font=menufont)
306        menu.add_separator()
307
308        for size in font_sizes:
309            def resize(size=size):
310                self.set_txtsize(size)
311            menu.add_command(label=str(size), underline=0,
312                             font=menufont, command=resize)
313        return menu
314
315    def makeHelpMenu(self, master):
316        menu = Menu(master)
317
318        for help_label, help_file in help_entries:
319            def show(help_label=help_label, help_file=help_file):
320                view_text(self.root, help_label, help_file)
321            menu.add_command(label=help_label, font=menufont, command=show)
322        return menu
323
324    def refreshCanvas(self):
325        if self.dirty:
326            self.screen.clear()
327            self.dirty=False
328
329    def loadfile(self, filename):
330        self.clearCanvas()
331        turtle.TurtleScreen._RUNNING = False
332        modname = 'turtledemo.' + filename
333        __import__(modname)
334        self.module = sys.modules[modname]
335        with open(self.module.__file__, 'r') as f:
336            chars = f.read()
337        self.text.delete("1.0", "end")
338        self.text.insert("1.0", chars)
339        self.root.title(filename + " - a Python turtle graphics example")
340        self.configGUI(NORMAL, DISABLED, DISABLED,
341                       "Press start button", "red")
342        self.state = READY
343
344    def startDemo(self):
345        self.refreshCanvas()
346        self.dirty = True
347        turtle.TurtleScreen._RUNNING = True
348        self.configGUI(DISABLED, NORMAL, DISABLED,
349                       "demo running...", "black")
350        self.screen.clear()
351        self.screen.mode("standard")
352        self.state = RUNNING
353
354        try:
355            result = self.module.main()
356            if result == "EVENTLOOP":
357                self.state = EVENTDRIVEN
358            else:
359                self.state = DONE
360        except turtle.Terminator:
361            if self.root is None:
362                return
363            self.state = DONE
364            result = "stopped!"
365        if self.state == DONE:
366            self.configGUI(NORMAL, DISABLED, NORMAL,
367                           result)
368        elif self.state == EVENTDRIVEN:
369            self.exitflag = True
370            self.configGUI(DISABLED, NORMAL, DISABLED,
371                           "use mouse/keys or STOP", "red")
372
373    def clearCanvas(self):
374        self.refreshCanvas()
375        self.screen._delete("all")
376        self.scanvas.config(cursor="")
377        self.configGUI(NORMAL, DISABLED, DISABLED)
378
379    def stopIt(self):
380        if self.exitflag:
381            self.clearCanvas()
382            self.exitflag = False
383            self.configGUI(NORMAL, DISABLED, DISABLED,
384                           "STOPPED!", "red")
385        turtle.TurtleScreen._RUNNING = False
386
387    def _destroy(self):
388        turtle.TurtleScreen._RUNNING = False
389        self.root.destroy()
390        self.root = None
391
392
393def main():
394    demo = DemoWindow()
395    demo.root.mainloop()
396
397if __name__ == '__main__':
398    main()
399