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 # 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 397def main(): 398 demo = DemoWindow() 399 demo.root.mainloop() 400 401if __name__ == '__main__': 402 main() 403