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 """
88 import sys
89 import os
90
91 from tkinter import *
92 from idlelib.colorizer import ColorDelegator, color_config
93 from idlelib.percolator import Percolator
94 from idlelib.textview import view_text
95 from turtledemo import __doc__ as about_turtledemo
96
97 import turtle
98
99 demo_dir = os.path.dirname(os.path.abspath(__file__))
100 darwin = sys.platform == 'darwin'
101
102 STARTUP = 1
103 READY = 2
104 RUNNING = 3
105 DONE = 4
106 EVENTDRIVEN = 5
107
108 menufont = ("Arial", 12, NORMAL)
109 btnfont = ("Arial", 12, 'bold')
110 txtfont = ['Lucida Console', 10, 'normal']
111
112 MINIMUM_FONT_SIZE = 6
113 MAXIMUM_FONT_SIZE = 100
114 font_sizes = [8, 9, 10, 11, 12, 14, 18, 20, 22, 24, 30]
115
116 def getExampleEntries():
117 return [entry[:-3] for entry in os.listdir(demo_dir) if
118 entry.endswith(".py") and entry[0] != '_']
119
120 help_entries = ( # (help_label, help_doc)
121 ('Turtledemo help', __doc__),
122 ('About turtledemo', about_turtledemo),
123 ('About turtle module', turtle.__doc__),
124 )
125
126
127 class ESC[4;38;5;81mDemoWindow(ESC[4;38;5;149mobject):
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 # t._Screen is a singleton class instantiated or retrieved
241 # by calling Screen. Since tdemo canvas needs a different
242 # configuration, we manually set class attributes before
243 # calling Screen and manually call superclass init after.
244 turtle._Screen._root = root
245
246 self.canvwidth = 1000
247 self.canvheight = 800
248 turtle._Screen._canvas = self.canvas = canvas = turtle.ScrolledCanvas(
249 root, 800, 600, self.canvwidth, self.canvheight)
250 canvas.adjustScrolls()
251 canvas._rootwindow.bind('<Configure>', self.onResize)
252 canvas._canvas['borderwidth'] = 0
253
254 self.screen = screen = turtle.Screen()
255 turtle.TurtleScreen.__init__(screen, canvas)
256 turtle.RawTurtle.screens = [screen]
257 return canvas
258
259 def set_txtsize(self, size):
260 txtfont[1] = size
261 self.text['font'] = tuple(txtfont)
262 self.output_lbl['text'] = 'Font size %d' % size
263
264 def decrease_size(self, dummy=None):
265 self.set_txtsize(max(txtfont[1] - 1, MINIMUM_FONT_SIZE))
266 return 'break'
267
268 def increase_size(self, dummy=None):
269 self.set_txtsize(min(txtfont[1] + 1, MAXIMUM_FONT_SIZE))
270 return 'break'
271
272 def update_mousewheel(self, event):
273 # For wheel up, event.delta = 120 on Windows, -1 on darwin.
274 # X-11 sends Control-Button-4 event instead.
275 if (event.delta < 0) == (not darwin):
276 return self.decrease_size()
277 else:
278 return self.increase_size()
279
280 def configGUI(self, start, stop, clear, txt="", color="blue"):
281 if darwin: # Leave Mac button colors alone - #44254.
282 self.start_btn.config(state=start)
283 self.stop_btn.config(state=stop)
284 self.clear_btn.config(state=clear)
285 else:
286 self.start_btn.config(state=start,
287 bg="#d00" if start == NORMAL else "#fca")
288 self.stop_btn.config(state=stop,
289 bg="#d00" if stop == NORMAL else "#fca")
290 self.clear_btn.config(state=clear,
291 bg="#d00" if clear == NORMAL else "#fca")
292 self.output_lbl.config(text=txt, fg=color)
293
294 def makeLoadDemoMenu(self, master):
295 menu = Menu(master)
296
297 for entry in getExampleEntries():
298 def load(entry=entry):
299 self.loadfile(entry)
300 menu.add_command(label=entry, underline=0,
301 font=menufont, command=load)
302 return menu
303
304 def makeFontMenu(self, master):
305 menu = Menu(master)
306 menu.add_command(label="Decrease (C-'-')", command=self.decrease_size,
307 font=menufont)
308 menu.add_command(label="Increase (C-'+')", command=self.increase_size,
309 font=menufont)
310 menu.add_separator()
311
312 for size in font_sizes:
313 def resize(size=size):
314 self.set_txtsize(size)
315 menu.add_command(label=str(size), underline=0,
316 font=menufont, command=resize)
317 return menu
318
319 def makeHelpMenu(self, master):
320 menu = Menu(master)
321
322 for help_label, help_file in help_entries:
323 def show(help_label=help_label, help_file=help_file):
324 view_text(self.root, help_label, help_file)
325 menu.add_command(label=help_label, font=menufont, command=show)
326 return menu
327
328 def refreshCanvas(self):
329 if self.dirty:
330 self.screen.clear()
331 self.dirty=False
332
333 def loadfile(self, filename):
334 self.clearCanvas()
335 turtle.TurtleScreen._RUNNING = False
336 modname = 'turtledemo.' + filename
337 __import__(modname)
338 self.module = sys.modules[modname]
339 with open(self.module.__file__, 'r') as f:
340 chars = f.read()
341 self.text.delete("1.0", "end")
342 self.text.insert("1.0", chars)
343 self.root.title(filename + " - a Python turtle graphics example")
344 self.configGUI(NORMAL, DISABLED, DISABLED,
345 "Press start button", "red")
346 self.state = READY
347
348 def startDemo(self):
349 self.refreshCanvas()
350 self.dirty = True
351 turtle.TurtleScreen._RUNNING = True
352 self.configGUI(DISABLED, NORMAL, DISABLED,
353 "demo running...", "black")
354 self.screen.clear()
355 self.screen.mode("standard")
356 self.state = RUNNING
357
358 try:
359 result = self.module.main()
360 if result == "EVENTLOOP":
361 self.state = EVENTDRIVEN
362 else:
363 self.state = DONE
364 except turtle.Terminator:
365 if self.root is None:
366 return
367 self.state = DONE
368 result = "stopped!"
369 if self.state == DONE:
370 self.configGUI(NORMAL, DISABLED, NORMAL,
371 result)
372 elif self.state == EVENTDRIVEN:
373 self.exitflag = True
374 self.configGUI(DISABLED, NORMAL, DISABLED,
375 "use mouse/keys or STOP", "red")
376
377 def clearCanvas(self):
378 self.refreshCanvas()
379 self.screen._delete("all")
380 self.canvas.config(cursor="")
381 self.configGUI(NORMAL, DISABLED, DISABLED)
382
383 def stopIt(self):
384 if self.exitflag:
385 self.clearCanvas()
386 self.exitflag = False
387 self.configGUI(NORMAL, DISABLED, DISABLED,
388 "STOPPED!", "red")
389 turtle.TurtleScreen._RUNNING = False
390
391 def _destroy(self):
392 turtle.TurtleScreen._RUNNING = False
393 self.root.destroy()
394 self.root = None
395
396
397 def main():
398 demo = DemoWindow()
399 demo.root.mainloop()
400
401 if __name__ == '__main__':
402 main()