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 127 128class DemoWindow(object): 129 130 def __init__(self, filename=None): 131 self.root = root = turtle._root = Tk() 132 root.title('Python turtle-graphics examples') 133 root.wm_protocol("WM_DELETE_WINDOW", self._destroy) 134 135 if darwin: 136 import subprocess 137 # Make sure we are the currently activated OS X application 138 # so that our menu bar appears. 139 p = subprocess.Popen( 140 [ 141 'osascript', 142 '-e', 'tell application "System Events"', 143 '-e', 'set frontmost of the first process whose ' 144 'unix id is {} to true'.format(os.getpid()), 145 '-e', 'end tell', 146 ], 147 stderr=subprocess.DEVNULL, 148 stdout=subprocess.DEVNULL,) 149 150 root.grid_rowconfigure(0, weight=1) 151 root.grid_columnconfigure(0, weight=1) 152 root.grid_columnconfigure(1, minsize=90, weight=1) 153 root.grid_columnconfigure(2, minsize=90, weight=1) 154 root.grid_columnconfigure(3, minsize=90, weight=1) 155 156 self.mBar = Menu(root, relief=RAISED, borderwidth=2) 157 self.mBar.add_cascade(menu=self.makeLoadDemoMenu(self.mBar), 158 label='Examples', underline=0) 159 self.mBar.add_cascade(menu=self.makeFontMenu(self.mBar), 160 label='Fontsize', underline=0) 161 self.mBar.add_cascade(menu=self.makeHelpMenu(self.mBar), 162 label='Help', underline=0) 163 root['menu'] = self.mBar 164 165 pane = PanedWindow(orient=HORIZONTAL, sashwidth=5, 166 sashrelief=SOLID, bg='#ddd') 167 pane.add(self.makeTextFrame(pane)) 168 pane.add(self.makeGraphFrame(pane)) 169 pane.grid(row=0, columnspan=4, sticky='news') 170 171 self.output_lbl = Label(root, height= 1, text=" --- ", bg="#ddf", 172 font=("Arial", 16, 'normal'), borderwidth=2, 173 relief=RIDGE) 174 self.start_btn = Button(root, text=" START ", font=btnfont, 175 fg="white", disabledforeground = "#fed", 176 command=self.startDemo) 177 self.stop_btn = Button(root, text=" STOP ", font=btnfont, 178 fg="white", disabledforeground = "#fed", 179 command=self.stopIt) 180 self.clear_btn = Button(root, text=" CLEAR ", font=btnfont, 181 fg="white", disabledforeground="#fed", 182 command = self.clearCanvas) 183 self.output_lbl.grid(row=1, column=0, sticky='news', padx=(0,5)) 184 self.start_btn.grid(row=1, column=1, sticky='ew') 185 self.stop_btn.grid(row=1, column=2, sticky='ew') 186 self.clear_btn.grid(row=1, column=3, sticky='ew') 187 188 Percolator(self.text).insertfilter(ColorDelegator()) 189 self.dirty = False 190 self.exitflag = False 191 if filename: 192 self.loadfile(filename) 193 self.configGUI(DISABLED, DISABLED, DISABLED, 194 "Choose example from menu", "black") 195 self.state = STARTUP 196 197 198 def onResize(self, event): 199 cwidth = self._canvas.winfo_width() 200 cheight = self._canvas.winfo_height() 201 self._canvas.xview_moveto(0.5*(self.canvwidth-cwidth)/self.canvwidth) 202 self._canvas.yview_moveto(0.5*(self.canvheight-cheight)/self.canvheight) 203 204 def makeTextFrame(self, root): 205 self.text_frame = text_frame = Frame(root) 206 self.text = text = Text(text_frame, name='text', padx=5, 207 wrap='none', width=45) 208 color_config(text) 209 210 self.vbar = vbar = Scrollbar(text_frame, name='vbar') 211 vbar['command'] = text.yview 212 vbar.pack(side=LEFT, fill=Y) 213 self.hbar = hbar = Scrollbar(text_frame, name='hbar', orient=HORIZONTAL) 214 hbar['command'] = text.xview 215 hbar.pack(side=BOTTOM, fill=X) 216 text['yscrollcommand'] = vbar.set 217 text['xscrollcommand'] = hbar.set 218 219 text['font'] = tuple(txtfont) 220 shortcut = 'Command' if darwin else 'Control' 221 text.bind_all('<%s-minus>' % shortcut, self.decrease_size) 222 text.bind_all('<%s-underscore>' % shortcut, self.decrease_size) 223 text.bind_all('<%s-equal>' % shortcut, self.increase_size) 224 text.bind_all('<%s-plus>' % shortcut, self.increase_size) 225 text.bind('<Control-MouseWheel>', self.update_mousewheel) 226 text.bind('<Control-Button-4>', self.increase_size) 227 text.bind('<Control-Button-5>', self.decrease_size) 228 229 text.pack(side=LEFT, fill=BOTH, expand=1) 230 return text_frame 231 232 def makeGraphFrame(self, root): 233 turtle._Screen._root = root 234 self.canvwidth = 1000 235 self.canvheight = 800 236 turtle._Screen._canvas = self._canvas = canvas = turtle.ScrolledCanvas( 237 root, 800, 600, self.canvwidth, self.canvheight) 238 canvas.adjustScrolls() 239 canvas._rootwindow.bind('<Configure>', self.onResize) 240 canvas._canvas['borderwidth'] = 0 241 242 self.screen = _s_ = turtle.Screen() 243 turtle.TurtleScreen.__init__(_s_, _s_._canvas) 244 self.scanvas = _s_._canvas 245 turtle.RawTurtle.screens = [_s_] 246 return canvas 247 248 def set_txtsize(self, size): 249 txtfont[1] = size 250 self.text['font'] = tuple(txtfont) 251 self.output_lbl['text'] = 'Font size %d' % size 252 253 def decrease_size(self, dummy=None): 254 self.set_txtsize(max(txtfont[1] - 1, MINIMUM_FONT_SIZE)) 255 return 'break' 256 257 def increase_size(self, dummy=None): 258 self.set_txtsize(min(txtfont[1] + 1, MAXIMUM_FONT_SIZE)) 259 return 'break' 260 261 def update_mousewheel(self, event): 262 # For wheel up, event.delte = 120 on Windows, -1 on darwin. 263 # X-11 sends Control-Button-4 event instead. 264 if (event.delta < 0) == (not darwin): 265 return self.decrease_size() 266 else: 267 return self.increase_size() 268 269 def configGUI(self, start, stop, clear, txt="", color="blue"): 270 self.start_btn.config(state=start, 271 bg="#d00" if start == NORMAL else "#fca") 272 self.stop_btn.config(state=stop, 273 bg="#d00" if stop == NORMAL else "#fca") 274 self.clear_btn.config(state=clear, 275 bg="#d00" if clear == NORMAL else"#fca") 276 self.output_lbl.config(text=txt, fg=color) 277 278 def makeLoadDemoMenu(self, master): 279 menu = Menu(master) 280 281 for entry in getExampleEntries(): 282 def load(entry=entry): 283 self.loadfile(entry) 284 menu.add_command(label=entry, underline=0, 285 font=menufont, command=load) 286 return menu 287 288 def makeFontMenu(self, master): 289 menu = Menu(master) 290 menu.add_command(label="Decrease (C-'-')", command=self.decrease_size, 291 font=menufont) 292 menu.add_command(label="Increase (C-'+')", command=self.increase_size, 293 font=menufont) 294 menu.add_separator() 295 296 for size in font_sizes: 297 def resize(size=size): 298 self.set_txtsize(size) 299 menu.add_command(label=str(size), underline=0, 300 font=menufont, command=resize) 301 return menu 302 303 def makeHelpMenu(self, master): 304 menu = Menu(master) 305 306 for help_label, help_file in help_entries: 307 def show(help_label=help_label, help_file=help_file): 308 view_text(self.root, help_label, help_file) 309 menu.add_command(label=help_label, font=menufont, command=show) 310 return menu 311 312 def refreshCanvas(self): 313 if self.dirty: 314 self.screen.clear() 315 self.dirty=False 316 317 def loadfile(self, filename): 318 self.clearCanvas() 319 turtle.TurtleScreen._RUNNING = False 320 modname = 'turtledemo.' + filename 321 __import__(modname) 322 self.module = sys.modules[modname] 323 with open(self.module.__file__, 'r') as f: 324 chars = f.read() 325 self.text.delete("1.0", "end") 326 self.text.insert("1.0", chars) 327 self.root.title(filename + " - a Python turtle graphics example") 328 self.configGUI(NORMAL, DISABLED, DISABLED, 329 "Press start button", "red") 330 self.state = READY 331 332 def startDemo(self): 333 self.refreshCanvas() 334 self.dirty = True 335 turtle.TurtleScreen._RUNNING = True 336 self.configGUI(DISABLED, NORMAL, DISABLED, 337 "demo running...", "black") 338 self.screen.clear() 339 self.screen.mode("standard") 340 self.state = RUNNING 341 342 try: 343 result = self.module.main() 344 if result == "EVENTLOOP": 345 self.state = EVENTDRIVEN 346 else: 347 self.state = DONE 348 except turtle.Terminator: 349 if self.root is None: 350 return 351 self.state = DONE 352 result = "stopped!" 353 if self.state == DONE: 354 self.configGUI(NORMAL, DISABLED, NORMAL, 355 result) 356 elif self.state == EVENTDRIVEN: 357 self.exitflag = True 358 self.configGUI(DISABLED, NORMAL, DISABLED, 359 "use mouse/keys or STOP", "red") 360 361 def clearCanvas(self): 362 self.refreshCanvas() 363 self.screen._delete("all") 364 self.scanvas.config(cursor="") 365 self.configGUI(NORMAL, DISABLED, DISABLED) 366 367 def stopIt(self): 368 if self.exitflag: 369 self.clearCanvas() 370 self.exitflag = False 371 self.configGUI(NORMAL, DISABLED, DISABLED, 372 "STOPPED!", "red") 373 turtle.TurtleScreen._RUNNING = False 374 375 def _destroy(self): 376 turtle.TurtleScreen._RUNNING = False 377 self.root.destroy() 378 self.root = None 379 380 381def main(): 382 demo = DemoWindow() 383 demo.root.mainloop() 384 385if __name__ == '__main__': 386 main() 387