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