Repository: PacktPublishing/Tkinter-GUI-Application-Development-Blueprints-Second-Edition Branch: master Commit: 1e160c057502 Files: 203 Total size: 765.5 KB Directory structure: gitextract_ux_6jgor/ ├── Chapter 01/ │ ├── 1.01.py │ ├── 1.02.py │ ├── 1.03.py │ ├── 1.04.py │ ├── 1.05.py │ ├── 1.06.py │ ├── 1.07.py │ ├── 1.08.py │ ├── 1.09.py │ ├── 1.10.py │ ├── 1.11.py │ ├── 1.12.py │ ├── gir.xbm │ ├── optionDB.txt │ ├── readme.txt │ └── textcontent.txt ├── Chapter 02/ │ ├── 2.01.py │ ├── 2.02.py │ ├── 2.03.py │ ├── 2.04.py │ ├── 2.05.py │ ├── 2.06.py │ ├── 2.07.py │ ├── 2.08.py │ ├── 2.09.py │ ├── 2.10.py │ ├── 2.11.py │ ├── 2.12.py │ └── readme.txt ├── Chapter 03/ │ ├── 3.01.py │ ├── 3.02.py │ ├── 3.03.py │ ├── 3.04.py │ ├── 3.05.py │ ├── 3.06.py │ ├── 3.07.py │ ├── 3.08.py │ ├── 3.09.py │ ├── 3.10.py │ ├── 3.11.py │ ├── 3.12.py │ └── readme.txt ├── Chapter 04/ │ ├── 4.01/ │ │ ├── configurations.py │ │ ├── controller.py │ │ ├── exceptions.py │ │ ├── model.py │ │ └── view.py │ ├── 4.02/ │ │ ├── configurations.py │ │ ├── controller.py │ │ ├── exceptions.py │ │ ├── model.py │ │ ├── piece.py │ │ └── view.py │ ├── 4.03/ │ │ ├── configurations.py │ │ ├── controller.py │ │ ├── exceptions.py │ │ ├── model.py │ │ ├── piece.py │ │ └── view.py │ ├── 4.04/ │ │ ├── configurations.py │ │ ├── controller.py │ │ ├── exceptions.py │ │ ├── model.py │ │ ├── piece.py │ │ └── view.py │ ├── 4.05/ │ │ ├── configurations.py │ │ ├── controller.py │ │ ├── exceptions.py │ │ ├── model.py │ │ ├── piece.py │ │ └── view.py │ ├── 4.06/ │ │ ├── configurations.py │ │ ├── controller.py │ │ ├── exceptions.py │ │ ├── model.py │ │ ├── piece.py │ │ └── view.py │ ├── 4.07/ │ │ ├── chess_options.ini │ │ ├── configurations.py │ │ ├── controller.py │ │ ├── exceptions.py │ │ ├── model.py │ │ ├── piece.py │ │ ├── preferenceswindow.py │ │ └── view.py │ └── readme.txt ├── Chapter 05/ │ ├── 5.01/ │ │ ├── model.py │ │ ├── player.py │ │ └── view.py │ ├── 5.02/ │ │ ├── model.py │ │ ├── player.py │ │ └── view.py │ ├── 5.03/ │ │ ├── model.py │ │ ├── player.py │ │ └── view.py │ ├── 5.04/ │ │ ├── model.py │ │ ├── player.py │ │ └── view.py │ ├── 5.05/ │ │ ├── model.py │ │ ├── player.py │ │ ├── seekbar.py │ │ └── view.py │ ├── 5.06/ │ │ ├── helpers.py │ │ ├── model.py │ │ ├── player.py │ │ ├── seekbar.py │ │ └── view.py │ ├── 5.07/ │ │ ├── helpers.py │ │ ├── model.py │ │ ├── player.py │ │ ├── seekbar.py │ │ └── view.py │ ├── 5.08/ │ │ ├── helpers.py │ │ ├── model.py │ │ ├── player.py │ │ ├── seekbar.py │ │ └── view.py │ ├── 5.09/ │ │ ├── helpers.py │ │ ├── model.py │ │ ├── player.py │ │ ├── pygleterror.py │ │ ├── seekbar.py │ │ └── view.py │ └── readme.txt ├── Chapter 06/ │ ├── 6.01.py │ ├── 6.02.py │ ├── 6.03.py │ ├── 6.04.py │ ├── 6.05.py │ ├── 6.06.py │ ├── 6.07.py │ ├── 6.08.py │ ├── 6.09.py │ ├── framework.py │ └── supershapes.py ├── Chapter 07/ │ ├── 7.01/ │ │ ├── constants.py │ │ └── view.py │ ├── 7.02/ │ │ ├── constants.py │ │ └── view.py │ ├── 7.03/ │ │ ├── audio.py │ │ ├── constants.py │ │ └── view.py │ ├── 7.04/ │ │ ├── audio.py │ │ ├── constants.py │ │ └── view.py │ ├── 7.05/ │ │ ├── audio.py │ │ ├── constants.py │ │ └── view.py │ ├── 7.06/ │ │ ├── audio.py │ │ ├── constants.py │ │ └── view.py │ ├── 7.07/ │ │ ├── audio.py │ │ ├── constants.py │ │ ├── score_maker.py │ │ └── view.py │ ├── handle_widget_resize.py │ ├── json/ │ │ ├── chords.json │ │ ├── progressions.json │ │ └── scales.json │ ├── nonresponsive.py │ └── responsive.py ├── Chapter 08/ │ ├── 8.01_screensaver.py │ ├── 8.02_pie_chart.py │ ├── 8.03_bar_graph.py │ ├── 8.04_scatter_plot.py │ ├── 8.05_matplotlib_embedding_graphs.py │ ├── 8.06_polar_plot.py │ ├── 8.07_gravity_simulation.py │ ├── 8.08_Mandelbrot.py │ ├── 8.09_vornoi_diagram.py │ ├── 8.10_spring_pendulum.py │ ├── 8.11_chaos_game.py │ ├── 8.12_phyllotaxis.py │ └── 8.13_3D_graphics.py ├── Chapter 09/ │ ├── 9.01_race_condition.py │ ├── 9.02_lock_demo.py │ ├── 9.03_threading_with queue.py │ ├── 9.04_game_of_ snake.py │ ├── 9.05_urllib_demo.py │ ├── 9.06_weather_ reporter.py │ ├── 9.07_socket_demo.py │ ├── 9.08_port_scanner.py │ ├── 9.09_chat_server.py │ ├── 9.10_chat_client.py │ ├── 9.11_phonebook.py │ ├── 9.12_async_demo.py │ ├── 9.13.arduino_sketch.ino │ ├── 9.14_read_from_serial_port.py │ └── readme.txt ├── Chapter 10/ │ ├── 10.01_trace_variable.py │ ├── 10.02_widget_traversal.py │ ├── 10.03_validation_mode_demo.py │ ├── 10.04_percent _substitutions _demo.py │ ├── 10.05_key_validation.py │ ├── 10.06_focus_out _validation.py │ ├── 10.07_formatting_entry_widget_to_display_date.py │ ├── 10.08_font_demo.py │ ├── 10.09_all_fonts_on_a_system.py │ ├── 10.10_font_selector.py │ ├── 10.11_reading_from_command_line.py │ └── 10.12_tkinter_class_hierarchy.py ├── LICENSE └── README.md ================================================ FILE CONTENTS ================================================ ================================================ FILE: Chapter 01/1.01.py ================================================ """ Code illustration: 1.01 Your first GUI application - the top level window Tkinter GUI Application Development Blueprints """ import tkinter as tk root = tk.Tk() root.mainloop() ================================================ FILE: Chapter 01/1.02.py ================================================ """ Code illustration: 1.02 Adding some widgets Tkinter GUI Application Development Blueprints """ import tkinter as tk root =tk.Tk() my_label = tk.Label(root, text="I am a label widget") #(1) my_button = tk.Button(root, text="I am a button") #(2) my_label.pack() #(3) my_button.pack() #(4) root.mainloop() ================================================ FILE: Chapter 01/1.03.py ================================================ """ Code illustration: 01.03 A demonstration of all core tkinter widgets Tkinter GUI Application Development Blueprints """ import tkinter as tk root = tk.Tk() root.title('I am a Top Level Widget, parent to other widgets') #create a frame widget for placing menu my_menu_bar = tk.Frame(root, relief='raised', bd=2) my_menu_bar.pack(fill=tk.X) # Create Menu Widget 1 and Sub Menu 1 my_menu_button = tk.Menubutton( my_menu_bar, text='Menu tk.Button Widget 1', ) my_menu_button.pack(side=tk.LEFT) #menu widget my_menu = tk.Menu(my_menu_button, tearoff=0) my_menu_button['menu'] = my_menu my_menu.add('command', label='Menu Widget 1') #Add Sub Menu 1 # Create Menu2 and Submenu2 menu_button_2 = tk.Menubutton( my_menu_bar, text='Menu 2', ) menu_button_2.pack(side=tk.LEFT) my_menu_2 = tk.Menu(menu_button_2, tearoff=0) menu_button_2['menu'] = my_menu_2 my_menu_2.add('command', label='Sub Menu 2') # Add Sub Menu 2 # # # my_frame_1 and its contents # # creating a frame (my_frame_1) my_frame_1 = tk.Frame(root, bd=2, relief=tk.SUNKEN) my_frame_1.pack(side=tk.LEFT) # add label to to my_frame_1 tk.Label(my_frame_1, text='I am a tk.Label widget').pack() #add entry widget to my_frame_1 tv = tk.StringVar() #discussed later tk.Entry(my_frame_1, textvariable=tv).pack() tv.set('I am an entry widget') #add button widget to my_frame_1 tk.Button(my_frame_1, text='tk.Button widget').pack() #add check button widget to my_frame_1 tk.Checkbutton(my_frame_1, text='Checktk.Button Widget').pack() #add radio buttons to my_frame_1 tk.Radiobutton(my_frame_1, text='Radio tk.Button Un', value=1).pack() tk.Radiobutton(my_frame_1, text='Radio tk.Button Dos', value=2).pack() tk.Radiobutton(my_frame_1, text='Radio tk.Button Tres', value=3).pack() #tk.OptionMenu Widget tk.Label(my_frame_1, text='Example of tk.OptionMenu Widget:').pack() tk.OptionMenu(my_frame_1, '', "Option A", "Option B", "Option C").pack() #adding my_image image tk.Label(my_frame_1, text='Image Fun with Bitmap Class:').pack() my_image = tk.BitmapImage(file="gir.xbm") my_label = tk.Label(my_frame_1, image=my_image) my_label.image = my_image # keep a reference! my_label.pack() # # # frame2 and widgets it contains. # # #create another frame(my_frame_2) to hold a list box, Spinbox Widget,Scale Widget, : my_frame_2 = tk.Frame(root, bd=2, relief=tk.GROOVE) my_frame_2.pack(side=tk.RIGHT) #add Photimage Class Widget to my_frame_2 tk.Label( my_frame_2, text='Image displayed with \nPhotoImage class widget:').pack() dance_photo = tk.PhotoImage(file='dance.gif') dance_photo_label = tk.Label(my_frame_2, image=dance_photo) dance_photo_label.image = dance_photo dance_photo_label.pack() #add my_listbox widget to my_frame_2 tk.Label(my_frame_2, text='Below is an example of my_listbox widget:').pack() my_listbox = tk.Listbox(my_frame_2, height=4) for line in ['Listbox Choice 1', 'Choice 2', 'Choice 3', 'Choice 4']: my_listbox.insert(tk.END, line) my_listbox.pack() #spinbox widget tk.Label(my_frame_2, text='Below is an example of spinbox widget:').pack() tk.Spinbox(my_frame_2, values=(1, 2, 4, 8, 10)).pack() #scale widget tk.Scale( my_frame_2, from_=0.0, to=100.0, label='Scale widget', orient=tk.HORIZONTAL).pack() #LabelFrame label_frame = tk.LabelFrame( my_frame_2, text="LabelFrame Widget", padx=10, pady=10) label_frame.pack(padx=10, pady=10) tk.Entry(label_frame).pack() #message widget tk.Message(my_frame_2, text='I am a Message widget').pack() # # # tk.Frame 3 # # my_frame_3 = tk.Frame(root, bd=2, relief=tk.SUNKEN) #text widget and associated tk.Scrollbar widget my_text = tk.Text(my_frame_3, height=10, width=40) file_object = open('textcontent.txt') file_content = file_object.read() file_object.close() my_text.insert(tk.END, file_content) my_text.pack(side=tk.LEFT, fill=tk.X, padx=5) #add scrollbar widget to the text widget my_scrollbar = tk.Scrollbar(my_frame_3, orient=tk.VERTICAL, command=my_text.yview) my_scrollbar.pack() my_text.configure(yscrollcommand=my_scrollbar.set) my_frame_3.pack() # # # tk.Frame 4 # # #create another frame(my_frame_4) my_frame_4 = tk.Frame(root) my_frame_4.pack() my_canvas = tk.Canvas(my_frame_4, bg='white', width=340, height=80) my_canvas.pack() my_canvas.create_oval(20, 15, 60, 60, fill='red') my_canvas.create_oval(40, 15, 60, 60, fill='grey') my_canvas.create_text( 130, 38, text='I am a tk.Canvas Widget', font=('arial', 8, 'bold')) # # # A paned window widget # # tk.Label(root, text='Below is an example of Paned window widget:').pack() tk.Label( root, text='Notice you can adjust the size of each pane by dragging it').pack() my_paned_window_1 = tk.PanedWindow() my_paned_window_1.pack(fill=tk.BOTH, expand=2) left_pane_text = tk.Text(my_paned_window_1, height=6, width=15) my_paned_window_1.add(left_pane_text) my_paned_window_2 = tk.PanedWindow(my_paned_window_1, orient=tk.VERTICAL) my_paned_window_1.add(my_paned_window_2) top_pane_text = tk.Text(my_paned_window_2, height=3, width=3) my_paned_window_2.add(top_pane_text) bottom_pane_text = tk.Text(my_paned_window_2, height=3, width=3) my_paned_window_2.add(bottom_pane_text) root.mainloop() ================================================ FILE: Chapter 01/1.04.py ================================================ """ Code illustration: 1.04 A demonstration of some of pack() options Tkinter GUI Application Development Blueprints """ import tkinter as tk root = tk.Tk() frame = tk.Frame(root) # demo of side and fill options tk.Label(frame, text="Pack Demo of side and fill").pack() tk.Button(frame, text="A").pack(side=tk.LEFT, fill=tk.Y) tk.Button(frame, text="B").pack(side=tk.TOP, fill=tk.X) tk.Button(frame, text="C").pack(side=tk.RIGHT, fill=tk.NONE) tk.Button(frame, text="D").pack(side=tk.TOP, fill=tk.BOTH) frame.pack() # note the top frame does not expand nor does it fill in #X or Y directions # demo of expand options - best understood by expanding the root widget # and seeing the effect on all the three buttons below. tk.Label(root, text="Pack Demo of expand").pack() tk.Button(root, text="I do not expand").pack() tk.Button(root, text="I do not fill x but I expand").pack(expand=1) tk.Button(root, text="I fill x and expand").pack(fill=tk.X, expand=1) root.mainloop() ================================================ FILE: Chapter 01/1.05.py ================================================ """ Code illustration: 1.05 Where to use pack() options @Tkinter GUI Application Development Blueprints """ import tkinter as tk root = tk.Tk() parent = tk.Frame(root) #placing widgets top-down tk.Button(parent, text='ALL IS WELL').pack(fill=tk.X) tk.Button(parent, text='BACK TO BASICS').pack(fill=tk.X) tk.Button(parent, text='CATCH ME IF U CAN').pack(fill=tk.X) #placing widgets side by side tk.Button(parent, text='LEFT').pack(side=tk.LEFT) tk.Button(parent, text='CENTER').pack(side=tk.LEFT) tk.Button(parent, text='RIGHT').pack(side=tk.LEFT) parent.pack() root.mainloop() ================================================ FILE: Chapter 01/1.06.py ================================================ """ Code illustration: 1.06 Simple example of grid Geometry Manager @Tkinter GUI Application Development Blueprints """ import tkinter as tk root = tk.Tk() tk.Label(root, text="Username").grid(row=0, sticky=tk.W) tk.Label(root, text="Password").grid(row=1, sticky=tk.W) tk.Entry(root).grid(row=0, column=1, sticky=tk.E) tk.Entry(root).grid(row=1, column=1, sticky=tk.E) tk.Button(root, text="Login").grid(row=2, column=1, sticky=tk.E) root.mainloop() ================================================ FILE: Chapter 01/1.07.py ================================================ """ Code illustration: 1.07 A demonstration of grid() geometry manager options @Tkinter GUI Application Development Blueprints """ import tkinter as tk parent = tk.Tk() parent.title('Find & Replace') tk.Label(parent, text="Find:").grid(row=0, column=0, sticky='e') tk.Entry(parent, width=60).grid(row=0, column=1, padx=2, pady=2, sticky='we', columnspan=9) tk.Label(parent, text="Replace:").grid(row=1, column=0, sticky='e') tk.Entry(parent).grid(row=1, column=1, padx=2, pady=2, sticky='we', columnspan=9) tk.Button(parent, text="Find").grid(row=0, column=10, sticky='e'+'w', padx=2, pady=2) tk.Button(parent, text="Find All").grid(row=1, column=10, sticky='e'+'w', padx=2) tk.Button(parent, text="Replace").grid(row=2, column=10, sticky='e'+'w', padx=2) tk.Button(parent, text="Replace All").grid(row=3, column=10, sticky='e'+'w', padx=2) tk.Checkbutton(parent, text='Match whole word only ').grid(row =2, column=1, columnspan=4,sticky='w') tk.Checkbutton(parent, text='Match Case').grid(row =3, column=1, columnspan=4,sticky='w') tk.Checkbutton(parent, text='Wrap around').grid(row =4, column=1, columnspan=4,sticky='w') tk.Label(parent, text="Direction:").grid(row=2, column=6,sticky='w') tk.Radiobutton(parent, text='Up', value=1).grid(row=3, column=6, columnspan=6, sticky='w') tk.Radiobutton(parent, text='Down', value=2).grid(row=3, column=7,columnspan=2, sticky='e') parent.mainloop() ================================================ FILE: Chapter 01/1.08.py ================================================ """ Code illustration: 1.08 A demonstration of common place() options @Tkinter GUI Application Development Blueprints """ import tkinter as tk root = tk.Tk() # Absolute positioning tk.Button(root, text="Absolute Placement").place(x=20, y=10) # Relative positioning tk.Button( root, text="Relative").place( relx=0.8, rely=0.2, relwidth=0.5, width=10, anchor=tk.NE) root.mainloop() ================================================ FILE: Chapter 01/1.09.py ================================================ """ Code illustration: 1.09 A demonstration of event binding with the bind() method @Tkinter GUI Application Development Blueprints """ import tkinter as tk root = tk.Tk() tk.Label(root, text='Click at different \n locations in the frame below').pack() def callback(event): ##(2) print(dir(event))##(3) Inspecting the instance event print("you clicked at", event.x, event.y )##(4) frame = tk.Frame(root, bg='khaki', width=130, height=80) frame.bind("", callback)##(1) frame.pack() root.mainloop() ================================================ FILE: Chapter 01/1.10.py ================================================ """ Code illustration: 1.10 A demonstration of events and the information available in it. @Tkinter GUI Application Development Blueprints """ import tkinter as tk def show_event_details(event): print('='*50) print("EventKeySymbol=" + str(event.keysym)) print("EventType=" + str(event.type)) print("EventWidgetId=" + str(event.widget)) print("EventCoordinate (x,y)=(" + str(event.x)+","+str(event.y)+")") print("Time:", str(event.time)) root = tk.Tk() button = tk.Button(root, text="tk.Button Bound to: \n Keyboard Enter & Mouse Click") #create button button.pack(pady=5,padx=4) button.focus_force() button.bind("", show_event_details) #bind button to mouse click button.bind("", show_event_details)#bind button to Enter Key tk.Label(text="tk.Entry is Bound to Mouseclick \n, FocusIn and Keypress Event").pack() entry = tk.Entry(root) #creating entry widget entry.pack() #binding entry widget to mouse click and focus in entry.bind("", show_event_details) # left mouse click entry.bind("", show_event_details) # right mouse click entry.bind("", show_event_details) #binding entry widget alphabets and numbers from keyboard alpha_num_keys = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz123456789' for key in alpha_num_keys: entry.bind(""%key, show_event_details) #binding entry widget to keysym keysyms = ['Alt_L', 'Alt_R','BackSpace', 'Cancel', 'Caps_Lock','Control_L', 'Control_R','Delete', 'Down', 'End', 'Escape', 'Execute','F1', 'F2', 'Home', 'Insert', 'Left','Linefeed','KP_0','KP_1','KP_2', 'KP_3','KP_4','KP_5','KP_6','KP_7','KP_8','KP_9','KP_Add', 'KP_Decimal','KP_Divide'] for i in keysyms: entry.bind(""%i, show_event_details) #binding tk.Canvas widget to Motion Event tk.Label(text="Canvas Bound to Motion Event\n(Hover over the area \nto see motion event )").pack() canvas = tk.Canvas(root, background='white',width=100, height=30) canvas.pack() canvas.bind('', show_event_details) tk.Label(text="Entry Widget Bound to \n").pack() entry_1 = tk.Entry(root) #creating entry widget entry_1.pack(pady=7) #binding entry widget to mouse click and focus in entry_1.bind("", show_event_details) # right mouse click root.mainloop() ================================================ FILE: Chapter 01/1.11.py ================================================ """ Code illustration: 1.11 A demonstration of tkinter Variable Class IntVar, StringVar & BooleanVar @Tkinter GUI Application Development Blueprints """ import tkinter as tk root = tk.Tk() def show(): print( "You entered:") print( "Employee Number: "+ str(employee_number.get())) print( "Login Password: "+ password.get()) print( "Remember Me: "+ str(remember_me.get())) print( '*'*30) #demo of IntVar tk.Label(root, text="Employee Number:").grid(row=1, column=1) employee_number = tk.IntVar() tk.Entry(root, width=40, textvariable=employee_number).grid(row=1, column=2, columnspan=2) employee_number.set("120350") #demo of StringVar tk.Label(root, text="Login Password:").grid(row=2, column=1, sticky='w') password = tk.StringVar() # defines the widget state as string tk.Entry(root,width=40, show="*", textvariable=password).grid(row=2, column=2, columnspan=2) password.set("mysecretpassword") tk.Button(root,text="Login", command=show).grid(row=3, column=3) #demo of Boolean var remember_me = tk.BooleanVar() tk.Checkbutton(root, text="Remember Me", variable=remember_me).grid(row=3, column=2) remember_me.set(True) root.mainloop() ================================================ FILE: Chapter 01/1.12.py ================================================ """ Code illustration: 1.12 A demonstration of tkinter styling @Tkinter GUI Application Development Blueprints """ import tkinter as tk root = tk.Tk() root.configure(background='#4D4D4D') #top level styling # connecting to the external styling optionDB.txt root.option_readfile('optionDB.txt') #widget specific styling text = tk.Text( root, background='#101010', foreground="#D6D6D6", borderwidth=18, relief='sunken', width=17, height=5) text.insert( tk.END, "Style is knowing who you are,what you want to say, and not giving a damn." ) text.grid(row=0, column=0, columnspan=6, padx=5, pady=5) # all the below widgets derive their styling from optionDB.txt file tk.Button(root, text='*').grid(row=1, column=1) tk.Button(root, text='^').grid(row=1, column=2) tk.Button(root, text='#').grid(row=1, column=3) tk.Button(root, text='<').grid(row=2, column=1) tk.Button( root, text='OK', cursor='target').grid( row=2, column=2) #changing cursor style tk.Button(root, text='>').grid(row=2, column=3) tk.Button(root, text='+').grid(row=3, column=1) tk.Button(root, text='v').grid(row=3, column=2) tk.Button(root, text='-').grid(row=3, column=3) for i in range(10): tk.Button( root, text=str(i)).grid( column=3 if i % 3 == 0 else (1 if i % 3 == 1 else 2), row=4 if i <= 3 else (5 if i <= 6 else 6)) root.mainloop() ================================================ FILE: Chapter 01/gir.xbm ================================================ #define 1026234pirate_width 226 #define 1026234pirate_height 199 static char 1026234pirate_bits[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x05, 0x80, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x02, 0x40, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6A, 0x06, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x95, 0x5B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA8, 0x6A, 0xA4, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x54, 0x02, 0xA0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x80, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x80, 0x00, 0x58, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0xC0, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0xA0, 0x01, 0x54, 0x05, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB8, 0xC0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x06, 0x40, 0x80, 0xA9, 0x1A, 0xA0, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x05, 0x60, 0x40, 0x5A, 0xF4, 0x60, 0x80, 0x35, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5C, 0x80, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x50, 0xB0, 0x03, 0x00, 0x81, 0x81, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x03, 0x00, 0x7E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x28, 0x00, 0x00, 0x42, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x03, 0x60, 0xB3, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x54, 0x00, 0x00, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x08, 0x02, 0x90, 0xDE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x28, 0x40, 0x02, 0x86, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x5A, 0x05, 0xD8, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x14, 0xA8, 0x09, 0x02, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xA5, 0x1A, 0x7C, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x06, 0x18, 0x18, 0x04, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB0, 0xDA, 0x55, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1A, 0x04, 0x08, 0x10, 0x0A, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x58, 0x27, 0xAA, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x05, 0x05, 0x01, 0x04, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA0, 0xCF, 0x97, 0x95, 0x35, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x0A, 0xA2, 0x32, 0x08, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x79, 0xB5, 0x6A, 0xAA, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x0A, 0x43, 0x21, 0x0E, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xEE, 0x75, 0xAD, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD0, 0x06, 0x0A, 0x05, 0xB0, 0x12, 0x80, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA0, 0x33, 0x97, 0x56, 0x3D, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x0F, 0x1A, 0x86, 0x61, 0x10, 0x0E, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9E, 0xDC, 0xF6, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x09, 0x91, 0x0A, 0x02, 0x82, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE8, 0x77, 0x0B, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x02, 0xA0, 0x45, 0x81, 0x01, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD0, 0xAF, 0x1D, 0x0A, 0x82, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x0E, 0x06, 0x80, 0x02, 0x00, 0x6A, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA0, 0x5F, 0x02, 0x04, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x12, 0x08, 0x60, 0x80, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD8, 0x5F, 0x01, 0x00, 0x86, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x0C, 0x50, 0x57, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBC, 0xAF, 0x02, 0x18, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x16, 0xA0, 0x05, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBE, 0x55, 0x42, 0x18, 0xA0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x18, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5E, 0x2A, 0xE2, 0x60, 0x44, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0xA4, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAF, 0x15, 0xC4, 0x54, 0x95, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0xA0, 0x58, 0x01, 0x00, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x54, 0x0A, 0x02, 0xA8, 0x6A, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xB0, 0xA0, 0x05, 0x80, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x95, 0x15, 0x2C, 0x6B, 0xA5, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x40, 0xC0, 0x02, 0x50, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFA, 0x0A, 0xD0, 0x95, 0x7A, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0xE9, 0xAA, 0x02, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xFD, 0x05, 0x68, 0x2A, 0x75, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x05, 0x00, 0x00, 0x56, 0x55, 0x00, 0x2A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0xFF, 0x03, 0x90, 0xD5, 0xFB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA0, 0x26, 0x00, 0x58, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xFF, 0x05, 0x50, 0x9A, 0x57, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0xFF, 0x01, 0xA0, 0xD5, 0xAB, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA0, 0xFF, 0x01, 0x60, 0xEA, 0xE7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0xFE, 0x02, 0x80, 0x95, 0x58, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x7D, 0x01, 0x80, 0x5A, 0x2A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x18, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA8, 0xDA, 0x00, 0x00, 0xA5, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x7D, 0x00, 0x00, 0x5A, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x1C, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB0, 0xFA, 0x00, 0x00, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFA, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x5A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFA, 0x25, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFA, 0x2B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0xFA, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xFE, 0x3D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0xFB, 0x1B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xE5, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xAB, 0x1D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x57, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xAB, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD0, 0xD7, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA0, 0xDB, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0xE5, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0xD5, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA4, 0xEA, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x97, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x69, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xAB, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xFF, 0x55, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xFF, 0xAB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xFF, 0xD5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x6B, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA0, 0xFF, 0x97, 0x00, 0x00, 0x00, 0x60, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0xFF, 0x37, 0x00, 0x00, 0x00, 0x50, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA8, 0xFE, 0x7B, 0x00, 0x00, 0x00, 0x20, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0xFA, 0x7D, 0x00, 0x00, 0x00, 0x54, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xDE, 0x55, 0x7E, 0x00, 0x00, 0x80, 0xAA, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE5, 0xA9, 0x5D, 0x00, 0x00, 0x1A, 0x54, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFA, 0x57, 0x12, 0x00, 0x00, 0xCE, 0xA8, 0x1A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0xFA, 0xA9, 0x2A, 0x00, 0x80, 0x0B, 0x28, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xFD, 0x97, 0x17, 0x00, 0x40, 0x06, 0x60, 0x56, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0xFE, 0xFB, 0x2B, 0x00, 0x00, 0x00, 0x64, 0x5C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA0, 0xF5, 0xF5, 0x17, 0x00, 0x00, 0x1C, 0x04, 0xFA, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x5A, 0xFE, 0x1F, 0x00, 0x00, 0x6A, 0x09, 0xE4, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0xA6, 0xA9, 0xFD, 0x17, 0x00, 0x00, 0xB4, 0x06, 0xFB, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x49, 0xFA, 0x55, 0xFE, 0x0B, 0x00, 0x00, 0x7A, 0x5B, 0x6A, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xDF, 0xB4, 0xFE, 0xAB, 0xFE, 0x17, 0x00, 0x00, 0x94, 0xA5, 0xB9, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0xFF, 0x5F, 0x6B, 0xFF, 0x57, 0xFC, 0x15, 0x00, 0x00, 0xAC, 0x55, 0xDA, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xDA, 0xFF, 0xE7, 0x97, 0xFF, 0xAF, 0xFF, 0x0B, 0x00, 0x00, 0x54, 0xAB, 0x60, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xA5, 0xFF, 0xDB, 0x5F, 0xFF, 0x57, 0xFE, 0x05, 0x00, 0x00, 0xA8, 0x36, 0xA0, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x5A, 0x2D, 0xC5, 0xAF, 0xFF, 0xAF, 0xBB, 0x0A, 0x00, 0x00, 0x50, 0x09, 0xE0, 0x2E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xD5, 0xAA, 0xDA, 0xA7, 0xFF, 0x67, 0xAA, 0x05, 0x00, 0x00, 0x40, 0x02, 0x40, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0xAA, 0x55, 0x67, 0xDA, 0xFF, 0xFB, 0x55, 0x06, 0x00, 0x00, 0x00, 0x00, 0x80, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x7F, 0x55, 0xDA, 0xAF, 0x6D, 0xFF, 0xF9, 0xAA, 0x05, 0x00, 0x00, 0x00, 0x00, 0x80, 0xAD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xFF, 0xAA, 0xA5, 0x5F, 0x91, 0xFF, 0xF5, 0x55, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x56, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x5F, 0xAA, 0xAF, 0x27, 0xA9, 0xB6, 0x3A, 0xA9, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xBF, 0x55, 0x5F, 0xF9, 0x5F, 0x59, 0x95, 0x5E, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA0, 0xA0, 0x5F, 0xEA, 0xFF, 0xF6, 0x9F, 0xAA, 0xF8, 0xBF, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF6, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x55, 0xAB, 0xD5, 0xFF, 0xFD, 0x7F, 0x55, 0xFF, 0xFF, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFA, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x88, 0xA2, 0xDA, 0xD5, 0xFF, 0xFB, 0xFF, 0x9A, 0xFF, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x54, 0xF5, 0xEB, 0xFF, 0xF7, 0xFF, 0xE5, 0xFF, 0xBF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xA8, 0xFA, 0xE5, 0xFF, 0xFF, 0xFF, 0xD7, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x74, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x50, 0xB5, 0xFB, 0xFF, 0xE7, 0xFF, 0xEB, 0xFF, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA8, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x90, 0x55, 0xF6, 0xFF, 0x5F, 0xFF, 0xD7, 0xFF, 0x5F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x78, 0xAA, 0xE9, 0xFF, 0xA3, 0xFF, 0xEB, 0xFF, 0x2F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB8, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0xA8, 0x3F, 0xE7, 0xFF, 0x5B, 0xFE, 0x5B, 0xFF, 0x2F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x64, 0xFF, 0xF8, 0xFF, 0x6F, 0xF9, 0xA5, 0xFE, 0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB0, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0xE8, 0xFF, 0xAA, 0xFF, 0xAF, 0x5A, 0x5D, 0xDB, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD0, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0xD4, 0xFF, 0xD5, 0xFF, 0x57, 0xA5, 0xAA, 0xA5, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0xE4, 0xFF, 0xBA, 0xFF, 0xAB, 0x6E, 0x95, 0x9B, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x00, 0xE8, 0xFF, 0x75, 0xFE, 0x57, 0xBF, 0xEA, 0x57, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xAF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0x00, 0xE8, 0xFF, 0xFB, 0x69, 0xD1, 0x7F, 0xE9, 0x57, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xAB, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x06, 0x00, 0xF8, 0xFF, 0xF9, 0x97, 0xED, 0xFF, 0xF6, 0xAF, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x5E, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x01, 0x00, 0xD0, 0xFF, 0xFD, 0x5A, 0xFA, 0xFF, 0xEB, 0x57, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0xAD, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB0, 0x00, 0x00, 0xA8, 0xFF, 0x53, 0xA5, 0xE5, 0x7F, 0xD6, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xDE, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x50, 0xFA, 0x96, 0xDA, 0xFA, 0xBF, 0xAA, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0xE4, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0xA0, 0x55, 0xE9, 0x5F, 0xF5, 0x7F, 0xD5, 0x1B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xDF, 0x3B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB0, 0x05, 0x00, 0x00, 0x50, 0xA6, 0xDA, 0xBF, 0xFA, 0xBF, 0xEA, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0xDF, 0x2B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x02, 0x00, 0x00, 0xA0, 0xFD, 0xF5, 0x5F, 0xF5, 0x7F, 0xF5, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xBF, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD4, 0x01, 0x00, 0x00, 0x00, 0xFF, 0xF9, 0xBF, 0xDC, 0x1F, 0xFA, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xBF, 0xFA, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x02, 0x00, 0x00, 0x80, 0xFE, 0xFB, 0x7F, 0xBD, 0xEF, 0xF5, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7E, 0xF9, 0x75, 0xFE, 0x01, 0x00, 0x00, 0x00, 0x00, 0xB8, 0x01, 0x00, 0x00, 0x00, 0xFD, 0xFE, 0xBF, 0xBF, 0x57, 0x7A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xFE, 0xFD, 0x99, 0xFF, 0x01, 0x00, 0x00, 0x00, 0x00, 0x50, 0x02, 0x00, 0x00, 0x00, 0xFC, 0xFD, 0x5F, 0x7F, 0x69, 0x1D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFC, 0xFF, 0xF6, 0x5E, 0x00, 0x00, 0x40, 0x00, 0xD0, 0x02, 0x00, 0x00, 0x00, 0x60, 0xFA, 0x5F, 0x7F, 0x5A, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAE, 0xFB, 0x77, 0xA5, 0xE5, 0x01, 0x00, 0x00, 0x00, 0xA8, 0x01, 0x00, 0x00, 0x00, 0x80, 0xE5, 0xAF, 0xBE, 0xE5, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD9, 0xFD, 0xA8, 0x9F, 0xFA, 0x03, 0x00, 0x40, 0x00, 0xA0, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x99, 0x67, 0x5D, 0x88, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA4, 0xBB, 0x55, 0x5E, 0xED, 0x07, 0x00, 0x20, 0x00, 0xD8, 0x00, 0x00, 0x00, 0x00, 0xA0, 0x11, 0x04, 0x00, 0xBC, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5A, 0x75, 0xAD, 0xAA, 0xD1, 0x07, 0x00, 0x60, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x15, 0x00, 0x00, 0x9C, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFA, 0xBB, 0xDE, 0x76, 0xAA, 0xC7, 0x02, 0x60, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x80, 0x1C, 0x00, 0x00, 0x5C, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x4F, 0x7F, 0xFD, 0x67, 0x2A, 0x04, 0x40, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x80, 0x04, 0x00, 0x00, 0x6E, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x9F, 0x7E, 0xFC, 0xFD, 0x05, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x1C, 0x00, 0x00, 0xE4, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x7F, 0xFF, 0xFF, 0xD3, 0x1A, 0x04, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x28, 0x00, 0x00, 0xE4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xAF, 0xFE, 0xFE, 0x9B, 0x29, 0x18, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x18, 0x00, 0x00, 0xD8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x5F, 0x79, 0xFE, 0xE7, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x01, 0x00, 0x00, 0x42, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA0, 0x65, 0xA2, 0xF9, 0xFB, 0x2B, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA0, 0x1C, 0x00, 0x00, 0xAE, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBB, 0x5D, 0xF6, 0xFD, 0x17, 0x10, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x6E, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7E, 0x7E, 0xAB, 0xFF, 0x15, 0x20, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x30, 0x00, 0x00, 0x9E, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBD, 0xFF, 0x55, 0xF7, 0x1B, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x38, 0x00, 0x00, 0x9E, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, 0xFE, 0xAA, 0xE6, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA0, 0x19, 0x00, 0x00, 0xEE, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA0, 0xFE, 0x55, 0x9D, 0x1A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x08, 0x00, 0x00, 0xE6, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0xFD, 0xEB, 0xD7, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x11, 0x00, 0x00, 0xD8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xFE, 0xF4, 0xCB, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x2D, 0x00, 0x00, 0xA6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x7E, 0xFB, 0xFF, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x10, 0x00, 0x00, 0xD8, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE5, 0xF7, 0xEF, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x29, 0x00, 0x00, 0x5C, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xCD, 0xF7, 0xF7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x15, 0x00, 0x00, 0xD8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x13, 0xEB, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x29, 0x00, 0x00, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0x80, 0x35, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x1C, 0x00, 0x00, 0xFC, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x1D, 0x00, 0x00, 0xE8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x19, 0x00, 0x00, 0xD4, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x1A, 0x00, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA0, 0x28, 0x00, 0x00, 0x6C, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0x00, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x15, 0x00, 0x00, 0x94, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x09, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA0, 0x21, 0x00, 0x00, 0x6A, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x62, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x3F, 0x00, 0x00, 0xDC, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x29, 0x00, 0x00, 0x6C, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x1B, 0x00, 0x00, 0x94, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x0D, 0x00, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x27, 0x00, 0x00, 0xA4, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1B, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x78, 0x00, 0x00, 0xD8, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x77, 0x00, 0x00, 0xB4, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x09, 0x00, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x38, 0x00, 0x00, 0xF8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x3B, 0x00, 0x00, 0x78, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x07, 0x00, 0x2E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x20, 0x00, 0x00, 0xA4, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x0E, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x19, 0x00, 0x00, 0x98, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x0A, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x29, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x0D, 0x00, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x68, 0x00, 0x00, 0xE8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x09, 0x00, 0x2B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x1A, 0x00, 0x00, 0x18, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x19, 0x00, 0x00, 0xA0, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x0B, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x66, 0x00, 0x00, 0x98, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x3A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xE2, 0x00, 0x00, 0xA8, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x09, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xDD, 0x01, 0x00, 0xD0, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x0C, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xAA, 0x02, 0x00, 0xE8, 0x6B, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x01, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x56, 0x09, 0x00, 0xF0, 0xB7, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE6, 0x06, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF9, 0x95, 0x00, 0xF0, 0xAF, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x03, 0x80, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFA, 0x7B, 0x01, 0xA0, 0x5F, 0x2A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xE7, 0x05, 0xC0, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF5, 0x7B, 0x01, 0x90, 0xEF, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xBD, 0x02, 0xE8, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFA, 0xBD, 0x03, 0x40, 0xD9, 0x1B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x51, 0x03, 0x58, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD4, 0xEC, 0x03, 0x40, 0xDB, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB8, 0x00, 0xBA, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x9B, 0x03, 0x00, 0xA7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF6, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x67, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; ================================================ FILE: Chapter 01/optionDB.txt ================================================ *Button*borderwidth:3 *Button*relief: raised *Button*width: 3 *Button*height: 1 *Button*pady:3 ================================================ FILE: Chapter 01/readme.txt ================================================ ==================================================================== Code Readme Tkinter GUI Application Development Blueprints Chapter 1: List of Programs ==================================================================== 1.01 : Your first GUI application - the top level window 1.02 : Adding some widgets 1.03 : A demonstration of all core tkinter widgets 1.04 : A demonstration of some of pack() options 1.05 : Where to use pack() options 1.06 : Simple example of grid Geometry Manager 1.07 : A demonstration of common grid() options 1.08 : A demonstration of common place() options 1.09 : A demonstration of event binding with the bind() method 1.10 : A demonstration of all Widget Binding 1.11 : A demonstration of tkinter Variable Class IntVar, StringVar & BooleanVar 1.12 : A demonstration of tkinter styling File Extension: .py Requires Python 3.4.0 Installation to run. @author: Bhaskar Chauhdary ================================================ FILE: Chapter 01/textcontent.txt ================================================ I am a text-widget. Tkinter includes 21 core widgets. This program demonstrates all of them. See if you can identify all of them 1) Toplevel Widget** 2) Button Widget 3) Canvas Widget 4) Checkbutton Widget 5) Entry Widget 6) Frame Widget 7) Label Widget 8) LabelFrame Widget 9) Listbox Widget 10) Menu Widget 11) Menubutton Widget 12) Message Widget 13) OptionMenu Widget 14) PanedWindow Widget 15) Radiobutton Widget 16) Scale Widget 17) Scrollbar Widget 18) Spinbox Widget 19) Text Widget 20) Bitmap Class Widget 21) Image Class Widget ** (hint - the window which houses all other widgets) ================================================ FILE: Chapter 02/2.01.py ================================================ """ Code illustration: 2.01 Text Editor Code Step 1: Adding Top-level Step 2: Add Menubuttons @Tkinter GUI Application Development Blueprints """ from tkinter import Tk, Menu PROGRAM_NAME = "Footprint Editor" root = Tk() root.geometry('350x350') root.title(PROGRAM_NAME) menu_bar = Menu(root) # menu begins file_menu = Menu(menu_bar, tearoff=0) # all file menu-items will be added here next menu_bar.add_cascade(label='File', menu=file_menu) edit_menu = Menu(menu_bar, tearoff=0) menu_bar.add_cascade(label='Edit', menu=edit_menu) view_menu = Menu(menu_bar, tearoff=0) menu_bar.add_cascade(label='View', menu=view_menu) about_menu = Menu(menu_bar, tearoff=0) menu_bar.add_cascade(label='About', menu=about_menu) root.config(menu=menu_bar) # menu ends root.mainloop() ================================================ FILE: Chapter 02/2.02.py ================================================ """ Code illustration: 2.02 Text Editor Code Step 3: Adding Menu items to each menu Step 4: Adding labels to hold shortcut toolbar and line number @Tkinter GUI Application Development Blueprints """ from tkinter import Tk, PhotoImage, Menu, Frame, Text, Scrollbar PROGRAM_NAME = "Footprint Editor" root = Tk() root.geometry('350x350') root.title(PROGRAM_NAME) # getting icons ready for compound menu new_file_icon = PhotoImage(file='icons/new_file.gif') open_file_icon = PhotoImage(file='icons/open_file.gif') save_file_icon = PhotoImage(file='icons/save.gif') cut_icon = PhotoImage(file='icons/cut.gif') copy_icon = PhotoImage(file='icons/copy.gif') paste_icon = PhotoImage(file='icons/paste.gif') undo_icon = PhotoImage(file='icons/undo.gif') redo_icon = PhotoImage(file='icons/redo.gif') menu_bar = Menu(root) # menu begins file_menu = Menu(menu_bar, tearoff=0) file_menu.add_command(label='New', accelerator='Ctrl+N', compound='left', image=new_file_icon, underline=0) file_menu.add_command(label='Open', accelerator='Ctrl+O', compound='left', image=open_file_icon, underline=0) file_menu.add_command(label='Save', accelerator='Ctrl+S', compound='left', image=save_file_icon, underline=0) file_menu.add_command(label='Save as', accelerator='Shift+Ctrl+S') file_menu.add_separator() file_menu.add_command(label='Exit', accelerator='Alt+F4') menu_bar.add_cascade(label='File', menu=file_menu) edit_menu = Menu(menu_bar, tearoff=0) edit_menu.add_command(label='Undo', accelerator='Ctrl+Z', compound='left', image=undo_icon) edit_menu.add_command(label='Redo', accelerator='Ctrl+Y', compound='left', image=redo_icon) edit_menu.add_separator() edit_menu.add_command(label='Cut', accelerator='Ctrl+X', compound='left', image=cut_icon) edit_menu.add_command(label='Copy', accelerator='Ctrl+C', compound='left', image=copy_icon) edit_menu.add_command(label='Paste', accelerator='Ctrl+V', compound='left', image=paste_icon) edit_menu.add_separator() edit_menu.add_command(label='Find', underline=0, accelerator='Ctrl+F') edit_menu.add_separator() edit_menu.add_command(label='Select All', underline=7, accelerator='Ctrl+A') menu_bar.add_cascade(label='Edit', menu=edit_menu) view_menu = Menu(menu_bar, tearoff=0) # View menu-items displays some variations # so we tackle view menu code to be entered here in a later section menu_bar.add_cascade(label='View', menu=view_menu) about_menu = Menu(menu_bar, tearoff=0) about_menu.add_command(label='About') about_menu.add_command(label='Help') menu_bar.add_cascade(label='About', menu=about_menu) root.config(menu=menu_bar) # menu ends # add top shortcut bar & left line number bar shortcut_bar = Frame(root, height=25, background='light sea green') shortcut_bar.pack(expand='no', fill='x') line_number_bar = Text(root, width=4, padx=3, takefocus=0, border=0, background='khaki', state='disabled', wrap='none') line_number_bar.pack(side='left', fill='y') # add the main content Text widget and Scrollbar widget content_text = Text(root, wrap='word') content_text.pack(expand='yes', fill='both') scroll_bar = Scrollbar(content_text) content_text.configure(yscrollcommand=scroll_bar.set) scroll_bar.config(command=content_text.yview) scroll_bar.pack(side='right', fill='y') root.mainloop() ================================================ FILE: Chapter 02/2.03.py ================================================ """ Code illustration: 2.03 Adding View Menu Items to demonstrate other Types of Menu Items 1. Checkbutton menu-item 2. Radiobutton menu-item 3. Cascade menu-item @Tkinter GUI Application Development Blueprints """ from tkinter import Tk, PhotoImage, Menu, Frame, Text, Scrollbar, IntVar, \ StringVar PROGRAM_NAME = "Footprint Editor" root = Tk() root.geometry('350x350') root.title(PROGRAM_NAME) new_file_icon = PhotoImage(file='icons/new_file.gif') open_file_icon = PhotoImage(file='icons/open_file.gif') save_file_icon = PhotoImage(file='icons/save.gif') cut_icon = PhotoImage(file='icons/cut.gif') copy_icon = PhotoImage(file='icons/copy.gif') paste_icon = PhotoImage(file='icons/paste.gif') undo_icon = PhotoImage(file='icons/undo.gif') redo_icon = PhotoImage(file='icons/redo.gif') menu_bar = Menu(root) file_menu = Menu(menu_bar, tearoff=0) file_menu.add_command(label='New', accelerator='Ctrl+N', compound='left', image=new_file_icon, underline=0) file_menu.add_command(label='Open', accelerator='Ctrl+O', compound='left', image=open_file_icon, underline=0) file_menu.add_command(label='Save', accelerator='Ctrl+S', compound='left', image=save_file_icon, underline=0) file_menu.add_command(label='Save as', accelerator='Shift+Ctrl+S') file_menu.add_separator() file_menu.add_command(label='Exit', accelerator='Alt+F4') menu_bar.add_cascade(label='File', menu=file_menu) edit_menu = Menu(menu_bar, tearoff=0) edit_menu.add_command(label='Undo', accelerator='Ctrl+Z', compound='left', image=undo_icon) edit_menu.add_command(label='Redo', accelerator='Ctrl+Y', compound='left', image=redo_icon) edit_menu.add_separator() edit_menu.add_command(label='Cut', accelerator='Ctrl+X', compound='left', image=cut_icon) edit_menu.add_command(label='Copy', accelerator='Ctrl+C', compound='left', image=copy_icon) edit_menu.add_command(label='Paste', accelerator='Ctrl+V', compound='left', image=paste_icon) edit_menu.add_separator() edit_menu.add_command(label='Find', underline=0, accelerator='Ctrl+F') edit_menu.add_separator() edit_menu.add_command(label='Select All', underline=7, accelerator='Ctrl+A') menu_bar.add_cascade(label='Edit', menu=edit_menu) ## # Implementing Checkbutton, Radiobutton and Cascade menu-items under View Menu in this iteration ## view_menu = Menu(menu_bar, tearoff=0) show_line_number = IntVar() show_line_number.set(1) view_menu.add_checkbutton(label='Show Line Number', variable=show_line_number) show_cursor_info = IntVar() show_cursor_info.set(1) view_menu.add_checkbutton( label='Show Cursor Location at Bottom', variable=show_cursor_info) highlight_line = IntVar() view_menu.add_checkbutton(label='Highlight Current Line', onvalue=1, offvalue=0, variable=highlight_line) themes_menu = Menu(menu_bar, tearoff=0) view_menu.add_cascade(label='Themes', menu=themes_menu) """ color scheme is defined with dictionary elements like - theme_name : foreground_color.background_color """ color_schemes = { 'Default': '#000000.#FFFFFF', 'Greygarious': '#83406A.#D1D4D1', 'Aquamarine': '#5B8340.#D1E7E0', 'Bold Beige': '#4B4620.#FFF0E1', 'Cobalt Blue': '#ffffBB.#3333aa', 'Olive Green': '#D1E7E0.#5B8340', 'Night Mode': '#FFFFFF.#000000', } theme_choice = StringVar() theme_choice.set('Default') for k in sorted(color_schemes): themes_menu.add_radiobutton(label=k, variable=theme_choice) menu_bar.add_cascade(label='View', menu=view_menu) # # End of View menu implementation in this iteration # about_menu = Menu(menu_bar, tearoff=0) about_menu.add_command(label='About') about_menu.add_command(label='Help') menu_bar.add_cascade(label='About', menu=about_menu) root.config(menu=menu_bar) shortcut_bar = Frame(root, height=25, background='light sea green') shortcut_bar.pack(expand='no', fill='x') line_number_bar = Text(root, width=4, padx=3, takefocus=0, border=0, background='khaki', state='disabled', wrap='none') line_number_bar.pack(side='left', fill='y') content_text = Text(root, wrap='word') content_text.pack(expand='yes', fill='both') scroll_bar = Scrollbar(content_text) content_text.configure(yscrollcommand=scroll_bar.set) scroll_bar.config(command=content_text.yview) scroll_bar.pack(side='right', fill='y') root.mainloop() ================================================ FILE: Chapter 02/2.04.py ================================================ """ Code illustration: 2.04 leveraging Text widget built-in options for 1. Cut 2. Copy 3. Paste 4. Undo 5. Redo @Tkinter GUI Application Development Blueprints """ from tkinter import Tk, PhotoImage, Menu, Frame, Text, Scrollbar, IntVar, \ StringVar PROGRAM_NAME = "Footprint Editor" root = Tk() root.geometry('350x350') root.title(PROGRAM_NAME) # Adding Text Widget Built-in Functionality def cut(): content_text.event_generate("<>") return "break" def copy(): content_text.event_generate("<>") return "break" def paste(): content_text.event_generate("<>") return "break" def undo(): content_text.event_generate("<>") return "break" def redo(event=None): content_text.event_generate("<>") return 'break' # end of this iteration new_file_icon = PhotoImage(file='icons/new_file.gif') open_file_icon = PhotoImage(file='icons/open_file.gif') save_file_icon = PhotoImage(file='icons/save.gif') cut_icon = PhotoImage(file='icons/cut.gif') copy_icon = PhotoImage(file='icons/copy.gif') paste_icon = PhotoImage(file='icons/paste.gif') undo_icon = PhotoImage(file='icons/undo.gif') redo_icon = PhotoImage(file='icons/redo.gif') menu_bar = Menu(root) file_menu = Menu(menu_bar, tearoff=0) file_menu.add_command(label='New', accelerator='Ctrl+N', compound='left', image=new_file_icon, underline=0) file_menu.add_command(label='Open', accelerator='Ctrl+O', compound='left', image=open_file_icon, underline=0) file_menu.add_command(label='Save', accelerator='Ctrl+S', compound='left', image=save_file_icon, underline=0) file_menu.add_command(label='Save as', accelerator='Shift+Ctrl+S') file_menu.add_separator() file_menu.add_command(label='Exit', accelerator='Alt+F4') menu_bar.add_cascade(label='File', menu=file_menu) edit_menu = Menu(menu_bar, tearoff=0) edit_menu.add_command(label='Undo', accelerator='Ctrl+Z', compound='left', image=undo_icon, command=undo) edit_menu.add_command(label='Redo', accelerator='Ctrl+Y', compound='left', image=redo_icon, command=redo) edit_menu.add_separator() edit_menu.add_command(label='Cut', accelerator='Ctrl+X', compound='left', image=cut_icon, command=cut) edit_menu.add_command(label='Copy', accelerator='Ctrl+C', compound='left', image=copy_icon, command=copy) edit_menu.add_command(label='Paste', accelerator='Ctrl+V', compound='left', image=paste_icon, command=paste) edit_menu.add_separator() edit_menu.add_command(label='Find', underline=0, accelerator='Ctrl+F') edit_menu.add_separator() edit_menu.add_command(label='Select All', underline=7, accelerator='Ctrl+A') menu_bar.add_cascade(label='Edit', menu=edit_menu) view_menu = Menu(menu_bar, tearoff=0) show_line_number = IntVar() show_line_number.set(1) view_menu.add_checkbutton(label='Show Line Number', variable=show_line_number) show_cursor_info = IntVar() show_cursor_info.set(1) view_menu.add_checkbutton( label='Show Cursor Location at Bottom', variable=show_cursor_info) highlight_line = IntVar() view_menu.add_checkbutton(label='Highlight Current Line', onvalue=1, offvalue=0, variable=highlight_line) themes_menu = Menu(menu_bar, tearoff=0) view_menu.add_cascade(label='Themes', menu=themes_menu) color_schemes = { 'Default': '#000000.#FFFFFF', 'Greygarious': '#83406A.#D1D4D1', 'Aquamarine': '#5B8340.#D1E7E0', 'Bold Beige': '#4B4620.#FFF0E1', 'Cobalt Blue': '#ffffBB.#3333aa', 'Olive Green': '#D1E7E0.#5B8340', 'Night Mode': '#FFFFFF.#000000', } theme_choice = StringVar() theme_choice.set('Default') for k in sorted(color_schemes): themes_menu.add_radiobutton(label=k, variable=theme_choice) menu_bar.add_cascade(label='View', menu=view_menu) about_menu = Menu(menu_bar, tearoff=0) about_menu.add_command(label='About') about_menu.add_command(label='Help') menu_bar.add_cascade(label='About', menu=about_menu) root.config(menu=menu_bar) shortcut_bar = Frame(root, height=25, background='light sea green') shortcut_bar.pack(expand='no', fill='x') line_number_bar = Text(root, width=4, padx=3, takefocus=0, border=0, background='khaki', state='disabled', wrap='none') line_number_bar.pack(side='left', fill='y') content_text = Text(root, wrap='word', undo=1) content_text.pack(expand='yes', fill='both') scroll_bar = Scrollbar(content_text) content_text.configure(yscrollcommand=scroll_bar.set) scroll_bar.config(command=content_text.yview) scroll_bar.pack(side='right', fill='y') # handling redo quirk content_text.bind('', redo) content_text.bind('', redo) root.mainloop() ================================================ FILE: Chapter 02/2.05.py ================================================ """ Code illustration: 2.05 Text Widget Indexing and Tagging explained using implementation of: 1. select_all 2. find_text @Tkinter GUI Application Development Blueprints """ from tkinter import Tk, PhotoImage, Menu, Frame, Text, Scrollbar, IntVar, \ StringVar PROGRAM_NAME = "Footprint Editor" root = Tk() root.geometry('350x350') root.title(PROGRAM_NAME) # Implementing select_all and find_text functions def select_all(event=None): content_text.tag_add('sel', '1.0', 'end') return "break" def find_text(event=None): search_toplevel = Tk.Toplevel(root) search_toplevel.title('Find Text') search_toplevel.transient(root) search_toplevel.resizable(False, False) Label(search_toplevel, text="Find All:").grid(row=0, column=0, sticky='e') search_entry_widget = Entry( search_toplevel, width=25) search_entry_widget.grid(row=0, column=1, padx=2, pady=2, sticky='we') search_entry_widget.focus_set() ignore_case_value = IntVar() Checkbutton(search_toplevel, text='Ignore Case', variable=ignore_case_value).grid( row=1, column=1, sticky='e', padx=2, pady=2) Button(search_toplevel, text="Find All", underline=0, command=lambda: search_output( search_entry_widget.get(), ignore_case_value.get(), content_text, search_toplevel, search_entry_widget) ).grid(row=0, column=2, sticky='e' + 'w', padx=2, pady=2) def close_search_window(): content_text.tag_remove('match', '1.0', END) search_toplevel.destroy() search_toplevel.protocol('WM_DELETE_WINDOW', close_search_window) return "break" def search_output(needle, if_ignore_case, content_text, search_toplevel, search_box): content_text.tag_remove('match', '1.0', END) matches_found = 0 if needle: start_pos = '1.0' while True: start_pos = content_text.search(needle, start_pos, nocase=if_ignore_case, stopindex=END) if not start_pos: break end_pos = '{}+{}c'.format(start_pos, len(needle)) content_text.tag_add('match', start_pos, end_pos) matches_found += 1 start_pos = end_pos content_text.tag_config( 'match', foreground='red', background='yellow') search_box.focus_set() search_toplevel.title('{} matches found'.format(matches_found)) def cut(): content_text.event_generate("<>") return "break" def copy(): content_text.event_generate("<>") return "break" def paste(): content_text.event_generate("<>") return "break" def undo(): content_text.event_generate("<>") return "break" def redo(event=None): content_text.event_generate("<>") return 'break' new_file_icon = PhotoImage(file='icons/new_file.gif') open_file_icon = PhotoImage(file='icons/open_file.gif') save_file_icon = PhotoImage(file='icons/save.gif') cut_icon = PhotoImage(file='icons/cut.gif') copy_icon = PhotoImage(file='icons/copy.gif') paste_icon = PhotoImage(file='icons/paste.gif') undo_icon = PhotoImage(file='icons/undo.gif') redo_icon = PhotoImage(file='icons/redo.gif') menu_bar = Menu(root) file_menu = Menu(menu_bar, tearoff=0) file_menu.add_command(label='New', accelerator='Ctrl+N', compound='left', image=new_file_icon, underline=0) file_menu.add_command(label='Open', accelerator='Ctrl+O', compound='left', image=open_file_icon, underline=0) file_menu.add_command(label='Save', accelerator='Ctrl+S', compound='left', image=save_file_icon, underline=0) file_menu.add_command(label='Save as', accelerator='Shift+Ctrl+S') file_menu.add_separator() file_menu.add_command(label='Exit', accelerator='Alt+F4') menu_bar.add_cascade(label='File', menu=file_menu) edit_menu = Menu(menu_bar, tearoff=0) edit_menu.add_command(label='Undo', accelerator='Ctrl+Z', compound='left', image=undo_icon, command=undo) edit_menu.add_command(label='Redo', accelerator='Ctrl+Y', compound='left', image=redo_icon, command=redo) edit_menu.add_separator() edit_menu.add_command(label='Cut', accelerator='Ctrl+X', compound='left', image=cut_icon, command=cut) edit_menu.add_command(label='Copy', accelerator='Ctrl+C', compound='left', image=copy_icon, command=copy) edit_menu.add_command(label='Paste', accelerator='Ctrl+V', compound='left', image=paste_icon, command=paste) edit_menu.add_separator() edit_menu.add_command(label='Find', underline=0, accelerator='Ctrl+F', command=find_text) edit_menu.add_separator() edit_menu.add_command(label='Select All', underline=7, accelerator='Ctrl+A', command=select_all) menu_bar.add_cascade(label='Edit', menu=edit_menu) view_menu = Menu(menu_bar, tearoff=0) show_line_number = IntVar() show_line_number.set(1) view_menu.add_checkbutton(label='Show Line Number', variable=show_line_number) show_cursor_info = IntVar() show_cursor_info.set(1) view_menu.add_checkbutton( label='Show Cursor Location at Bottom', variable=show_cursor_info) highlight_line = IntVar() view_menu.add_checkbutton(label='Highlight Current Line', onvalue=1, offvalue=0, variable=highlight_line) themes_menu = Menu(menu_bar, tearoff=0) view_menu.add_cascade(label='Themes', menu=themes_menu) color_schemes = { 'Default': '#000000.#FFFFFF', 'Greygarious': '#83406A.#D1D4D1', 'Aquamarine': '#5B8340.#D1E7E0', 'Bold Beige': '#4B4620.#FFF0E1', 'Cobalt Blue': '#ffffBB.#3333aa', 'Olive Green': '#D1E7E0.#5B8340', 'Night Mode': '#FFFFFF.#000000', } theme_choice = StringVar() theme_choice.set('Default') for k in sorted(color_schemes): themes_menu.add_radiobutton(label=k, variable=theme_choice) menu_bar.add_cascade(label='View', menu=view_menu) about_menu = Menu(menu_bar, tearoff=0) about_menu.add_command(label='About') about_menu.add_command(label='Help') menu_bar.add_cascade(label='About', menu=about_menu) root.config(menu=menu_bar) shortcut_bar = Frame(root, height=25, background='light sea green') shortcut_bar.pack(expand='no', fill='x') line_number_bar = Text(root, width=4, padx=3, takefocus=0, border=0, background='khaki', state='disabled', wrap='none') line_number_bar.pack(side='left', fill='y') content_text = Text(root, wrap='word', undo=1) content_text.pack(expand='yes', fill='both') scroll_bar = Scrollbar(content_text) content_text.configure(yscrollcommand=scroll_bar.set) scroll_bar.config(command=content_text.yview) scroll_bar.pack(side='right', fill='y') content_text.bind('', find_text) content_text.bind('', find_text) content_text.bind('', select_all) content_text.bind('', select_all) content_text.bind('', redo) content_text.bind('', redo) root.mainloop() ================================================ FILE: Chapter 02/2.06.py ================================================ """ Code illustration: 2.06 A demonstration of different types of top-level window @Tkinter GUI Application Development Blueprints """ from tkinter import Tk, Label, Toplevel root = Tk() # top level window root.title('Toplevel Window') root.geometry('300x300') Label(root, text='I am the Main Toplevel window\n All other windows here are my children').pack() # child toplevel child_toplevel = Toplevel(root) Label(child_toplevel, text='I am a child of root\n If i loose focus, I may hide below the top level, \n I am destroyed, if root is destroyed').pack() child_toplevel.geometry('400x100+300+300') # transient window transient_toplevel = Toplevel(root) Label(transient_toplevel, text='I am a transient window of root\n I always stay on top of my parent\n I get hidden if my parent window is minimized').pack() transient_toplevel.transient(root) # no window decoration no_window_decoration = Toplevel(root, bg='black') Label(no_window_decoration, text='I am a top-level with no window manager\n I cannot be resized or moved', bg='black', fg='white').pack() no_window_decoration.overrideredirect(1) no_window_decoration.geometry('250x100+700+500') root.mainloop() ================================================ FILE: Chapter 02/2.07.py ================================================ """ Code illustration: 2.07 Adding features: File > New File > Open File > Save File > Save As @Tkinter GUI Application Development Blueprints """ import os from tkinter import Tk, PhotoImage, Menu, Frame, Text, Scrollbar, IntVar, \ StringVar, END import tkinter.filedialog PROGRAM_NAME = "Footprint Editor" file_name = None root = Tk() root.geometry('350x350') root.title(PROGRAM_NAME) # new_file, open_file, save, save_as implementation def new_file(event=None): root.title("Untitled") global file_name file_name = None content_text.delete(1.0, END) def open_file(event=None): input_file_name = tkinter.filedialog.askopenfilename(defaultextension=".txt", filetypes=[("All Files", "*.*"), ("Text Documents", "*.txt")]) if input_file_name: global file_name file_name = input_file_name root.title('{} - {}'.format(os.path.basename(file_name), PROGRAM_NAME)) content_text.delete(1.0, END) with open(file_name) as _file: content_text.insert(1.0, _file.read()) def write_to_file(file_name): try: content = content_text.get(1.0, 'end') with open(file_name, 'w') as the_file: the_file.write(content) except IOError: pass # in actual we will show a error message box. # we discuss message boxes in the next section so ignored here. def save_as(event=None): input_file_name = tkinter.filedialog.asksaveasfilename(defaultextension=".txt", filetypes=[("All Files", "*.*"), ("Text Documents", "*.txt")]) if input_file_name: global file_name file_name = input_file_name write_to_file(file_name) root.title('{} - {}'.format(os.path.basename(file_name), PROGRAM_NAME)) return "break" def save(event=None): global file_name if not file_name: save_as() else: write_to_file(file_name) return "break" # End of iteration def select_all(event=None): content_text.tag_add('sel', '1.0', 'end') return "break" def find_text(event=None): search_toplevel = Toplevel(root) search_toplevel.title('Find Text') search_toplevel.transient(root) search_toplevel.resizable(False, False) Label(search_toplevel, text="Find All:").grid(row=0, column=0, sticky='e') search_entry_widget = Entry( search_toplevel, width=25) search_entry_widget.grid(row=0, column=1, padx=2, pady=2, sticky='we') search_entry_widget.focus_set() ignore_case_value = IntVar() Checkbutton(search_toplevel, text='Ignore Case', variable=ignore_case_value).grid( row=1, column=1, sticky='e', padx=2, pady=2) Button(search_toplevel, text="Find All", underline=0, command=lambda: search_output( search_entry_widget.get(), ignore_case_value.get(), content_text, search_toplevel, search_entry_widget) ).grid(row=0, column=2, sticky='e' + 'w', padx=2, pady=2) def close_search_window(): content_text.tag_remove('match', '1.0', END) search_toplevel.destroy() search_toplevel.protocol('WM_DELETE_WINDOW', close_search_window) return "break" def search_output(needle, if_ignore_case, content_text, search_toplevel, search_box): content_text.tag_remove('match', '1.0', END) matches_found = 0 if needle: start_pos = '1.0' while True: start_pos = content_text.search(needle, start_pos, nocase=if_ignore_case, stopindex=END) if not start_pos: break end_pos = '{}+{}c'.format(start_pos, len(needle)) content_text.tag_add('match', start_pos, end_pos) matches_found += 1 start_pos = end_pos content_text.tag_config( 'match', foreground='red', background='yellow') search_box.focus_set() search_toplevel.title('{} matches found'.format(matches_found)) def cut(): content_text.event_generate("<>") return "break" def copy(): content_text.event_generate("<>") return "break" def paste(): content_text.event_generate("<>") return "break" def undo(): content_text.event_generate("<>") return "break" def redo(event=None): content_text.event_generate("<>") return 'break' new_file_icon = PhotoImage(file='icons/new_file.gif') open_file_icon = PhotoImage(file='icons/open_file.gif') save_file_icon = PhotoImage(file='icons/save.gif') cut_icon = PhotoImage(file='icons/cut.gif') copy_icon = PhotoImage(file='icons/copy.gif') paste_icon = PhotoImage(file='icons/paste.gif') undo_icon = PhotoImage(file='icons/undo.gif') redo_icon = PhotoImage(file='icons/redo.gif') menu_bar = Menu(root) file_menu = Menu(menu_bar, tearoff=0) file_menu.add_command(label='New', accelerator='Ctrl+N', compound='left', image=new_file_icon, underline=0, command=new_file) file_menu.add_command(label='Open', accelerator='Ctrl+O', compound='left', image=open_file_icon, underline=0, command=open_file) file_menu.add_command(label='Save', accelerator='Ctrl+S', compound='left', image=save_file_icon, underline=0, command=save) file_menu.add_command( label='Save as', accelerator='Shift+Ctrl+S', command=save_as) file_menu.add_separator() file_menu.add_command(label='Exit', accelerator='Alt+F4') menu_bar.add_cascade(label='File', menu=file_menu) edit_menu = Menu(menu_bar, tearoff=0) edit_menu.add_command(label='Undo', accelerator='Ctrl+Z', compound='left', image=undo_icon, command=undo) edit_menu.add_command(label='Redo', accelerator='Ctrl+Y', compound='left', image=redo_icon, command=redo) edit_menu.add_separator() edit_menu.add_command(label='Cut', accelerator='Ctrl+X', compound='left', image=cut_icon, command=cut) edit_menu.add_command(label='Copy', accelerator='Ctrl+C', compound='left', image=copy_icon, command=copy) edit_menu.add_command(label='Paste', accelerator='Ctrl+V', compound='left', image=paste_icon, command=paste) edit_menu.add_separator() edit_menu.add_command(label='Find', underline=0, accelerator='Ctrl+F', command=find_text) edit_menu.add_separator() edit_menu.add_command(label='Select All', underline=7, accelerator='Ctrl+A', command=select_all) menu_bar.add_cascade(label='Edit', menu=edit_menu) view_menu = Menu(menu_bar, tearoff=0) show_line_number = IntVar() show_line_number.set(1) view_menu.add_checkbutton(label='Show Line Number', variable=show_line_number) show_cursor_info = IntVar() show_cursor_info.set(1) view_menu.add_checkbutton( label='Show Cursor Location at Bottom', variable=show_cursor_info) highlight_line = IntVar() view_menu.add_checkbutton(label='Highlight Current Line', onvalue=1, offvalue=0, variable=highlight_line) themes_menu = Menu(menu_bar, tearoff=0) view_menu.add_cascade(label='Themes', menu=themes_menu) color_schemes = { 'Default': '#000000.#FFFFFF', 'Greygarious': '#83406A.#D1D4D1', 'Aquamarine': '#5B8340.#D1E7E0', 'Bold Beige': '#4B4620.#FFF0E1', 'Cobalt Blue': '#ffffBB.#3333aa', 'Olive Green': '#D1E7E0.#5B8340', 'Night Mode': '#FFFFFF.#000000', } theme_choice = StringVar() theme_choice.set('Default') for k in sorted(color_schemes): themes_menu.add_radiobutton(label=k, variable=theme_choice) menu_bar.add_cascade(label='View', menu=view_menu) about_menu = Menu(menu_bar, tearoff=0) about_menu.add_command(label='About') about_menu.add_command(label='Help') menu_bar.add_cascade(label='About', menu=about_menu) root.config(menu=menu_bar) shortcut_bar = Frame(root, height=25, background='light sea green') shortcut_bar.pack(expand='no', fill='x') line_number_bar = Text(root, width=4, padx=3, takefocus=0, border=0, background='khaki', state='disabled', wrap='none') line_number_bar.pack(side='left', fill='y') content_text = Text(root, wrap='word', undo=1) content_text.pack(expand='yes', fill='both') scroll_bar = Scrollbar(content_text) content_text.configure(yscrollcommand=scroll_bar.set) scroll_bar.config(command=content_text.yview) scroll_bar.pack(side='right', fill='y') # Shortcut key bindings for this iteration content_text.bind('', new_file) content_text.bind('', new_file) content_text.bind('', open_file) content_text.bind('', open_file) content_text.bind('', save) content_text.bind('', save) # Iteration ends content_text.bind('', find_text) content_text.bind('', find_text) content_text.bind('', select_all) content_text.bind('', select_all) content_text.bind('', redo) content_text.bind('', redo) root.mainloop() ================================================ FILE: Chapter 02/2.08.py ================================================ """ Code illustration: 2.08 A demonstration of tkinter.messagebox showinfo showwarning showerror askquestion askokcancel askyesno askyesnocancel askretrycancel @Tkinter GUI Application Development Blueprints """ from tkinter import Tk, Frame, Label, Button, BOTH, LEFT import tkinter.messagebox as tmb root = Tk() fr1 = Frame(root) fr2 = Frame(root) opt = {'fill': BOTH, 'side': LEFT, 'padx': 2, 'pady': 3} # Demo of tkinter.messagebox Label(fr1, text="Demo of tkinter.messagebox").pack() Button(fr1, text='info', command=lambda: tmb.showinfo( "Show Info", "This is FYI")).pack(opt) Button(fr1, text='warning', command=lambda: tmb.showwarning( "Show Warning", "Don't be silly")).pack(opt) Button(fr1, text='error', command=lambda: tmb.showerror( "Show Error", "It leaked")).pack(opt) Button(fr1, text='question', command=lambda: tmb.askquestion( "Ask Question", "Can you read this ?")).pack(opt) Button(fr2, text='okcancel', command=lambda: tmb.askokcancel( "Ask OK Cancel", "Say Ok or Cancel?")).pack(opt) Button(fr2, text='yesno', command=lambda: tmb.askyesno( "Ask Yes-No", "Say yes or no?")).pack(opt) Button(fr2, text='yesnocancel', command=lambda: tmb.askyesnocancel( "Yes-No-Cancel", "Say yes no cancel")).pack(opt) Button(fr2, text='retrycancel', command=lambda: tmb.askretrycancel( "Ask Retry Cancel", "Retry or what?")).pack(opt) fr1.pack() fr2.pack() root.mainloop() ================================================ FILE: Chapter 02/2.09.py ================================================ """ Code illustration: 2.09 Adding: About, Help Message Box 'Really Quit ?' Prompt to exit button @Tkinter GUI Application Development Blueprints """ import os from tkinter import Tk, PhotoImage, Menu, Frame, Text, Scrollbar, IntVar, \ StringVar, END import tkinter.filedialog import tkinter.messagebox PROGRAM_NAME = "Footprint Editor" file_name = None root = Tk() root.geometry('350x350') root.title(PROGRAM_NAME) # about, help and exit messagebox def display_about_messagebox(event=None): tkinter.messagebox.showinfo( "About", PROGRAM_NAME + "\nTkinter GUI Application\n Development Blueprints") def display_help_messagebox(event=None): tkinter.messagebox.showinfo( "Help", "Help Book: \nTkinter GUI Application\n Development Blueprints", icon='question') def exit_editor(event=None): if tkinter.messagebox.askokcancel("Quit?", "Really quit?"): root.destroy() # keyboard shortcut for help added towards the last of this program # iteration ends def new_file(event=None): root.title("Untitled") global file_name file_name = None content_text.delete(1.0, END) def open_file(event=None): input_file_name = tkinter.filedialog.askopenfilename(defaultextension=".txt", filetypes=[("All Files", "*.*"), ("Text Documents", "*.txt")]) if input_file_name: global file_name file_name = input_file_name root.title('{} - {}'.format(os.path.basename(file_name), PROGRAM_NAME)) content_text.delete(1.0, END) with open(file_name) as _file: content_text.insert(1.0, _file.read()) def write_to_file(file_name): try: content = content_text.get(1.0, 'end') with open(file_name, 'w') as the_file: the_file.write(content) except IOError: tkinter.messagebox.showwarning("Save", "Could not save the file.") def save_as(event=None): input_file_name = tkinter.filedialog.asksaveasfilename(defaultextension=".txt", filetypes=[("All Files", "*.*"), ("Text Documents", "*.txt")]) if input_file_name: global file_name file_name = input_file_name write_to_file(file_name) root.title('{} - {}'.format(os.path.basename(file_name), PROGRAM_NAME)) return "break" def save(event=None): global file_name if not file_name: save_as() else: write_to_file(file_name) return "break" def select_all(event=None): content_text.tag_add('sel', '1.0', 'end') return "break" def find_text(event=None): search_toplevel = Toplevel(root) search_toplevel.title('Find Text') search_toplevel.transient(root) search_toplevel.resizable(False, False) Label(search_toplevel, text="Find All:").grid(row=0, column=0, sticky='e') search_entry_widget = Entry( search_toplevel, width=25) search_entry_widget.grid(row=0, column=1, padx=2, pady=2, sticky='we') search_entry_widget.focus_set() ignore_case_value = IntVar() Checkbutton(search_toplevel, text='Ignore Case', variable=ignore_case_value).grid( row=1, column=1, sticky='e', padx=2, pady=2) Button(search_toplevel, text="Find All", underline=0, command=lambda: search_output( search_entry_widget.get(), ignore_case_value.get(), content_text, search_toplevel, search_entry_widget) ).grid(row=0, column=2, sticky='e' + 'w', padx=2, pady=2) def close_search_window(): content_text.tag_remove('match', '1.0', END) search_toplevel.destroy() search_toplevel.protocol('WM_DELETE_WINDOW', close_search_window) return "break" def search_output(needle, if_ignore_case, content_text, search_toplevel, search_box): content_text.tag_remove('match', '1.0', END) matches_found = 0 if needle: start_pos = '1.0' while True: start_pos = content_text.search(needle, start_pos, nocase=if_ignore_case, stopindex=END) if not start_pos: break end_pos = '{}+{}c'.format(start_pos, len(needle)) content_text.tag_add('match', start_pos, end_pos) matches_found += 1 start_pos = end_pos content_text.tag_config( 'match', foreground='red', background='yellow') search_box.focus_set() search_toplevel.title('{} matches found'.format(matches_found)) def cut(): content_text.event_generate("<>") return "break" def copy(): content_text.event_generate("<>") return "break" def paste(): content_text.event_generate("<>") return "break" def undo(): content_text.event_generate("<>") return "break" def redo(event=None): content_text.event_generate("<>") return 'break' new_file_icon = PhotoImage(file='icons/new_file.gif') open_file_icon = PhotoImage(file='icons/open_file.gif') save_file_icon = PhotoImage(file='icons/save.gif') cut_icon = PhotoImage(file='icons/cut.gif') copy_icon = PhotoImage(file='icons/copy.gif') paste_icon = PhotoImage(file='icons/paste.gif') undo_icon = PhotoImage(file='icons/undo.gif') redo_icon = PhotoImage(file='icons/redo.gif') menu_bar = Menu(root) file_menu = Menu(menu_bar, tearoff=0) file_menu.add_command(label='New', accelerator='Ctrl+N', compound='left', image=new_file_icon, underline=0, command=new_file) file_menu.add_command(label='Open', accelerator='Ctrl+O', compound='left', image=open_file_icon, underline=0, command=open_file) file_menu.add_command(label='Save', accelerator='Ctrl+S', compound='left', image=save_file_icon, underline=0, command=save) file_menu.add_command( label='Save as', accelerator='Shift+Ctrl+S', command=save_as) file_menu.add_separator() file_menu.add_command(label='Exit', accelerator='Alt+F4', command=exit_editor) menu_bar.add_cascade(label='File', menu=file_menu) edit_menu = Menu(menu_bar, tearoff=0) edit_menu.add_command(label='Undo', accelerator='Ctrl+Z', compound='left', image=undo_icon, command=undo) edit_menu.add_command(label='Redo', accelerator='Ctrl+Y', compound='left', image=redo_icon, command=redo) edit_menu.add_separator() edit_menu.add_command(label='Cut', accelerator='Ctrl+X', compound='left', image=cut_icon, command=cut) edit_menu.add_command(label='Copy', accelerator='Ctrl+C', compound='left', image=copy_icon, command=copy) edit_menu.add_command(label='Paste', accelerator='Ctrl+V', compound='left', image=paste_icon, command=paste) edit_menu.add_separator() edit_menu.add_command(label='Find', underline=0, accelerator='Ctrl+F', command=find_text) edit_menu.add_separator() edit_menu.add_command(label='Select All', underline=7, accelerator='Ctrl+A', command=select_all) menu_bar.add_cascade(label='Edit', menu=edit_menu) view_menu = Menu(menu_bar, tearoff=0) show_line_number = IntVar() show_line_number.set(1) view_menu.add_checkbutton(label='Show Line Number', variable=show_line_number) show_cursor_info = IntVar() show_cursor_info.set(1) view_menu.add_checkbutton( label='Show Cursor Location at Bottom', variable=show_cursor_info) highlight_line = IntVar() view_menu.add_checkbutton(label='Highlight Current Line', onvalue=1, offvalue=0, variable=highlight_line) themes_menu = Menu(menu_bar, tearoff=0) view_menu.add_cascade(label='Themes', menu=themes_menu) color_schemes = { 'Default': '#000000.#FFFFFF', 'Greygarious': '#83406A.#D1D4D1', 'Aquamarine': '#5B8340.#D1E7E0', 'Bold Beige': '#4B4620.#FFF0E1', 'Cobalt Blue': '#ffffBB.#3333aa', 'Olive Green': '#D1E7E0.#5B8340', 'Night Mode': '#FFFFFF.#000000', } theme_choice = StringVar() theme_choice.set('Default') for k in sorted(color_schemes): themes_menu.add_radiobutton(label=k, variable=theme_choice) menu_bar.add_cascade(label='View', menu=view_menu) about_menu = Menu(menu_bar, tearoff=0) about_menu.add_command(label='About', command=display_about_messagebox) about_menu.add_command(label='Help', command=display_help_messagebox) menu_bar.add_cascade(label='About', menu=about_menu) root.config(menu=menu_bar) shortcut_bar = Frame(root, height=25, background='light sea green') shortcut_bar.pack(expand='no', fill='x') line_number_bar = Text(root, width=4, padx=3, takefocus=0, border=0, background='khaki', state='disabled', wrap='none') line_number_bar.pack(side='left', fill='y') content_text = Text(root, wrap='word', undo=1) content_text.pack(expand='yes', fill='both') scroll_bar = Scrollbar(content_text) content_text.configure(yscrollcommand=scroll_bar.set) scroll_bar.config(command=content_text.yview) scroll_bar.pack(side='right', fill='y') # added keyboard shortcut for help content_text.bind('', display_help_messagebox) # ends content_text.bind('', new_file) content_text.bind('', new_file) content_text.bind('', open_file) content_text.bind('', open_file) content_text.bind('', save) content_text.bind('', save) content_text.bind('', find_text) content_text.bind('', find_text) content_text.bind('', select_all) content_text.bind('', select_all) content_text.bind('', redo) content_text.bind('', redo) root.protocol('WM_DELETE_WINDOW', exit_editor) root.mainloop() ================================================ FILE: Chapter 02/2.10.py ================================================ """ Code illustration: 2.10 Adding Shortcut Icons toolbar Displaying Line Numbers Highlighting Current Line @Tkinter GUI Application Development Blueprints """ import os from tkinter import Tk, PhotoImage, Menu, Frame, Text, Scrollbar, IntVar, \ StringVar, BooleanVar, Button, END import tkinter.filedialog import tkinter.messagebox PROGRAM_NAME = "Footprint Editor" file_name = None root = Tk() root.geometry('350x350') root.title(PROGRAM_NAME) def update_line_numbers(event=None): line_numbers = get_line_numbers() line_number_bar.config(state='normal') line_number_bar.delete('1.0', 'end') line_number_bar.insert('1.0', line_numbers) line_number_bar.config(state='disabled') def highlight_line(interval=100): content_text.tag_remove("active_line", 1.0, "end") content_text.tag_add( "active_line", "insert linestart", "insert lineend+1c") content_text.after(interval, toggle_highlight) def undo_highlight(): content_text.tag_remove("active_line", 1.0, "end") def toggle_highlight(event=None): if to_highlight_line.get(): highlight_line() else: undo_highlight() def on_content_changed(event=None): update_line_numbers() def get_line_numbers(): output = '' if show_line_number.get(): row, col = content_text.index("end").split('.') for i in range(1, int(row)): output += str(i) + '\n' return output def display_about_messagebox(event=None): tkinter.messagebox.showinfo( "About", PROGRAM_NAME + "\nTkinter GUI Application\n Development Blueprints") def display_help_messagebox(event=None): tkinter.messagebox.showinfo( "Help", "Help Book: \nTkinter GUI Application\n Development Blueprints", icon='question') def exit_editor(event=None): if tkinter.messagebox.askokcancel("Quit?", "Really quit?"): root.destroy() def new_file(event=None): root.title("Untitled") global file_name file_name = None content_text.delete(1.0, END) on_content_changed() def open_file(event=None): input_file_name = tkinter.filedialog.askopenfilename(defaultextension=".txt", filetypes=[("All Files", "*.*"), ("Text Documents", "*.txt")]) if input_file_name: global file_name file_name = input_file_name root.title('{} - {}'.format(os.path.basename(file_name), PROGRAM_NAME)) content_text.delete(1.0, END) with open(file_name) as _file: content_text.insert(1.0, _file.read()) on_content_changed() def write_to_file(file_name): try: content = content_text.get(1.0, 'end') with open(file_name, 'w') as the_file: the_file.write(content) except IOError: tkinter.messagebox.showwarning("Save", "Could not save the file.") def save_as(event=None): input_file_name = tkinter.filedialog.asksaveasfilename(defaultextension=".txt", filetypes=[("All Files", "*.*"), ("Text Documents", "*.txt")]) if input_file_name: global file_name file_name = input_file_name write_to_file(file_name) root.title('{} - {}'.format(os.path.basename(file_name), PROGRAM_NAME)) return "break" def save(event=None): global file_name if not file_name: save_as() else: write_to_file(file_name) return "break" def select_all(event=None): content_text.tag_add('sel', '1.0', 'end') return "break" def find_text(event=None): search_toplevel = Toplevel(root) search_toplevel.title('Find Text') search_toplevel.transient(root) search_toplevel.resizable(False, False) Label(search_toplevel, text="Find All:").grid(row=0, column=0, sticky='e') search_entry_widget = Entry( search_toplevel, width=25) search_entry_widget.grid(row=0, column=1, padx=2, pady=2, sticky='we') search_entry_widget.focus_set() ignore_case_value = IntVar() Checkbutton(search_toplevel, text='Ignore Case', variable=ignore_case_value).grid( row=1, column=1, sticky='e', padx=2, pady=2) Button(search_toplevel, text="Find All", underline=0, command=lambda: search_output( search_entry_widget.get(), ignore_case_value.get(), content_text, search_toplevel, search_entry_widget) ).grid(row=0, column=2, sticky='e' + 'w', padx=2, pady=2) def close_search_window(): content_text.tag_remove('match', '1.0', END) search_toplevel.destroy() search_toplevel.protocol('WM_DELETE_WINDOW', close_search_window) return "break" def search_output(needle, if_ignore_case, content_text, search_toplevel, search_box): content_text.tag_remove('match', '1.0', END) matches_found = 0 if needle: start_pos = '1.0' while True: start_pos = content_text.search(needle, start_pos, nocase=if_ignore_case, stopindex=END) if not start_pos: break end_pos = '{}+{}c'.format(start_pos, len(needle)) content_text.tag_add('match', start_pos, end_pos) matches_found += 1 start_pos = end_pos content_text.tag_config( 'match', foreground='red', background='yellow') search_box.focus_set() search_toplevel.title('{} matches found'.format(matches_found)) def cut(): content_text.event_generate("<>") on_content_changed() return "break" def copy(): content_text.event_generate("<>") return "break" def paste(): content_text.event_generate("<>") on_content_changed() return "break" def undo(): content_text.event_generate("<>") on_content_changed() return "break" def redo(event=None): content_text.event_generate("<>") on_content_changed() return 'break' new_file_icon = PhotoImage(file='icons/new_file.gif') open_file_icon = PhotoImage(file='icons/open_file.gif') save_file_icon = PhotoImage(file='icons/save.gif') cut_icon = PhotoImage(file='icons/cut.gif') copy_icon = PhotoImage(file='icons/copy.gif') paste_icon = PhotoImage(file='icons/paste.gif') undo_icon = PhotoImage(file='icons/undo.gif') redo_icon = PhotoImage(file='icons/redo.gif') menu_bar = Menu(root) file_menu = Menu(menu_bar, tearoff=0) file_menu.add_command(label='New', accelerator='Ctrl+N', compound='left', image=new_file_icon, underline=0, command=new_file) file_menu.add_command(label='Open', accelerator='Ctrl+O', compound='left', image=open_file_icon, underline=0, command=open_file) file_menu.add_command(label='Save', accelerator='Ctrl+S', compound='left', image=save_file_icon, underline=0, command=save) file_menu.add_command( label='Save as', accelerator='Shift+Ctrl+S', command=save_as) file_menu.add_separator() file_menu.add_command(label='Exit', accelerator='Alt+F4', command=exit_editor) menu_bar.add_cascade(label='File', menu=file_menu) edit_menu = Menu(menu_bar, tearoff=0) edit_menu.add_command(label='Undo', accelerator='Ctrl+Z', compound='left', image=undo_icon, command=undo) edit_menu.add_command(label='Redo', accelerator='Ctrl+Y', compound='left', image=redo_icon, command=redo) edit_menu.add_separator() edit_menu.add_command(label='Cut', accelerator='Ctrl+X', compound='left', image=cut_icon, command=cut) edit_menu.add_command(label='Copy', accelerator='Ctrl+C', compound='left', image=copy_icon, command=copy) edit_menu.add_command(label='Paste', accelerator='Ctrl+V', compound='left', image=paste_icon, command=paste) edit_menu.add_separator() edit_menu.add_command(label='Find', underline=0, accelerator='Ctrl+F', command=find_text) edit_menu.add_separator() edit_menu.add_command(label='Select All', underline=7, accelerator='Ctrl+A', command=select_all) menu_bar.add_cascade(label='Edit', menu=edit_menu) view_menu = Menu(menu_bar, tearoff=0) show_line_number = IntVar() show_line_number.set(1) view_menu.add_checkbutton(label='Show Line Number', variable=show_line_number, command=update_line_numbers) show_cursor_info = IntVar() show_cursor_info.set(1) view_menu.add_checkbutton( label='Show Cursor Location at Bottom', variable=show_cursor_info) to_highlight_line = BooleanVar() view_menu.add_checkbutton(label='Highlight Current Line', onvalue=1, offvalue=0, variable=to_highlight_line, command=toggle_highlight) themes_menu = Menu(menu_bar, tearoff=0) view_menu.add_cascade(label='Themes', menu=themes_menu) color_schemes = { 'Default': '#000000.#FFFFFF', 'Greygarious': '#83406A.#D1D4D1', 'Aquamarine': '#5B8340.#D1E7E0', 'Bold Beige': '#4B4620.#FFF0E1', 'Cobalt Blue': '#ffffBB.#3333aa', 'Olive Green': '#D1E7E0.#5B8340', 'Night Mode': '#FFFFFF.#000000', } theme_choice = StringVar() theme_choice.set('Default') for k in sorted(color_schemes): themes_menu.add_radiobutton(label=k, variable=theme_choice) menu_bar.add_cascade(label='View', menu=view_menu) about_menu = Menu(menu_bar, tearoff=0) about_menu.add_command(label='About', command=display_about_messagebox) about_menu.add_command(label='Help', command=display_help_messagebox) menu_bar.add_cascade(label='About', menu=about_menu) root.config(menu=menu_bar) shortcut_bar = Frame(root, height=25) shortcut_bar.pack(expand='no', fill='x') # adding shortcut icons icons = ('new_file', 'open_file', 'save', 'cut', 'copy', 'paste', 'undo', 'redo', 'find_text') for i, icon in enumerate(icons): tool_bar_icon = PhotoImage(file='icons/{}.gif'.format(icon)) cmd = eval(icon) tool_bar = Button(shortcut_bar, image=tool_bar_icon, command=cmd) tool_bar.image = tool_bar_icon tool_bar.pack(side='left') line_number_bar = Text(root, width=4, padx=3, takefocus=0, border=0, background='khaki', state='disabled', wrap='none') line_number_bar.pack(side='left', fill='y') content_text = Text(root, wrap='word', undo=1) content_text.pack(expand='yes', fill='both') scroll_bar = Scrollbar(content_text) content_text.configure(yscrollcommand=scroll_bar.set) scroll_bar.config(command=content_text.yview) scroll_bar.pack(side='right', fill='y') content_text.bind('', display_help_messagebox) content_text.bind('', new_file) content_text.bind('', new_file) content_text.bind('', open_file) content_text.bind('', open_file) content_text.bind('', save) content_text.bind('', save) content_text.bind('', find_text) content_text.bind('', find_text) content_text.bind('', select_all) content_text.bind('', select_all) content_text.bind('', redo) content_text.bind('', redo) # added in this iteration content_text.bind('', on_content_changed) content_text.tag_configure('active_line', background='ivory2') ### root.protocol('WM_DELETE_WINDOW', exit_editor) root.mainloop() ================================================ FILE: Chapter 02/2.11.py ================================================ """ Code illustration: 2.11 Adding cursor location info at bottom Adding Color Theme @Tkinter GUI Application Development Blueprints """ import os from tkinter import Tk, PhotoImage, Menu, Frame, Text, Scrollbar, IntVar, \ StringVar, BooleanVar, Button, END, Label, INSERT import tkinter.filedialog import tkinter.messagebox PROGRAM_NAME = "Footprint Editor" file_name = None root = Tk() root.geometry('350x350') root.title(PROGRAM_NAME) # show cursor info at bottom def show_cursor_info_bar(): show_cursor_info_checked = show_cursor_info.get() if show_cursor_info_checked: cursor_info_bar.pack(expand='no', fill=None, side='right', anchor='se') else: cursor_info_bar.pack_forget() def update_cursor_info_bar(event=None): row, col = content_text.index(INSERT).split('.') line_num, col_num = str(int(row)), str(int(col) + 1) # col starts at 0 infotext = "Line: {0} | Column: {1}".format(line_num, col_num) cursor_info_bar.config(text=infotext) # change themes def change_theme(event=None): selected_theme = theme_choice.get() fg_bg_colors = color_schemes.get(selected_theme) foreground_color, background_color = fg_bg_colors.split('.') content_text.config( background=background_color, fg=foreground_color) def update_line_numbers(event=None): line_numbers = get_line_numbers() line_number_bar.config(state='normal') line_number_bar.delete('1.0', 'end') line_number_bar.insert('1.0', line_numbers) line_number_bar.config(state='disabled') def highlight_line(interval=100): content_text.tag_remove("active_line", 1.0, "end") content_text.tag_add( "active_line", "insert linestart", "insert lineend+1c") content_text.after(interval, toggle_highlight) def undo_highlight(): content_text.tag_remove("active_line", 1.0, "end") def toggle_highlight(event=None): if to_highlight_line.get(): highlight_line() else: undo_highlight() def on_content_changed(event=None): update_line_numbers() update_cursor_info_bar() def get_line_numbers(): output = '' if show_line_number.get(): row, col = content_text.index("end").split('.') for i in range(1, int(row)): output += str(i) + '\n' return output def update_line_numbers(event=None): line_numbers = get_line_numbers() line_number_bar.config(state='normal') line_number_bar.delete('1.0', 'end') line_number_bar.insert('1.0', line_numbers) line_number_bar.config(state='disabled') def display_about_messagebox(event=None): tkinter.messagebox.showinfo( "About", PROGRAM_NAME + "\nTkinter GUI Application\n Development Blueprints") def display_help_messagebox(event=None): tkinter.messagebox.showinfo( "Help", "Help Book: \nTkinter GUI Application\n Development Blueprints", icon='question') def exit_editor(event=None): if tkinter.messagebox.askokcancel("Quit?", "Really quit?"): root.destroy() def new_file(event=None): root.title("Untitled") global file_name file_name = None content_text.delete(1.0, END) on_content_changed() def open_file(event=None): input_file_name = tkinter.filedialog.askopenfilename(defaultextension=".txt", filetypes=[("All Files", "*.*"), ("Text Documents", "*.txt")]) if input_file_name: global file_name file_name = input_file_name root.title('{} - {}'.format(os.path.basename(file_name), PROGRAM_NAME)) content_text.delete(1.0, END) with open(file_name) as _file: content_text.insert(1.0, _file.read()) on_content_changed() def write_to_file(file_name): try: content = content_text.get(1.0, 'end') with open(file_name, 'w') as the_file: the_file.write(content) except IOError: tkinter.messagebox.showwarning("Save", "Could not save the file.") def save_as(event=None): input_file_name = tkinter.filedialog.asksaveasfilename(defaultextension=".txt", filetypes=[("All Files", "*.*"), ("Text Documents", "*.txt")]) if input_file_name: global file_name file_name = input_file_name write_to_file(file_name) root.title('{} - {}'.format(os.path.basename(file_name), PROGRAM_NAME)) return "break" def save(event=None): global file_name if not file_name: save_as() else: write_to_file(file_name) return "break" def select_all(event=None): content_text.tag_add('sel', '1.0', 'end') return "break" def find_text(event=None): search_toplevel = Toplevel(root) search_toplevel.title('Find Text') search_toplevel.transient(root) search_toplevel.resizable(False, False) Label(search_toplevel, text="Find All:").grid(row=0, column=0, sticky='e') search_entry_widget = Entry( search_toplevel, width=25) search_entry_widget.grid(row=0, column=1, padx=2, pady=2, sticky='we') search_entry_widget.focus_set() ignore_case_value = IntVar() Checkbutton(search_toplevel, text='Ignore Case', variable=ignore_case_value).grid( row=1, column=1, sticky='e', padx=2, pady=2) Button(search_toplevel, text="Find All", underline=0, command=lambda: search_output( search_entry_widget.get(), ignore_case_value.get(), content_text, search_toplevel, search_entry_widget) ).grid(row=0, column=2, sticky='e' + 'w', padx=2, pady=2) def close_search_window(): content_text.tag_remove('match', '1.0', END) search_toplevel.destroy() search_toplevel.protocol('WM_DELETE_WINDOW', close_search_window) return "break" def search_output(needle, if_ignore_case, content_text, search_toplevel, search_box): content_text.tag_remove('match', '1.0', END) matches_found = 0 if needle: start_pos = '1.0' while True: start_pos = content_text.search(needle, start_pos, nocase=if_ignore_case, stopindex=END) if not start_pos: break end_pos = '{}+{}c'.format(start_pos, len(needle)) content_text.tag_add('match', start_pos, end_pos) matches_found += 1 start_pos = end_pos content_text.tag_config( 'match', foreground='red', background='yellow') search_box.focus_set() search_toplevel.title('{} matches found'.format(matches_found)) def cut(): content_text.event_generate("<>") on_content_changed() return "break" def copy(): content_text.event_generate("<>") return "break" def paste(): content_text.event_generate("<>") on_content_changed() return "break" def undo(): content_text.event_generate("<>") on_content_changed() return "break" def redo(event=None): content_text.event_generate("<>") on_content_changed() return 'break' new_file_icon = PhotoImage(file='icons/new_file.gif') open_file_icon = PhotoImage(file='icons/open_file.gif') save_file_icon = PhotoImage(file='icons/save.gif') cut_icon = PhotoImage(file='icons/cut.gif') copy_icon = PhotoImage(file='icons/copy.gif') paste_icon = PhotoImage(file='icons/paste.gif') undo_icon = PhotoImage(file='icons/undo.gif') redo_icon = PhotoImage(file='icons/redo.gif') menu_bar = Menu(root) file_menu = Menu(menu_bar, tearoff=0) file_menu.add_command(label='New', accelerator='Ctrl+N', compound='left', image=new_file_icon, underline=0, command=new_file) file_menu.add_command(label='Open', accelerator='Ctrl+O', compound='left', image=open_file_icon, underline=0, command=open_file) file_menu.add_command(label='Save', accelerator='Ctrl+S', compound='left', image=save_file_icon, underline=0, command=save) file_menu.add_command( label='Save as', accelerator='Shift+Ctrl+S', command=save_as) file_menu.add_separator() file_menu.add_command(label='Exit', accelerator='Alt+F4', command=exit_editor) menu_bar.add_cascade(label='File', menu=file_menu) edit_menu = Menu(menu_bar, tearoff=0) edit_menu.add_command(label='Undo', accelerator='Ctrl+Z', compound='left', image=undo_icon, command=undo) edit_menu.add_command(label='Redo', accelerator='Ctrl+Y', compound='left', image=redo_icon, command=redo) edit_menu.add_separator() edit_menu.add_command(label='Cut', accelerator='Ctrl+X', compound='left', image=cut_icon, command=cut) edit_menu.add_command(label='Copy', accelerator='Ctrl+C', compound='left', image=copy_icon, command=copy) edit_menu.add_command(label='Paste', accelerator='Ctrl+V', compound='left', image=paste_icon, command=paste) edit_menu.add_separator() edit_menu.add_command(label='Find', underline=0, accelerator='Ctrl+F', command=find_text) edit_menu.add_separator() edit_menu.add_command(label='Select All', underline=7, accelerator='Ctrl+A', command=select_all) menu_bar.add_cascade(label='Edit', menu=edit_menu) view_menu = Menu(menu_bar, tearoff=0) show_line_number = IntVar() show_line_number.set(1) view_menu.add_checkbutton(label='Show Line Number', variable=show_line_number, command=update_line_numbers) show_cursor_info = IntVar() show_cursor_info.set(1) view_menu.add_checkbutton( label='Show Cursor Location at Bottom', variable=show_cursor_info, command=show_cursor_info_bar) to_highlight_line = BooleanVar() view_menu.add_checkbutton(label='Highlight Current Line', onvalue=1, offvalue=0, variable=to_highlight_line, command=toggle_highlight) themes_menu = Menu(menu_bar, tearoff=0) view_menu.add_cascade(label='Themes', menu=themes_menu) color_schemes = { 'Default': '#000000.#FFFFFF', 'Greygarious': '#83406A.#D1D4D1', 'Aquamarine': '#5B8340.#D1E7E0', 'Bold Beige': '#4B4620.#FFF0E1', 'Cobalt Blue': '#ffffBB.#3333aa', 'Olive Green': '#D1E7E0.#5B8340', 'Night Mode': '#FFFFFF.#000000', } theme_choice = StringVar() theme_choice.set('Default') for k in sorted(color_schemes): themes_menu.add_radiobutton( label=k, variable=theme_choice, command=change_theme) menu_bar.add_cascade(label='View', menu=view_menu) about_menu = Menu(menu_bar, tearoff=0) about_menu.add_command(label='About', command=display_about_messagebox) about_menu.add_command(label='Help', command=display_help_messagebox) menu_bar.add_cascade(label='About', menu=about_menu) root.config(menu=menu_bar) shortcut_bar = Frame(root, height=25) shortcut_bar.pack(expand='no', fill='x') icons = ('new_file', 'open_file', 'save', 'cut', 'copy', 'paste', 'undo', 'redo', 'find_text') for i, icon in enumerate(icons): tool_bar_icon = PhotoImage(file='icons/{}.gif'.format(icon)) cmd = eval(icon) tool_bar = Button(shortcut_bar, image=tool_bar_icon, command=cmd) tool_bar.image = tool_bar_icon tool_bar.pack(side='left') line_number_bar = Text(root, width=4, padx=3, takefocus=0, border=0, background='khaki', state='disabled', wrap='none') line_number_bar.pack(side='left', fill='y') content_text = Text(root, wrap='word', undo=1) content_text.pack(expand='yes', fill='both') scroll_bar = Scrollbar(content_text) content_text.configure(yscrollcommand=scroll_bar.set) scroll_bar.config(command=content_text.yview) scroll_bar.pack(side='right', fill='y') # add cursor info label cursor_info_bar = Label(content_text, text='Line: 1 | Column: 1') cursor_info_bar.pack(expand='no', fill=None, side='right', anchor='se') content_text.bind('', display_help_messagebox) content_text.bind('', new_file) content_text.bind('', new_file) content_text.bind('', open_file) content_text.bind('', open_file) content_text.bind('', save) content_text.bind('', save) content_text.bind('', find_text) content_text.bind('', find_text) content_text.bind('', select_all) content_text.bind('', select_all) content_text.bind('', redo) content_text.bind('', redo) content_text.bind('', on_content_changed) content_text.tag_configure('active_line', background='ivory2') root.protocol('WM_DELETE_WINDOW', exit_editor) root.mainloop() ================================================ FILE: Chapter 02/2.12.py ================================================ """ Code illustration: 2.12.py Features Added: Add context/ Pop-up Menu Set focus on launch @Tkinter GUI Application Development Blueprints """ import os from tkinter import Tk, PhotoImage, Menu, Frame, Text, Scrollbar, IntVar, \ StringVar, BooleanVar, Button, END, Label, INSERT import tkinter.filedialog import tkinter.messagebox PROGRAM_NAME = "Footprint Editor" file_name = None root = Tk() root.geometry('350x350') root.title(PROGRAM_NAME) # show pop-up menu def show_popup_menu(event): popup_menu.tk_popup(event.x_root, event.y_root) def show_cursor_info_bar(): show_cursor_info_checked = show_cursor_info.get() if show_cursor_info_checked: cursor_info_bar.pack(expand='no', fill=None, side='right', anchor='se') else: cursor_info_bar.pack_forget() def update_cursor_info_bar(event=None): row, col = content_text.index(INSERT).split('.') line_num, col_num = str(int(row)), str(int(col) + 1) # col starts at 0 infotext = "Line: {0} | Column: {1}".format(line_num, col_num) cursor_info_bar.config(text=infotext) def change_theme(event=None): selected_theme = theme_choice.get() fg_bg_colors = color_schemes.get(selected_theme) foreground_color, background_color = fg_bg_colors.split('.') content_text.config( background=background_color, fg=foreground_color) def update_line_numbers(event=None): line_numbers = get_line_numbers() line_number_bar.config(state='normal') line_number_bar.delete('1.0', 'end') line_number_bar.insert('1.0', line_numbers) line_number_bar.config(state='disabled') def highlight_line(interval=100): content_text.tag_remove("active_line", 1.0, "end") content_text.tag_add( "active_line", "insert linestart", "insert lineend+1c") content_text.after(interval, toggle_highlight) def undo_highlight(): content_text.tag_remove("active_line", 1.0, "end") def toggle_highlight(event=None): if to_highlight_line.get(): highlight_line() else: undo_highlight() def on_content_changed(event=None): update_line_numbers() update_cursor_info_bar() def get_line_numbers(): output = '' if show_line_number.get(): row, col = content_text.index("end").split('.') for i in range(1, int(row)): output += str(i) + '\n' return output def display_about_messagebox(event=None): tkinter.messagebox.showinfo( "About", "{}{}".format(PROGRAM_NAME, "\nTkinter GUI Application\n Development Blueprints")) def display_help_messagebox(event=None): tkinter.messagebox.showinfo( "Help", "Help Book: \nTkinter GUI Application\n Development Blueprints", icon='question') def exit_editor(event=None): if tkinter.messagebox.askokcancel("Quit?", "Really quit?"): root.destroy() def new_file(event=None): root.title("Untitled") global file_name file_name = None content_text.delete(1.0, END) on_content_changed() def open_file(event=None): input_file_name = tkinter.filedialog.askopenfilename(defaultextension=".txt", filetypes=[("All Files", "*.*"), ("Text Documents", "*.txt")]) if input_file_name: global file_name file_name = input_file_name root.title('{} - {}'.format(os.path.basename(file_name), PROGRAM_NAME)) content_text.delete(1.0, END) with open(file_name) as _file: content_text.insert(1.0, _file.read()) on_content_changed() def write_to_file(file_name): try: content = content_text.get(1.0, 'end') with open(file_name, 'w') as the_file: the_file.write(content) except IOError: tkinter.messagebox.showwarning("Save", "Could not save the file.") def save_as(event=None): input_file_name = tkinter.filedialog.asksaveasfilename(defaultextension=".txt", filetypes=[("All Files", "*.*"), ("Text Documents", "*.txt")]) if input_file_name: global file_name file_name = input_file_name write_to_file(file_name) root.title('{} - {}'.format(os.path.basename(file_name), PROGRAM_NAME)) return "break" def save(event=None): global file_name if not file_name: save_as() else: write_to_file(file_name) return "break" def select_all(event=None): content_text.tag_add('sel', '1.0', 'end') return "break" def find_text(event=None): search_toplevel = Toplevel(root) search_toplevel.title('Find Text') search_toplevel.transient(root) Label(search_toplevel, text="Find All:").grid(row=0, column=0, sticky='e') search_entry_widget = Entry( search_toplevel, width=25) search_entry_widget.grid(row=0, column=1, padx=2, pady=2, sticky='we') search_entry_widget.focus_set() ignore_case_value = IntVar() Checkbutton(search_toplevel, text='Ignore Case', variable=ignore_case_value).grid( row=1, column=1, sticky='e', padx=2, pady=2) Button(search_toplevel, text="Find All", underline=0, command=lambda: search_output( search_entry_widget.get(), ignore_case_value.get(), content_text, search_toplevel, search_entry_widget) ).grid(row=0, column=2, sticky='e' + 'w', padx=2, pady=2) def close_search_window(): content_text.tag_remove('match', '1.0', END) search_toplevel.destroy() search_toplevel.protocol('WM_DELETE_WINDOW', close_search_window) return "break" def search_output(needle, if_ignore_case, content_text, search_toplevel, search_box): content_text.tag_remove('match', '1.0', END) matches_found = 0 if needle: start_pos = '1.0' while True: start_pos = content_text.search(needle, start_pos, nocase=if_ignore_case, stopindex=END) if not start_pos: break end_pos = '{}+{}c'.format(start_pos, len(needle)) content_text.tag_add('match', start_pos, end_pos) matches_found += 1 start_pos = end_pos content_text.tag_config( 'match', foreground='red', background='yellow') search_box.focus_set() search_toplevel.title('{} matches found'.format(matches_found)) def cut(): content_text.event_generate("<>") on_content_changed() return "break" def copy(): content_text.event_generate("<>") return "break" def paste(): content_text.event_generate("<>") on_content_changed() return "break" def undo(): content_text.event_generate("<>") on_content_changed() return "break" def redo(event=None): content_text.event_generate("<>") on_content_changed() return 'break' new_file_icon = PhotoImage(file='icons/new_file.gif') open_file_icon = PhotoImage(file='icons/open_file.gif') save_file_icon = PhotoImage(file='icons/save.gif') cut_icon = PhotoImage(file='icons/cut.gif') copy_icon = PhotoImage(file='icons/copy.gif') paste_icon = PhotoImage(file='icons/paste.gif') undo_icon = PhotoImage(file='icons/undo.gif') redo_icon = PhotoImage(file='icons/redo.gif') menu_bar = Menu(root) file_menu = Menu(menu_bar, tearoff=0) file_menu.add_command(label='New', accelerator='Ctrl+N', compound='left', image=new_file_icon, underline=0, command=new_file) file_menu.add_command(label='Open', accelerator='Ctrl+O', compound='left', image=open_file_icon, underline=0, command=open_file) file_menu.add_command(label='Save', accelerator='Ctrl+S', compound='left', image=save_file_icon, underline=0, command=save) file_menu.add_command( label='Save as', accelerator='Shift+Ctrl+S', command=save_as) file_menu.add_separator() file_menu.add_command(label='Exit', accelerator='Alt+F4', command=exit_editor) menu_bar.add_cascade(label='File', menu=file_menu) edit_menu = Menu(menu_bar, tearoff=0) edit_menu.add_command(label='Undo', accelerator='Ctrl+Z', compound='left', image=undo_icon, command=undo) edit_menu.add_command(label='Redo', accelerator='Ctrl+Y', compound='left', image=redo_icon, command=redo) edit_menu.add_separator() edit_menu.add_command(label='Cut', accelerator='Ctrl+X', compound='left', image=cut_icon, command=cut) edit_menu.add_command(label='Copy', accelerator='Ctrl+C', compound='left', image=copy_icon, command=copy) edit_menu.add_command(label='Paste', accelerator='Ctrl+V', compound='left', image=paste_icon, command=paste) edit_menu.add_separator() edit_menu.add_command(label='Find', underline=0, accelerator='Ctrl+F', command=find_text) edit_menu.add_separator() edit_menu.add_command(label='Select All', underline=7, accelerator='Ctrl+A', command=select_all) menu_bar.add_cascade(label='Edit', menu=edit_menu) view_menu = Menu(menu_bar, tearoff=0) show_line_number = IntVar() show_line_number.set(1) view_menu.add_checkbutton(label='Show Line Number', variable=show_line_number, command=update_line_numbers) show_cursor_info = IntVar() show_cursor_info.set(1) view_menu.add_checkbutton( label='Show Cursor Location at Bottom', variable=show_cursor_info, command=show_cursor_info_bar) to_highlight_line = BooleanVar() view_menu.add_checkbutton(label='Highlight Current Line', onvalue=1, offvalue=0, variable=to_highlight_line, command=toggle_highlight) themes_menu = Menu(menu_bar, tearoff=0) view_menu.add_cascade(label='Themes', menu=themes_menu) color_schemes = { 'Default': '#000000.#FFFFFF', 'Greygarious': '#83406A.#D1D4D1', 'Aquamarine': '#5B8340.#D1E7E0', 'Bold Beige': '#4B4620.#FFF0E1', 'Cobalt Blue': '#ffffBB.#3333aa', 'Olive Green': '#D1E7E0.#5B8340', 'Night Mode': '#FFFFFF.#000000', } theme_choice = StringVar() theme_choice.set('Default') for k in sorted(color_schemes): themes_menu.add_radiobutton( label=k, variable=theme_choice, command=change_theme) menu_bar.add_cascade(label='View', menu=view_menu) about_menu = Menu(menu_bar, tearoff=0) about_menu.add_command(label='About', command=display_about_messagebox) about_menu.add_command(label='Help', command=display_help_messagebox) menu_bar.add_cascade(label='About', menu=about_menu) root.config(menu=menu_bar) shortcut_bar = Frame(root, height=25) shortcut_bar.pack(expand='no', fill='x') icons = ('new_file', 'open_file', 'save', 'cut', 'copy', 'paste', 'undo', 'redo', 'find_text') for i, icon in enumerate(icons): tool_bar_icon = PhotoImage(file='icons/{}.gif'.format(icon)) cmd = eval(icon) tool_bar = Button(shortcut_bar, image=tool_bar_icon, command=cmd) tool_bar.image = tool_bar_icon tool_bar.pack(side='left') line_number_bar = Text(root, width=4, padx=3, takefocus=0, border=0, background='khaki', state='disabled', wrap='none') line_number_bar.pack(side='left', fill='y') content_text = Text(root, wrap='word', undo=1) content_text.pack(expand='yes', fill='both') scroll_bar = Scrollbar(content_text) content_text.configure(yscrollcommand=scroll_bar.set) scroll_bar.config(command=content_text.yview) scroll_bar.pack(side='right', fill='y') cursor_info_bar = Label(content_text, text='Line: 1 | Column: 1') cursor_info_bar.pack(expand='no', fill=None, side='right', anchor='se') content_text.bind('', display_help_messagebox) content_text.bind('', new_file) content_text.bind('', new_file) content_text.bind('', open_file) content_text.bind('', open_file) content_text.bind('', save) content_text.bind('', save) content_text.bind('', find_text) content_text.bind('', find_text) content_text.bind('', select_all) content_text.bind('', select_all) content_text.bind('', redo) content_text.bind('', redo) content_text.bind('', on_content_changed) content_text.tag_configure('active_line', background='ivory2') # set up the pop-up menu popup_menu = Menu(content_text) for i in ('cut', 'copy', 'paste', 'undo', 'redo'): cmd = eval(i) popup_menu.add_command(label=i, compound='left', command=cmd) popup_menu.add_separator() popup_menu.add_command(label='Select All', underline=7, command=select_all) content_text.bind('', show_popup_menu) # bind right mouse click to show pop up and set focus to text widget on launch content_text.bind('', show_popup_menu) content_text.focus_set() root.protocol('WM_DELETE_WINDOW', exit_editor) root.mainloop() ================================================ FILE: Chapter 02/readme.txt ================================================ ==================================================================== Code Readme Tkinter GUI Application Development Blueprints Chapter 2: Make a Text-Editor ==================================================================== List of code samples:- 2.01: Add top-level window/ Add menubuttons 2.02: Add menu-items within each menu button/ Add labels to hold shortcut toolbar and line number 2.03 : View Menu items - showing different types of menu items 2.04: Leveraging power of text widget built-in options 2.05: Indexing and tagging - implementation of 'select all' and 'find all' 2.06: A demonstration of different types of top-level window 2.07: Adding File>New,File>Open,File>Save and File>Save As functionality 2.08: A demonstration of tkMessageBox 2.09: Add About, Help & Quit Functionality 2.10: Add shortcut icon toolbar/ display line numbers/ highlight current line 2.11: Add infobar 2.12: Add contextual menu @author: Bhaskar Chaudahary @publisher: Packt Publishing ================================================ FILE: Chapter 03/3.01.py ================================================ """ Code illustration: 3.01 - Creating OOP Based GUI structure Chapter 3 : Programmable Drum Machine Tkinter GUI Application Development Blueprints """ from tkinter import Tk PROGRAM_NAME = ' Explosion Drum Machine ' class DrumMachine: def __init__(self, root): self.root = root self.root.title(PROGRAM_NAME) if __name__ == '__main__': root = Tk() DrumMachine(root) root.mainloop() ================================================ FILE: Chapter 03/3.02.py ================================================ """ Code illustration: 3.02 - Defining & Initializing the Data Structure Chapter 3 : Programmable Drum Machine Tkinter GUI Application Development Blueprints """ from tkinter import Tk PROGRAM_NAME = ' Explosion Drum Machine ' MAX_NUMBER_OF_PATTERNS = 10 MAX_NUMBER_OF_DRUM_SAMPLES = 5 INITIAL_NUMBER_OF_UNITS = 4 INITIAL_BPU = 4 INITIAL_BEATS_PER_MINUTE = 240 class DrumMachine: def __init__(self, root): self.root = root self.root.title(PROGRAM_NAME) self.beats_per_minute = INITIAL_BEATS_PER_MINUTE self.all_patterns = [None] * MAX_NUMBER_OF_PATTERNS self.init_all_patterns() def init_all_patterns(self): self.all_patterns = [ { 'list_of_drum_files': [None] * MAX_NUMBER_OF_DRUM_SAMPLES, 'number_of_units': INITIAL_NUMBER_OF_UNITS, 'bpu': INITIAL_BPU, 'is_button_clicked_list': self.init_is_button_clicked_list( MAX_NUMBER_OF_DRUM_SAMPLES, INITIAL_NUMBER_OF_UNITS * INITIAL_BPU ) } for k in range(MAX_NUMBER_OF_PATTERNS)] def init_is_button_clicked_list(self, num_of_rows, num_of_columns): return [[False] * num_of_columns for x in range(num_of_rows)] if __name__ == '__main__': root = Tk() DrumMachine(root) root.mainloop() ================================================ FILE: Chapter 03/3.03.py ================================================ """ Code illustration: 3.03 Displaying All Visual Elements: Methods newly defined and implemented here: - init_gui() - create_top_bar() - create_left_drum_loader() - create_right_button_matrix() - create_play_bar() - find_number_of_columns() - display_button_color() - display_all_button_colors() - get_button_value() - on_button_clicked() - process_button_clicked() - set_button_value() Methods added as command callback, defined here but not yet implemented - on_pattern_changed() - on_number_of_units_changed() - on_bpu_changed() - on_open_file_button_clicked() - on_button_clicked() - on_play_button_clicked() - on_stop_button_clicked() - on_loop_button_toggled() - on_beats_per_minute_changed() Chapter 3 : Programmable Drum Machine Tkinter GUI Application Development Blueprints """ """ Code illustration: 3.12 applying ttk themes to our play button, stop button, loopbutton adding separators new imports here: - from tkinter import ttk methods modified here: - create_play_bar() Chapter 3 : Programmable Drum Machine Tkinter GUI Application Development Blueprints """ from tkinter import Tk, Entry, W, E, N, S, PhotoImage, Checkbutton, Button, \ Menu, Frame, Label, Spinbox PROGRAM_NAME = ' Explosion Drum Machine ' MAX_NUMBER_OF_PATTERNS = 10 MAX_NUMBER_OF_DRUM_SAMPLES = 5 MAX_NUMBER_OF_UNITS = 5 MAX_BPU = 5 INITIAL_NUMBER_OF_UNITS = 4 INITIAL_BPU = 4 INITIAL_BEATS_PER_MINUTE = 240 MIN_BEATS_PER_MINUTE = 80 MAX_BEATS_PER_MINUTE = 360 COLOR_1 = 'grey55' COLOR_2 = 'khaki' BUTTON_CLICKED_COLOR = 'green' class DrumMachine: def __init__(self, root): self.root = root self.root.title(PROGRAM_NAME) self.all_patterns = [None] * MAX_NUMBER_OF_PATTERNS self.beats_per_minute = INITIAL_BEATS_PER_MINUTE self.current_pattern_index = 0 self.drum_load_entry_widget = [None] * MAX_NUMBER_OF_DRUM_SAMPLES self.init_all_patterns() self.init_gui() def init_all_patterns(self): self.all_patterns = [ { 'list_of_drum_files': [None] * MAX_NUMBER_OF_DRUM_SAMPLES, 'number_of_units': INITIAL_NUMBER_OF_UNITS, 'bpu': INITIAL_BPU, 'is_button_clicked_list': self.init_is_button_clicked_list( MAX_NUMBER_OF_DRUM_SAMPLES, INITIAL_NUMBER_OF_UNITS * INITIAL_BPU ) } for k in range(MAX_NUMBER_OF_PATTERNS)] def on_pattern_changed(self): pass def on_number_of_units_changed(self): pass def on_bpu_changed(self): pass def on_open_file_button_clicked(self, drum_index): pass def on_play_button_clicked(self): pass def on_stop_button_clicked(self): pass def on_loop_button_toggled(self): pass def on_beats_per_minute_changed(self): pass def init_is_button_clicked_list(self, num_of_rows, num_of_columns): return [[False] * num_of_columns for x in range(num_of_rows)] def get_button_value(self, row, col): return self.all_patterns[self.current_pattern_index][ 'is_button_clicked_list'][row][col] def find_number_of_columns(self): return int(self.number_of_units_widget.get()) * int(self.bpu_widget.get()) def process_button_clicked(self, row, col): self.set_button_value(row, col, not self.get_button_value(row, col)) self.display_button_color(row, col) def set_button_value(self, row, col, bool_value): self.all_patterns[self.current_pattern_index][ 'is_button_clicked_list'][row][col] = bool_value def on_button_clicked(self, row, col): def event_handler(): self.process_button_clicked(row, col) return event_handler def display_all_button_colors(self): number_of_columns = self.find_number_of_columns() for r in range(MAX_NUMBER_OF_DRUM_SAMPLES): for c in range(number_of_columns): self.display_button_color(r, c) def display_button_color(self, row, col): bpu = int(self.bpu_widget.get()) original_color = COLOR_1 if ((col//bpu) % 2) else COLOR_2 button_color = BUTTON_CLICKED_COLOR if self.get_button_value( row, col) else original_color self.buttons[row][col].config(background=button_color) def create_play_bar(self): playbar_frame = Frame(self.root, height=15) start_row = MAX_NUMBER_OF_DRUM_SAMPLES + 10 playbar_frame.grid(row=start_row, columnspan=13, sticky=W + E, padx=15, pady=10) self.play_icon = PhotoImage(file="images/play.gif") self.play_button = Button( playbar_frame, text='Play', image=self.play_icon, compound='left', command=self.on_play_button_clicked) self.play_button.grid(row=start_row, column=1, padx=2) Button(playbar_frame, text='Stop', command=self.on_stop_button_clicked).grid( row=start_row, column=3, padx=2) self.loopbutton = Checkbutton( playbar_frame, text='Loop', command=self.on_loop_button_toggled, textvariable=True) self.loopbutton.grid(row=start_row, column=16, padx=5) Label(playbar_frame, text='Beats Per Minute').grid( row=start_row, column=25) self.beats_per_minute_widget = Spinbox(playbar_frame, from_=MIN_BEATS_PER_MINUTE, to=MAX_BEATS_PER_MINUTE, width=5, increment=5.0, command=self.on_beats_per_minute_changed) self.beats_per_minute_widget.grid(row=start_row, column=30) self.beats_per_minute_widget.delete(0,"end") self.beats_per_minute_widget.insert(0,INITIAL_BEATS_PER_MINUTE) photo = PhotoImage(file='images/signature.gif') label = Label(playbar_frame, image=photo) label.image = photo label.grid(row=start_row, column=50, padx=1, sticky='w') def create_right_button_matrix(self): right_frame = Frame(self.root) right_frame.grid(row=10, column=6, sticky=W + E + N + S, padx=15, pady=4) self.buttons = [[None for x in range( self.find_number_of_columns())] for x in range(MAX_NUMBER_OF_DRUM_SAMPLES)] for row in range(MAX_NUMBER_OF_DRUM_SAMPLES): for col in range(self.find_number_of_columns()): self.buttons[row][col] = Button( right_frame, command=self.on_button_clicked(row, col)) self.buttons[row][col].grid(row=row, column=col) self.display_button_color(row, col) def create_left_drum_loader(self): left_frame = Frame(self.root) left_frame.grid(row=10, column=0, columnspan=6, sticky=W + E + N + S) open_file_icon = PhotoImage(file='images/openfile.gif') for i in range(MAX_NUMBER_OF_DRUM_SAMPLES): open_file_button = Button(left_frame, image=open_file_icon, command=self.on_open_file_button_clicked(i)) open_file_button.image = open_file_icon open_file_button.grid(row=i, column=0, padx=5, pady=4) self.drum_load_entry_widget[i] = Entry(left_frame) self.drum_load_entry_widget[i].grid( row=i, column=4, padx=7, pady=4) def create_top_bar(self): topbar_frame = Frame(self.root, height=25) topbar_frame.grid(row=0, columnspan=12, rowspan=10, padx=5, pady=5) Label(topbar_frame, text='Pattern Number:').grid(row=0, column=1) self.pattern_index_widget = Spinbox(topbar_frame, from_=0, to=MAX_NUMBER_OF_PATTERNS - 1, width=5, command=self.on_pattern_changed) self.pattern_index_widget.grid(row=0, column=2) self.current_pattern_name_widget = Entry(topbar_frame) self.current_pattern_name_widget.grid(row=0, column=3, padx=7, pady=2) Label(topbar_frame, text='Number of Units:').grid(row=0, column=4) self.number_of_units_widget = Spinbox(topbar_frame, from_=1, to=MAX_NUMBER_OF_UNITS, width=5, command=self.on_number_of_units_changed) self.number_of_units_widget.delete(0,"end") self.number_of_units_widget.insert(0,INITIAL_NUMBER_OF_UNITS) self.number_of_units_widget.grid(row=0, column=5) Label(topbar_frame, text='BPUs:').grid(row=0, column=6) self.bpu_widget = Spinbox(topbar_frame, from_=1, to=MAX_BPU, width=5, command=self.on_bpu_changed) self.bpu_widget.grid(row=0, column=7) self.bpu_widget.delete(0,"end") self.bpu_widget.insert(0,INITIAL_BPU) def create_top_menu(self): self.menu_bar = Menu(self.root) self.file_menu = Menu(self.menu_bar, tearoff=0) self.file_menu.add_command(label="Load Project") self.file_menu.add_command(label="Save Project") self.file_menu.add_separator() self.file_menu.add_command(label="Exit") self.menu_bar.add_cascade(label="File", menu=self.file_menu) self.about_menu = Menu(self.menu_bar, tearoff=0) self.about_menu.add_command(label="About") self.menu_bar.add_cascade(label="About", menu=self.about_menu) self.root.config(menu=self.menu_bar) def init_gui(self): self.create_top_menu() self.create_top_bar() self.create_left_drum_loader() self.create_right_button_matrix() self.create_play_bar() if __name__ == '__main__': root = Tk() DrumMachine(root) root.mainloop() ================================================ FILE: Chapter 03/3.04.py ================================================ """ Code illustration: 3.04 - Adding all the getter & setter methods for our data structure. - get_current_pattern_dict() - get_bpu() - set_bpu() - get_number_of_units() - set_number_of_units() - get_list_of_drum_files() - get_drum_file_path(drum_index) - set_drum_file_path(drum_index, file_path) - get_is_button_clicked_list() - set_is_button_clicked_list(num_of_rows, num_of_columns) - get_beats_per_minute() - set_beats_per_minute() - Defining on_number_of_units_changed() method - Defining on_bpu_changed() method Chapter 3 : Programmable Drum Machine Tkinter GUI Application Development Blueprints """ from tkinter import Tk, Entry, W, E, N, S, PhotoImage, Checkbutton, Button, \ Menu, Frame, Label, Spinbox PROGRAM_NAME = ' Explosion Drum Machine ' MAX_NUMBER_OF_PATTERNS = 10 MAX_NUMBER_OF_DRUM_SAMPLES = 5 MAX_NUMBER_OF_UNITS = 5 MAX_BPU = 5 INITIAL_NUMBER_OF_UNITS = 4 INITIAL_BPU = 4 INITIAL_BEATS_PER_MINUTE = 240 MIN_BEATS_PER_MINUTE = 80 MAX_BEATS_PER_MINUTE = 360 COLOR_1 = 'grey55' COLOR_2 = 'khaki' BUTTON_CLICKED_COLOR = 'green' class DrumMachine: def __init__(self, root): self.root = root self.root.title(PROGRAM_NAME) self.all_patterns = [None] * MAX_NUMBER_OF_PATTERNS self.beats_per_minute = INITIAL_BEATS_PER_MINUTE self.current_pattern_index = 0 self.drum_load_entry_widget = [None] * MAX_NUMBER_OF_DRUM_SAMPLES self.init_all_patterns() self.init_gui() # # getters and setters begins # def get_current_pattern_dict(self): return self.all_patterns[self.current_pattern_index] def get_bpu(self): return self.get_current_pattern_dict()['bpu'] def set_bpu(self): self.get_current_pattern_dict()['bpu'] = int(self.bpu_widget.get()) def get_number_of_units(self): return self.get_current_pattern_dict()['number_of_units'] def set_number_of_units(self): self.get_current_pattern_dict( )['number_of_units'] = int(self.number_of_units_widget.get()) def get_list_of_drum_files(self): return self.get_current_pattern_dict()['list_of_drum_files'] def get_drum_file_path(self, drum_index): return self.get_list_of_drum_files()[drum_index] def set_drum_file_path(self, drum_index, file_path): self.get_list_of_drum_files()[drum_index] = file_path def get_is_button_clicked_list(self): return self.get_current_pattern_dict()['is_button_clicked_list'] def set_is_button_clicked_list(self, num_of_rows, num_of_columns): self.get_current_pattern_dict()['is_button_clicked_list'] = [ [False] * num_of_columns for x in range(num_of_rows)] def init_all_patterns(self): self.all_patterns = [ { 'list_of_drum_files': [None] * MAX_NUMBER_OF_DRUM_SAMPLES, 'number_of_units': INITIAL_NUMBER_OF_UNITS, 'bpu': INITIAL_BPU, 'is_button_clicked_list': self.init_is_button_clicked_list( MAX_NUMBER_OF_DRUM_SAMPLES, INITIAL_NUMBER_OF_UNITS * INITIAL_BPU ) } for k in range(MAX_NUMBER_OF_PATTERNS)] def on_pattern_changed(self): pass def on_number_of_units_changed(self): self.set_number_of_units() self.set_is_button_clicked_list(MAX_NUMBER_OF_DRUM_SAMPLES, self.find_number_of_columns()) self.create_right_button_matrix() def on_bpu_changed(self): self.set_bpu() self.set_is_button_clicked_list(MAX_NUMBER_OF_DRUM_SAMPLES, self.find_number_of_columns()) self.create_right_button_matrix() def on_open_file_button_clicked(self, drum_index): pass def on_play_button_clicked(self): pass def on_stop_button_clicked(self): pass def on_loop_button_toggled(self): pass def on_beats_per_minute_changed(self): pass def init_is_button_clicked_list(self, num_of_rows, num_of_columns): return [[False] * num_of_columns for x in range(num_of_rows)] def get_button_value(self, row, col): return self.all_patterns[self.current_pattern_index][ 'is_button_clicked_list'][row][col] def find_number_of_columns(self): return int(self.number_of_units_widget.get()) * int(self.bpu_widget.get()) def process_button_clicked(self, row, col): self.set_button_value(row, col, not self.get_button_value(row, col)) self.display_button_color(row, col) def set_button_value(self, row, col, bool_value): self.all_patterns[self.current_pattern_index][ 'is_button_clicked_list'][row][col] = bool_value def on_button_clicked(self, row, col): def event_handler(): self.process_button_clicked(row, col) return event_handler def display_all_button_colors(self): number_of_columns = self.find_number_of_columns() for r in range(MAX_NUMBER_OF_DRUM_SAMPLES): for c in range(number_of_columns): self.display_button_color(r, c) def display_button_color(self, row, col): bpu = int(self.bpu_widget.get()) original_color = COLOR_1 if ((col//bpu) % 2) else COLOR_2 button_color = BUTTON_CLICKED_COLOR if self.get_button_value( row, col) else original_color self.buttons[row][col].config(background=button_color) def create_play_bar(self): playbar_frame = Frame(self.root, height=15) start_row = MAX_NUMBER_OF_DRUM_SAMPLES + 10 playbar_frame.grid(row=start_row, columnspan=13, sticky=W + E, padx=15, pady=10) self.play_icon = PhotoImage(file="images/play.gif") self.play_button = Button( playbar_frame, text='Play', image=self.play_icon, compound='left', command=self.on_play_button_clicked) self.play_button.grid(row=start_row, column=1, padx=2) Button(playbar_frame, text='Stop', command=self.on_stop_button_clicked).grid( row=start_row, column=3, padx=2) self.loopbutton = Checkbutton( playbar_frame, text='Loop', command=self.on_loop_button_toggled) self.loopbutton.grid(row=start_row, column=16, padx=5) Label(playbar_frame, text='Beats Per Minute').grid( row=start_row, column=25) self.beats_per_minute_widget = Spinbox(playbar_frame, from_=MIN_BEATS_PER_MINUTE, to=MAX_BEATS_PER_MINUTE, width=5, increment=5.0, command=self.on_beats_per_minute_changed) self.beats_per_minute_widget.grid(row=start_row, column=30) self.beats_per_minute_widget.delete(0,"end") self.beats_per_minute_widget.insert(0,INITIAL_BEATS_PER_MINUTE) photo = PhotoImage(file='images/signature.gif') label = Label(playbar_frame, image=photo) label.image = photo label.grid(row=start_row, column=50, padx=1, sticky='w') def create_right_button_matrix(self): right_frame = Frame(self.root) right_frame.grid(row=10, column=6, sticky=W + E + N + S, padx=15, pady=4) self.buttons = [[None for x in range( self.find_number_of_columns())] for x in range(MAX_NUMBER_OF_DRUM_SAMPLES)] for row in range(MAX_NUMBER_OF_DRUM_SAMPLES): for col in range(self.find_number_of_columns()): self.buttons[row][col] = Button( right_frame, command=self.on_button_clicked(row, col)) self.buttons[row][col].grid(row=row, column=col) self.display_button_color(row, col) def create_left_drum_loader(self): left_frame = Frame(self.root) left_frame.grid(row=10, column=0, columnspan=6, sticky=W + E + N + S) open_file_icon = PhotoImage(file='images/openfile.gif') for i in range(MAX_NUMBER_OF_DRUM_SAMPLES): open_file_button = Button(left_frame, image=open_file_icon, command=self.on_open_file_button_clicked(i)) open_file_button.image = open_file_icon open_file_button.grid(row=i, column=0, padx=5, pady=4) self.drum_load_entry_widget[i] = Entry(left_frame) self.drum_load_entry_widget[i].grid( row=i, column=4, padx=7, pady=4) def create_top_bar(self): topbar_frame = Frame(self.root, height=25) topbar_frame.grid(row=0, columnspan=12, rowspan=10, padx=5, pady=5) Label(topbar_frame, text='Pattern Number:').grid(row=0, column=1) self.pattern_index_widget = Spinbox(topbar_frame, from_=0, to=MAX_NUMBER_OF_PATTERNS - 1, width=5, command=self.on_pattern_changed) self.pattern_index_widget.grid(row=0, column=2) self.current_pattern_name_widget = Entry(topbar_frame) self.current_pattern_name_widget.grid(row=0, column=3, padx=7, pady=2) Label(topbar_frame, text='Number of Units:').grid(row=0, column=4) self.number_of_units_widget = Spinbox(topbar_frame, from_=1, to=MAX_NUMBER_OF_UNITS, width=5, command=self.on_number_of_units_changed) self.number_of_units_widget.delete(0,"end") self.number_of_units_widget.insert(0,INITIAL_NUMBER_OF_UNITS) self.number_of_units_widget.grid(row=0, column=5) Label(topbar_frame, text='BPUs:').grid(row=0, column=6) self.bpu_widget = Spinbox(topbar_frame, from_=1, to=MAX_BPU, width=5, command=self.on_bpu_changed) self.bpu_widget.grid(row=0, column=7) self.bpu_widget.delete(0,"end") self.bpu_widget.insert(0,INITIAL_BPU) def create_top_menu(self): self.menu_bar = Menu(self.root) self.file_menu = Menu(self.menu_bar, tearoff=0) self.file_menu.add_command(label="Load Project") self.file_menu.add_command(label="Save Project") self.file_menu.add_separator() self.file_menu.add_command(label="Exit") self.menu_bar.add_cascade(label="File", menu=self.file_menu) self.about_menu = Menu(self.menu_bar, tearoff=0) self.about_menu.add_command(label="About") self.menu_bar.add_cascade(label="About", menu=self.about_menu) self.root.config(menu=self.menu_bar) def init_gui(self): self.create_top_menu() self.create_top_bar() self.create_left_drum_loader() self.create_right_button_matrix() self.create_play_bar() if __name__ == '__main__': root = Tk() DrumMachine(root) root.mainloop() ================================================ FILE: Chapter 03/3.05.py ================================================ """ Code illustration: 3.05 - Loading drum samples New modules imported here: - os, tkinter.filedialog New methods implemented here on_open_file_button_clicked(): display_all_drum_file_names(): display_drum_name(): Chapter 3 : Programmable Drum Machine Tkinter GUI Application Development Blueprints """ import os from tkinter import Tk, Entry, W, E, N, S, PhotoImage, Checkbutton, Button, \ Menu, Frame, Label, Spinbox, END from tkinter import filedialog PROGRAM_NAME = ' Explosion Drum Machine ' MAX_NUMBER_OF_PATTERNS = 10 MAX_NUMBER_OF_DRUM_SAMPLES = 5 MAX_NUMBER_OF_UNITS = 5 MAX_BPU = 5 INITIAL_NUMBER_OF_UNITS = 4 INITIAL_BPU = 4 INITIAL_BEATS_PER_MINUTE = 240 MIN_BEATS_PER_MINUTE = 80 MAX_BEATS_PER_MINUTE = 360 COLOR_1 = 'grey55' COLOR_2 = 'khaki' BUTTON_CLICKED_COLOR = 'green' class DrumMachine: def __init__(self, root): self.root = root self.root.title(PROGRAM_NAME) self.all_patterns = [None] * MAX_NUMBER_OF_PATTERNS self.beats_per_minute = INITIAL_BEATS_PER_MINUTE self.current_pattern_index = 0 self.drum_load_entry_widget = [None] * MAX_NUMBER_OF_DRUM_SAMPLES self.init_all_patterns() self.init_gui() def on_open_file_button_clicked(self, drum_index): def event_handler(): file_path = filedialog.askopenfilename(defaultextension=".wav", filetypes=[("Wave Files", "*.wav"), ("OGG Files", "*.ogg")]) if not file_path: return self.set_drum_file_path(drum_index, file_path) self.display_all_drum_file_names() return event_handler def display_all_drum_file_names(self): for i, drum_name in enumerate(self.get_list_of_drum_files()): self.display_drum_name(i, drum_name) def display_drum_name(self, text_widget_num, file_path): if file_path is None: return drum_name = os.path.basename(file_path) self.drum_load_entry_widget[text_widget_num].delete(0, END) self.drum_load_entry_widget[text_widget_num].insert(0, drum_name) # # getters and setters begins # def get_current_pattern_dict(self): return self.all_patterns[self.current_pattern_index] def get_bpu(self): return self.get_current_pattern_dict()['bpu'] def set_bpu(self): self.get_current_pattern_dict()['bpu'] = int(self.bpu_widget.get()) def get_number_of_units(self): return self.get_current_pattern_dict()['number_of_units'] def set_number_of_units(self): self.get_current_pattern_dict( )['number_of_units'] = int(self.number_of_units_widget.get()) def get_list_of_drum_files(self): return self.get_current_pattern_dict()['list_of_drum_files'] def get_drum_file_path(self, drum_index): return self.get_list_of_drum_files()[drum_index] def set_drum_file_path(self, drum_index, file_path): self.get_list_of_drum_files()[drum_index] = file_path def get_is_button_clicked_list(self): return self.get_current_pattern_dict()['is_button_clicked_list'] def set_is_button_clicked_list(self, num_of_rows, num_of_columns): self.get_current_pattern_dict()['is_button_clicked_list'] = [ [False] * num_of_columns for x in range(num_of_rows)] def init_all_patterns(self): self.all_patterns = [ { 'list_of_drum_files': [None] * MAX_NUMBER_OF_DRUM_SAMPLES, 'number_of_units': INITIAL_NUMBER_OF_UNITS, 'bpu': INITIAL_BPU, 'is_button_clicked_list': self.init_is_button_clicked_list( MAX_NUMBER_OF_DRUM_SAMPLES, INITIAL_NUMBER_OF_UNITS * INITIAL_BPU ) } for k in range(MAX_NUMBER_OF_PATTERNS)] def on_pattern_changed(self): pass def on_number_of_units_changed(self): self.set_number_of_units() self.set_is_button_clicked_list(MAX_NUMBER_OF_DRUM_SAMPLES, self.find_number_of_columns()) self.create_right_button_matrix() def on_bpu_changed(self): self.set_bpu() self.set_is_button_clicked_list(MAX_NUMBER_OF_DRUM_SAMPLES, self.find_number_of_columns()) self.create_right_button_matrix() def on_play_button_clicked(self): pass def on_stop_button_clicked(self): pass def on_loop_button_toggled(self): pass def on_beats_per_minute_changed(self): pass def init_is_button_clicked_list(self, num_of_rows, num_of_columns): return [[False] * num_of_columns for x in range(num_of_rows)] def get_button_value(self, row, col): return self.all_patterns[self.current_pattern_index][ 'is_button_clicked_list'][row][col] def find_number_of_columns(self): return int(self.number_of_units_widget.get()) * int(self.bpu_widget.get()) def process_button_clicked(self, row, col): self.set_button_value(row, col, not self.get_button_value(row, col)) self.display_button_color(row, col) def set_button_value(self, row, col, bool_value): self.all_patterns[self.current_pattern_index][ 'is_button_clicked_list'][row][col] = bool_value def on_button_clicked(self, row, col): def event_handler(): self.process_button_clicked(row, col) return event_handler def display_all_button_colors(self): number_of_columns = self.find_number_of_columns() for r in range(MAX_NUMBER_OF_DRUM_SAMPLES): for c in range(number_of_columns): self.display_button_color(r, c) def display_button_color(self, row, col): bpu = int(self.bpu_widget.get()) original_color = COLOR_1 if ((col//bpu) % 2) else COLOR_2 button_color = BUTTON_CLICKED_COLOR if self.get_button_value( row, col) else original_color self.buttons[row][col].config(background=button_color) def create_play_bar(self): playbar_frame = Frame(self.root, height=15) start_row = MAX_NUMBER_OF_DRUM_SAMPLES + 10 playbar_frame.grid(row=start_row, columnspan=13, sticky=W + E, padx=15, pady=10) self.play_icon = PhotoImage(file="images/play.gif") self.play_button = Button( playbar_frame, text='Play', image=self.play_icon, compound='left', command=self.on_play_button_clicked) self.play_button.grid(row=start_row, column=1, padx=2) Button(playbar_frame, text='Stop', command=self.on_stop_button_clicked).grid( row=start_row, column=3, padx=2) self.loopbutton = Checkbutton( playbar_frame, text='Loop', command=self.on_loop_button_toggled) self.loopbutton.grid(row=start_row, column=16, padx=5) Label(playbar_frame, text='Beats Per Minute').grid( row=start_row, column=25) self.beats_per_minute_widget = Spinbox(playbar_frame, from_=MIN_BEATS_PER_MINUTE, to=MAX_BEATS_PER_MINUTE, width=5, increment=5.0, command=self.on_beats_per_minute_changed) self.beats_per_minute_widget.grid(row=start_row, column=30) self.beats_per_minute_widget.delete(0,"end") self.beats_per_minute_widget.insert(0,INITIAL_BEATS_PER_MINUTE) photo = PhotoImage(file='images/signature.gif') label = Label(playbar_frame, image=photo) label.image = photo label.grid(row=start_row, column=50, padx=1, sticky='w') def create_right_button_matrix(self): right_frame = Frame(self.root) right_frame.grid(row=10, column=6, sticky=W + E + N + S, padx=15, pady=4) self.buttons = [[None for x in range( self.find_number_of_columns())] for x in range(MAX_NUMBER_OF_DRUM_SAMPLES)] for row in range(MAX_NUMBER_OF_DRUM_SAMPLES): for col in range(self.find_number_of_columns()): self.buttons[row][col] = Button( right_frame, command=self.on_button_clicked(row, col)) self.buttons[row][col].grid(row=row, column=col) self.display_button_color(row, col) def create_left_drum_loader(self): left_frame = Frame(self.root) left_frame.grid(row=10, column=0, columnspan=6, sticky=W + E + N + S) open_file_icon = PhotoImage(file='images/openfile.gif') for i in range(MAX_NUMBER_OF_DRUM_SAMPLES): open_file_button = Button(left_frame, image=open_file_icon, command=self.on_open_file_button_clicked(i)) open_file_button.image = open_file_icon open_file_button.grid(row=i, column=0, padx=5, pady=4) self.drum_load_entry_widget[i] = Entry(left_frame) self.drum_load_entry_widget[i].grid( row=i, column=4, padx=7, pady=4) def create_top_bar(self): topbar_frame = Frame(self.root, height=25) topbar_frame.grid(row=0, columnspan=12, rowspan=10, padx=5, pady=5) Label(topbar_frame, text='Pattern Number:').grid(row=0, column=1) self.pattern_index_widget = Spinbox(topbar_frame, from_=0, to=MAX_NUMBER_OF_PATTERNS - 1, width=5, command=self.on_pattern_changed) self.pattern_index_widget.grid(row=0, column=2) self.current_pattern_name_widget = Entry(topbar_frame) self.current_pattern_name_widget.grid(row=0, column=3, padx=7, pady=2) Label(topbar_frame, text='Number of Units:').grid(row=0, column=4) self.number_of_units_widget = Spinbox(topbar_frame, from_=1, to=MAX_NUMBER_OF_UNITS, width=5, command=self.on_number_of_units_changed) self.number_of_units_widget.delete(0,"end") self.number_of_units_widget.insert(0,INITIAL_NUMBER_OF_UNITS) self.number_of_units_widget.grid(row=0, column=5) Label(topbar_frame, text='BPUs:').grid(row=0, column=6) self.bpu_widget = Spinbox(topbar_frame, from_=1, to=MAX_BPU, width=5, command=self.on_bpu_changed) self.bpu_widget.grid(row=0, column=7) self.bpu_widget.delete(0,"end") self.bpu_widget.insert(0,INITIAL_BPU) def create_top_menu(self): self.menu_bar = Menu(self.root) self.file_menu = Menu(self.menu_bar, tearoff=0) self.file_menu.add_command(label="Load Project") self.file_menu.add_command(label="Save Project") self.file_menu.add_separator() self.file_menu.add_command(label="Exit") self.menu_bar.add_cascade(label="File", menu=self.file_menu) self.about_menu = Menu(self.menu_bar, tearoff=0) self.about_menu.add_command(label="About") self.menu_bar.add_cascade(label="About", menu=self.about_menu) self.root.config(menu=self.menu_bar) def init_gui(self): self.create_top_menu() self.create_top_bar() self.create_left_drum_loader() self.create_right_button_matrix() self.create_play_bar() if __name__ == '__main__': root = Tk() DrumMachine(root) root.mainloop() ================================================ FILE: Chapter 03/3.06.py ================================================ """ ****** Non reponsive warning ! ****** - This program becomes non responsive on playback of audio files. - It is meant to be that way as a demonstration. - You can close the program by stopping the python script ************************************* Code illustration: 3.06 Playing Audio New Modules Imported Here - import pygame - import time Attributes Added here: - self.loop - self.now_playing New Methods defined here: init_pygame() play_sound() start_play() stop_play() play_pattern() time_to_play_each_column() on_beats_per_minute_changed() get_column_from_matrix() Dummy Methods that existed earlier but modified here: on_loop_button_toggled() on_play_button_clicked() on_stop_button_clicked() Chapter 3 : Programmable Drum Machine Tkinter GUI Application Development Blueprints """ import os import time from tkinter import Tk, Entry, W, E, N, S, PhotoImage, Checkbutton, Button, \ Menu, Frame, Label, Spinbox, END, BooleanVar from tkinter import filedialog import pygame PROGRAM_NAME = ' Explosion Drum Machine ' MAX_NUMBER_OF_PATTERNS = 10 MAX_NUMBER_OF_DRUM_SAMPLES = 5 MAX_NUMBER_OF_UNITS = 5 MAX_BPU = 5 INITIAL_NUMBER_OF_UNITS = 4 INITIAL_BPU = 4 INITIAL_BEATS_PER_MINUTE = 240 MIN_BEATS_PER_MINUTE = 80 MAX_BEATS_PER_MINUTE = 360 COLOR_1 = 'grey55' COLOR_2 = 'khaki' BUTTON_CLICKED_COLOR = 'green' class DrumMachine: def __init__(self, root): self.root = root self.root.title(PROGRAM_NAME) self.all_patterns = [None] * MAX_NUMBER_OF_PATTERNS self.beats_per_minute = INITIAL_BEATS_PER_MINUTE self.current_pattern_index = 0 self.loop = True self.now_playing = False self.drum_load_entry_widget = [None] * MAX_NUMBER_OF_DRUM_SAMPLES self.init_all_patterns() self.init_gui() def on_open_file_button_clicked(self, drum_index): def event_handler(): file_path = filedialog.askopenfilename(defaultextension=".wav", filetypes=[("Wave Files", "*.wav"), ("OGG Files", "*.ogg")]) if not file_path: return self.set_drum_file_path(drum_index, file_path) self.display_all_drum_file_names() return event_handler def display_all_drum_file_names(self): for i, drum_name in enumerate(self.get_list_of_drum_files()): self.display_drum_name(i, drum_name) def display_drum_name(self, text_widget_num, file_path): if file_path is None: return drum_name = os.path.basename(file_path) self.drum_load_entry_widget[text_widget_num].delete(0, END) self.drum_load_entry_widget[text_widget_num].insert(0, drum_name) # # getters and setters begins # def get_current_pattern_dict(self): return self.all_patterns[self.current_pattern_index] def get_bpu(self): return self.get_current_pattern_dict()['bpu'] def set_bpu(self): self.get_current_pattern_dict()['bpu'] = int(self.bpu_widget.get()) def get_number_of_units(self): return self.get_current_pattern_dict()['number_of_units'] def set_number_of_units(self): self.get_current_pattern_dict( )['number_of_units'] = int(self.number_of_units_widget.get()) def get_list_of_drum_files(self): return self.get_current_pattern_dict()['list_of_drum_files'] def get_drum_file_path(self, drum_index): return self.get_list_of_drum_files()[drum_index] def set_drum_file_path(self, drum_index, file_path): self.get_list_of_drum_files()[drum_index] = file_path def get_is_button_clicked_list(self): return self.get_current_pattern_dict()['is_button_clicked_list'] def set_is_button_clicked_list(self, num_of_rows, num_of_columns): self.get_current_pattern_dict()['is_button_clicked_list'] = [ [False] * num_of_columns for x in range(num_of_rows)] def init_all_patterns(self): self.all_patterns = [ { 'list_of_drum_files': [None] * MAX_NUMBER_OF_DRUM_SAMPLES, 'number_of_units': INITIAL_NUMBER_OF_UNITS, 'bpu': INITIAL_BPU, 'is_button_clicked_list': self.init_is_button_clicked_list( MAX_NUMBER_OF_DRUM_SAMPLES, INITIAL_NUMBER_OF_UNITS * INITIAL_BPU ) } for k in range(MAX_NUMBER_OF_PATTERNS)] def on_pattern_changed(self): pass def on_number_of_units_changed(self): self.set_number_of_units() self.set_is_button_clicked_list(MAX_NUMBER_OF_DRUM_SAMPLES, self.find_number_of_columns()) self.create_right_button_matrix() def on_bpu_changed(self): self.set_bpu() self.set_is_button_clicked_list(MAX_NUMBER_OF_DRUM_SAMPLES, self.find_number_of_columns()) self.create_right_button_matrix() def on_play_button_clicked(self): self.start_play() def start_play(self): self.init_pygame() self.play_pattern() def on_stop_button_clicked(self): self.stop_play() def stop_play(self): self.now_playing = False def init_pygame(self): pygame.mixer.pre_init(44100, -16, 1, 512) pygame.init() def play_sound(self, sound_filename): if sound_filename is not None: pygame.mixer.Sound(sound_filename).play() def get_column_from_matrix(self, matrix, i): return [row[i] for row in matrix] def play_pattern(self): self.now_playing = True while self.now_playing: play_list = self.get_is_button_clicked_list() num_columns = len(play_list[0]) for column_index in range(num_columns): column_to_play = self.get_column_from_matrix( play_list, column_index) for i, item in enumerate(column_to_play): if item: sound_filename = self.get_drum_file_path(i) self.play_sound(sound_filename) time.sleep(self.time_to_play_each_column()) if not self.now_playing: break if not self.loop: break self.now_playing = False def time_to_play_each_column(self): beats_per_second = self.beats_per_minute / 60 time_to_play_each_column = 1 / beats_per_second return time_to_play_each_column def on_loop_button_toggled(self): self.loop = self.loopbuttonvar.get() def on_beats_per_minute_changed(self): self.beats_per_minute = int(self.beats_per_minute_widget.get()) def init_is_button_clicked_list(self, num_of_rows, num_of_columns): return [[False] * num_of_columns for x in range(num_of_rows)] def get_button_value(self, row, col): return self.all_patterns[self.current_pattern_index][ 'is_button_clicked_list'][row][col] def find_number_of_columns(self): return int(self.number_of_units_widget.get()) * int(self.bpu_widget.get()) def process_button_clicked(self, row, col): self.set_button_value(row, col, not self.get_button_value(row, col)) self.display_button_color(row, col) def set_button_value(self, row, col, bool_value): self.all_patterns[self.current_pattern_index][ 'is_button_clicked_list'][row][col] = bool_value def on_button_clicked(self, row, col): def event_handler(): self.process_button_clicked(row, col) return event_handler def display_all_button_colors(self): number_of_columns = self.find_number_of_columns() for r in range(MAX_NUMBER_OF_DRUM_SAMPLES): for c in range(number_of_columns): self.display_button_color(r, c) def display_button_color(self, row, col): bpu = int(self.bpu_widget.get()) original_color = COLOR_1 if ((col//bpu) % 2) else COLOR_2 button_color = BUTTON_CLICKED_COLOR if self.get_button_value( row, col) else original_color self.buttons[row][col].config(background=button_color) def create_play_bar(self): playbar_frame = Frame(self.root, height=15) start_row = MAX_NUMBER_OF_DRUM_SAMPLES + 10 playbar_frame.grid(row=start_row, columnspan=13, sticky=W + E, padx=15, pady=10) self.play_icon = PhotoImage(file="images/play.gif") self.play_button = Button( playbar_frame, text='Play', image=self.play_icon, compound='left', command=self.on_play_button_clicked) self.play_button.grid(row=start_row, column=1, padx=2) Button(playbar_frame, text='Stop', command=self.on_stop_button_clicked).grid( row=start_row, column=3, padx=2) self.loopbuttonvar = BooleanVar() self.loopbuttonvar.set(True) self.loopbutton = Checkbutton( playbar_frame, text='Loop', command=self.on_loop_button_toggled, variable=self.loopbuttonvar) self.loopbutton.grid(row=start_row, column=16, padx=5) Label(playbar_frame, text='Beats Per Minute').grid( row=start_row, column=25) self.beats_per_minute_widget = Spinbox(playbar_frame, from_=MIN_BEATS_PER_MINUTE, to=MAX_BEATS_PER_MINUTE, width=5, increment=5.0, command=self.on_beats_per_minute_changed) self.beats_per_minute_widget.grid(row=start_row, column=30) self.beats_per_minute_widget.delete(0,"end") self.beats_per_minute_widget.insert(0,INITIAL_BEATS_PER_MINUTE) photo = PhotoImage(file='images/signature.gif') label = Label(playbar_frame, image=photo) label.image = photo label.grid(row=start_row, column=50, padx=1, sticky='w') def create_right_button_matrix(self): right_frame = Frame(self.root) right_frame.grid(row=10, column=6, sticky=W + E + N + S, padx=15, pady=4) self.buttons = [[None for x in range( self.find_number_of_columns())] for x in range(MAX_NUMBER_OF_DRUM_SAMPLES)] for row in range(MAX_NUMBER_OF_DRUM_SAMPLES): for col in range(self.find_number_of_columns()): self.buttons[row][col] = Button( right_frame, command=self.on_button_clicked(row, col)) self.buttons[row][col].grid(row=row, column=col) self.display_button_color(row, col) def create_left_drum_loader(self): left_frame = Frame(self.root) left_frame.grid(row=10, column=0, columnspan=6, sticky=W + E + N + S) open_file_icon = PhotoImage(file='images/openfile.gif') for i in range(MAX_NUMBER_OF_DRUM_SAMPLES): open_file_button = Button(left_frame, image=open_file_icon, command=self.on_open_file_button_clicked(i)) open_file_button.image = open_file_icon open_file_button.grid(row=i, column=0, padx=5, pady=4) self.drum_load_entry_widget[i] = Entry(left_frame) self.drum_load_entry_widget[i].grid( row=i, column=4, padx=7, pady=4) def create_top_bar(self): topbar_frame = Frame(self.root, height=25) topbar_frame.grid(row=0, columnspan=12, rowspan=10, padx=5, pady=5) Label(topbar_frame, text='Pattern Number:').grid(row=0, column=1) self.pattern_index_widget = Spinbox(topbar_frame, from_=0, to=MAX_NUMBER_OF_PATTERNS - 1, width=5, command=self.on_pattern_changed) self.pattern_index_widget.grid(row=0, column=2) self.current_pattern_name_widget = Entry(topbar_frame) self.current_pattern_name_widget.grid(row=0, column=3, padx=7, pady=2) Label(topbar_frame, text='Number of Units:').grid(row=0, column=4) self.number_of_units_widget = Spinbox(topbar_frame, from_=1, to=MAX_NUMBER_OF_UNITS, width=5, command=self.on_number_of_units_changed) self.number_of_units_widget.delete(0,"end") self.number_of_units_widget.insert(0,INITIAL_NUMBER_OF_UNITS) self.number_of_units_widget.grid(row=0, column=5) Label(topbar_frame, text='BPUs:').grid(row=0, column=6) self.bpu_widget = Spinbox(topbar_frame, from_=1, to=MAX_BPU, width=5, command=self.on_bpu_changed) self.bpu_widget.grid(row=0, column=7) self.bpu_widget.delete(0,"end") self.bpu_widget.insert(0,INITIAL_BPU) def create_top_menu(self): self.menu_bar = Menu(self.root) self.file_menu = Menu(self.menu_bar, tearoff=0) self.file_menu.add_command(label="Load Project") self.file_menu.add_command(label="Save Project") self.file_menu.add_separator() self.file_menu.add_command(label="Exit") self.menu_bar.add_cascade(label="File", menu=self.file_menu) self.about_menu = Menu(self.menu_bar, tearoff=0) self.about_menu.add_command(label="About") self.menu_bar.add_cascade(label="About", menu=self.about_menu) self.root.config(menu=self.menu_bar) def init_gui(self): self.create_top_menu() self.create_top_bar() self.create_left_drum_loader() self.create_right_button_matrix() self.create_play_bar() if __name__ == '__main__': root = Tk() DrumMachine(root) root.mainloop() ================================================ FILE: Chapter 03/3.07.py ================================================ """ Code illustration: 3.07 Tkinter and Threading ********************** New modules imported here: - threading New methods defined here: - play_in_thread() - toggle_play_button_state() Method modified here: - start_play() - __init__ method - to override the close button - on_play_button_clicked() - on_stop_button_clicked() - on_loop_button_toggled() - play_pattern() - added a call to toggle_play_button_state() Chapter 3 : Programmable Drum Machine Tkinter GUI Application Development Blueprints """ import os import time import threading from tkinter import Tk, Entry, W, E, N, S, PhotoImage, Checkbutton, Button, \ Menu, Frame, Label, Spinbox, END, BooleanVar from tkinter import filedialog import pygame PROGRAM_NAME = ' Explosion Drum Machine ' MAX_NUMBER_OF_PATTERNS = 10 MAX_NUMBER_OF_DRUM_SAMPLES = 5 MAX_NUMBER_OF_UNITS = 5 MAX_BPU = 5 INITIAL_NUMBER_OF_UNITS = 4 INITIAL_BPU = 4 INITIAL_BEATS_PER_MINUTE = 240 MIN_BEATS_PER_MINUTE = 80 MAX_BEATS_PER_MINUTE = 360 COLOR_1 = 'grey55' COLOR_2 = 'khaki' BUTTON_CLICKED_COLOR = 'green' class DrumMachine: def __init__(self, root): self.root = root self.root.title(PROGRAM_NAME) self.all_patterns = [None] * MAX_NUMBER_OF_PATTERNS self.beats_per_minute = INITIAL_BEATS_PER_MINUTE self.current_pattern_index = 0 self.loop = True self.now_playing = False self.drum_load_entry_widget = [None] * MAX_NUMBER_OF_DRUM_SAMPLES self.init_all_patterns() self.init_gui() def on_open_file_button_clicked(self, drum_index): def event_handler(): file_path = filedialog.askopenfilename(defaultextension=".wav", filetypes=[("Wave Files", "*.wav"), ("OGG Files", "*.ogg")]) if not file_path: return self.set_drum_file_path(drum_index, file_path) self.display_all_drum_file_names() return event_handler def display_all_drum_file_names(self): for i, drum_name in enumerate(self.get_list_of_drum_files()): self.display_drum_name(i, drum_name) def display_drum_name(self, text_widget_num, file_path): if file_path is None: return drum_name = os.path.basename(file_path) self.drum_load_entry_widget[text_widget_num].delete(0, END) self.drum_load_entry_widget[text_widget_num].insert(0, drum_name) # # getters and setters begins # def get_current_pattern_dict(self): return self.all_patterns[self.current_pattern_index] def get_bpu(self): return self.get_current_pattern_dict()['bpu'] def set_bpu(self): self.get_current_pattern_dict()['bpu'] = int(self.bpu_widget.get()) def get_number_of_units(self): return self.get_current_pattern_dict()['number_of_units'] def set_number_of_units(self): self.get_current_pattern_dict( )['number_of_units'] = int(self.number_of_units_widget.get()) def get_list_of_drum_files(self): return self.get_current_pattern_dict()['list_of_drum_files'] def get_drum_file_path(self, drum_index): return self.get_list_of_drum_files()[drum_index] def set_drum_file_path(self, drum_index, file_path): self.get_list_of_drum_files()[drum_index] = file_path def get_is_button_clicked_list(self): return self.get_current_pattern_dict()['is_button_clicked_list'] def set_is_button_clicked_list(self, num_of_rows, num_of_columns): self.get_current_pattern_dict()['is_button_clicked_list'] = [ [False] * num_of_columns for x in range(num_of_rows)] def init_all_patterns(self): self.all_patterns = [ { 'list_of_drum_files': [None] * MAX_NUMBER_OF_DRUM_SAMPLES, 'number_of_units': INITIAL_NUMBER_OF_UNITS, 'bpu': INITIAL_BPU, 'is_button_clicked_list': self.init_is_button_clicked_list( MAX_NUMBER_OF_DRUM_SAMPLES, INITIAL_NUMBER_OF_UNITS * INITIAL_BPU ) } for k in range(MAX_NUMBER_OF_PATTERNS)] def on_pattern_changed(self): pass def on_number_of_units_changed(self): self.set_number_of_units() self.set_is_button_clicked_list(MAX_NUMBER_OF_DRUM_SAMPLES, self.find_number_of_columns()) self.create_right_button_matrix() def on_bpu_changed(self): self.set_bpu() self.set_is_button_clicked_list(MAX_NUMBER_OF_DRUM_SAMPLES, self.find_number_of_columns()) self.create_right_button_matrix() def play_in_thread(self): self.thread = threading.Thread(target = self.play_pattern) self.thread.start() def on_play_button_clicked(self): self.start_play() self.toggle_play_button_state() def start_play(self): self.init_pygame() self.play_in_thread() def on_stop_button_clicked(self): self.stop_play() self.toggle_play_button_state() def toggle_play_button_state(self): if self.now_playing: self.play_button.config(state="disabled") else: self.play_button.config(state="normal") def exit_app(self): self.now_playing = False if messagebox.askokcancel("Quit", "Really quit?"): self.root.destroy() def stop_play(self): self.now_playing = False def init_pygame(self): pygame.mixer.pre_init(44100, -16, 1, 512) pygame.init() def play_sound(self, sound_filename): if sound_filename is not None: pygame.mixer.Sound(sound_filename).play() def get_column_from_matrix(self, matrix, i): return [row[i] for row in matrix] def play_pattern(self): self.now_playing = True self.toggle_play_button_state() while self.now_playing: play_list = self.get_is_button_clicked_list() num_columns = len(play_list[0]) for column_index in range(num_columns): column_to_play = self.get_column_from_matrix( play_list, column_index) for i, item in enumerate(column_to_play): if item: sound_filename = self.get_drum_file_path(i) self.play_sound(sound_filename) time.sleep(self.time_to_play_each_column()) if not self.now_playing: break if not self.loop: break self.now_playing = False self.toggle_play_button_state() def time_to_play_each_column(self): beats_per_second = self.beats_per_minute / 60 time_to_play_each_column = 1 / beats_per_second return time_to_play_each_column def on_loop_button_toggled(self): self.loop = self.loopbuttonvar.get() def on_beats_per_minute_changed(self): self.beats_per_minute = int(self.beats_per_minute_widget.get()) def init_is_button_clicked_list(self, num_of_rows, num_of_columns): return [[False] * num_of_columns for x in range(num_of_rows)] def get_button_value(self, row, col): return self.all_patterns[self.current_pattern_index][ 'is_button_clicked_list'][row][col] def find_number_of_columns(self): return int(self.number_of_units_widget.get()) * int(self.bpu_widget.get()) def process_button_clicked(self, row, col): self.set_button_value(row, col, not self.get_button_value(row, col)) self.display_button_color(row, col) def set_button_value(self, row, col, bool_value): self.all_patterns[self.current_pattern_index][ 'is_button_clicked_list'][row][col] = bool_value def on_button_clicked(self, row, col): def event_handler(): self.process_button_clicked(row, col) return event_handler def display_all_button_colors(self): number_of_columns = self.find_number_of_columns() for r in range(MAX_NUMBER_OF_DRUM_SAMPLES): for c in range(number_of_columns): self.display_button_color(r, c) def display_button_color(self, row, col): bpu = int(self.bpu_widget.get()) original_color = COLOR_1 if ((col//bpu) % 2) else COLOR_2 button_color = BUTTON_CLICKED_COLOR if self.get_button_value( row, col) else original_color self.buttons[row][col].config(background=button_color) def create_play_bar(self): playbar_frame = Frame(self.root, height=15) start_row = MAX_NUMBER_OF_DRUM_SAMPLES + 10 playbar_frame.grid(row=start_row, columnspan=13, sticky=W + E, padx=15, pady=10) self.play_icon = PhotoImage(file="images/play.gif") self.play_button = Button( playbar_frame, text='Play', image=self.play_icon, compound='left', command=self.on_play_button_clicked) self.play_button.grid(row=start_row, column=1, padx=2) Button(playbar_frame, text='Stop', command=self.on_stop_button_clicked).grid( row=start_row, column=3, padx=2) self.loopbuttonvar = BooleanVar() self.loopbuttonvar.set(True) self.loopbutton = Checkbutton( playbar_frame, text='Loop', command=self.on_loop_button_toggled, variable=self.loopbuttonvar) self.loopbutton.grid(row=start_row, column=16, padx=5) Label(playbar_frame, text='Beats Per Minute').grid( row=start_row, column=25) self.beats_per_minute_widget = Spinbox(playbar_frame, from_=MIN_BEATS_PER_MINUTE, to=MAX_BEATS_PER_MINUTE, width=5, increment=5.0, command=self.on_beats_per_minute_changed) self.beats_per_minute_widget.grid(row=start_row, column=30) self.beats_per_minute_widget.delete(0,"end") self.beats_per_minute_widget.insert(0,INITIAL_BEATS_PER_MINUTE) photo = PhotoImage(file='images/signature.gif') label = Label(playbar_frame, image=photo) label.image = photo label.grid(row=start_row, column=50, padx=1, sticky='w') def create_right_button_matrix(self): right_frame = Frame(self.root) right_frame.grid(row=10, column=6, sticky=W + E + N + S, padx=15, pady=4) self.buttons = [[None for x in range( self.find_number_of_columns())] for x in range(MAX_NUMBER_OF_DRUM_SAMPLES)] for row in range(MAX_NUMBER_OF_DRUM_SAMPLES): for col in range(self.find_number_of_columns()): self.buttons[row][col] = Button( right_frame, command=self.on_button_clicked(row, col)) self.buttons[row][col].grid(row=row, column=col) self.display_button_color(row, col) def create_left_drum_loader(self): left_frame = Frame(self.root) left_frame.grid(row=10, column=0, columnspan=6, sticky=W + E + N + S) open_file_icon = PhotoImage(file='images/openfile.gif') for i in range(MAX_NUMBER_OF_DRUM_SAMPLES): open_file_button = Button(left_frame, image=open_file_icon, command=self.on_open_file_button_clicked(i)) open_file_button.image = open_file_icon open_file_button.grid(row=i, column=0, padx=5, pady=4) self.drum_load_entry_widget[i] = Entry(left_frame) self.drum_load_entry_widget[i].grid( row=i, column=4, padx=7, pady=4) def create_top_bar(self): topbar_frame = Frame(self.root, height=25) topbar_frame.grid(row=0, columnspan=12, rowspan=10, padx=5, pady=5) Label(topbar_frame, text='Pattern Number:').grid(row=0, column=1) self.pattern_index_widget = Spinbox(topbar_frame, from_=0, to=MAX_NUMBER_OF_PATTERNS - 1, width=5, command=self.on_pattern_changed) self.pattern_index_widget.grid(row=0, column=2) self.current_pattern_name_widget = Entry(topbar_frame) self.current_pattern_name_widget.grid(row=0, column=3, padx=7, pady=2) Label(topbar_frame, text='Number of Units:').grid(row=0, column=4) self.number_of_units_widget = Spinbox(topbar_frame, from_=1, to=MAX_NUMBER_OF_UNITS, width=5, command=self.on_number_of_units_changed) self.number_of_units_widget.delete(0,"end") self.number_of_units_widget.insert(0,INITIAL_NUMBER_OF_UNITS) self.number_of_units_widget.grid(row=0, column=5) Label(topbar_frame, text='BPUs:').grid(row=0, column=6) self.bpu_widget = Spinbox(topbar_frame, from_=1, to=MAX_BPU, width=5, command=self.on_bpu_changed) self.bpu_widget.grid(row=0, column=7) self.bpu_widget.delete(0,"end") self.bpu_widget.insert(0,INITIAL_BPU) def create_top_menu(self): self.menu_bar = Menu(self.root) self.file_menu = Menu(self.menu_bar, tearoff=0) self.file_menu.add_command(label="Load Project") self.file_menu.add_command(label="Save Project") self.file_menu.add_separator() self.file_menu.add_command(label="Exit") self.menu_bar.add_cascade(label="File", menu=self.file_menu) self.about_menu = Menu(self.menu_bar, tearoff=0) self.about_menu.add_command(label="About") self.menu_bar.add_cascade(label="About", menu=self.about_menu) self.root.config(menu=self.menu_bar) def init_gui(self): self.create_top_menu() self.create_top_bar() self.create_left_drum_loader() self.create_right_button_matrix() self.create_play_bar() if __name__ == '__main__': root = Tk() DrumMachine(root) root.mainloop() ================================================ FILE: Chapter 03/3.08.py ================================================ """ Code illustration: 3.08 Adding support for multiple beat patterns New methods added here: - display_pattern_name() - change_pattern() - restart_play_of_new_pattern Methods modified here: - create_top_bar() - added a call to display_pattern_name() - on_pattern_changed() Chapter 3 : Programmable Drum Machine Tkinter GUI Application Development Blueprints """ import os import time import threading from tkinter import Tk, Entry, W, E, N, S, PhotoImage, Checkbutton, Button, \ Menu, Frame, Label, Spinbox, END, BooleanVar from tkinter import filedialog import pygame PROGRAM_NAME = ' Explosion Drum Machine ' MAX_NUMBER_OF_PATTERNS = 10 MAX_NUMBER_OF_DRUM_SAMPLES = 5 MAX_NUMBER_OF_UNITS = 5 MAX_BPU = 5 INITIAL_NUMBER_OF_UNITS = 4 INITIAL_BPU = 4 INITIAL_BEATS_PER_MINUTE = 240 MIN_BEATS_PER_MINUTE = 80 MAX_BEATS_PER_MINUTE = 360 COLOR_1 = 'grey55' COLOR_2 = 'khaki' BUTTON_CLICKED_COLOR = 'green' class DrumMachine: def __init__(self, root): self.root = root self.root.title(PROGRAM_NAME) self.all_patterns = [None] * MAX_NUMBER_OF_PATTERNS self.beats_per_minute = INITIAL_BEATS_PER_MINUTE self.current_pattern_index = 0 self.loop = True self.now_playing = False self.drum_load_entry_widget = [None] * MAX_NUMBER_OF_DRUM_SAMPLES self.init_all_patterns() self.init_gui() def on_open_file_button_clicked(self, drum_index): def event_handler(): file_path = filedialog.askopenfilename(defaultextension=".wav", filetypes=[("Wave Files", "*.wav"), ("OGG Files", "*.ogg")]) if not file_path: return self.set_drum_file_path(drum_index, file_path) self.display_all_drum_file_names() return event_handler def display_all_drum_file_names(self): for i, drum_name in enumerate(self.get_list_of_drum_files()): self.display_drum_name(i, drum_name) def display_drum_name(self, text_widget_num, file_path): if file_path is None: return drum_name = os.path.basename(file_path) self.drum_load_entry_widget[text_widget_num].delete(0, END) self.drum_load_entry_widget[text_widget_num].insert(0, drum_name) # # getters and setters begins # def get_current_pattern_dict(self): return self.all_patterns[self.current_pattern_index] def get_bpu(self): return self.get_current_pattern_dict()['bpu'] def set_bpu(self): self.get_current_pattern_dict()['bpu'] = int(self.bpu_widget.get()) def get_number_of_units(self): return self.get_current_pattern_dict()['number_of_units'] def set_number_of_units(self): self.get_current_pattern_dict( )['number_of_units'] = int(self.number_of_units_widget.get()) def get_list_of_drum_files(self): return self.get_current_pattern_dict()['list_of_drum_files'] def get_drum_file_path(self, drum_index): return self.get_list_of_drum_files()[drum_index] def set_drum_file_path(self, drum_index, file_path): self.get_list_of_drum_files()[drum_index] = file_path def get_is_button_clicked_list(self): return self.get_current_pattern_dict()['is_button_clicked_list'] def set_is_button_clicked_list(self, num_of_rows, num_of_columns): self.get_current_pattern_dict()['is_button_clicked_list'] = [ [False] * num_of_columns for x in range(num_of_rows)] def init_all_patterns(self): self.all_patterns = [ { 'list_of_drum_files': [None] * MAX_NUMBER_OF_DRUM_SAMPLES, 'number_of_units': INITIAL_NUMBER_OF_UNITS, 'bpu': INITIAL_BPU, 'is_button_clicked_list': self.init_is_button_clicked_list( MAX_NUMBER_OF_DRUM_SAMPLES, INITIAL_NUMBER_OF_UNITS * INITIAL_BPU ) } for k in range(MAX_NUMBER_OF_PATTERNS)] def display_pattern_name(self): self.current_pattern_name_widget.config(state='normal') self.current_pattern_name_widget.delete(0, 'end') self.current_pattern_name_widget.insert(0, 'Pattern {}'.format(self.current_pattern_index)) self.current_pattern_name_widget.config(state='readonly') def on_pattern_changed(self): self.change_pattern() def change_pattern(self): self.current_pattern_index = int(self.pattern_index_widget.get()) self.display_pattern_name() self.create_left_drum_loader() self.display_all_drum_file_names() self.create_right_button_matrix() self.display_all_button_colors() def on_number_of_units_changed(self): self.set_number_of_units() self.set_is_button_clicked_list(MAX_NUMBER_OF_DRUM_SAMPLES, self.find_number_of_columns()) self.create_right_button_matrix() def on_bpu_changed(self): self.set_bpu() self.set_is_button_clicked_list(MAX_NUMBER_OF_DRUM_SAMPLES, self.find_number_of_columns()) self.create_right_button_matrix() def play_in_thread(self): self.thread = threading.Thread(target = self.play_pattern) self.thread.start() def on_play_button_clicked(self): self.start_play() self.toggle_play_button_state() def start_play(self): self.init_pygame() self.play_in_thread() def on_stop_button_clicked(self): self.stop_play() self.toggle_play_button_state() def toggle_play_button_state(self): if self.now_playing: self.play_button.config(state="disabled") else: self.play_button.config(state="normal") def exit_app(self): self.now_playing = False if messagebox.askokcancel("Quit", "Really quit?"): self.root.destroy() def stop_play(self): self.now_playing = False def init_pygame(self): pygame.mixer.pre_init(44100, -16, 1, 512) pygame.init() def play_sound(self, sound_filename): if sound_filename is not None: pygame.mixer.Sound(sound_filename).play() def get_column_from_matrix(self, matrix, i): return [row[i] for row in matrix] def play_pattern(self): self.now_playing = True self.toggle_play_button_state() while self.now_playing: play_list = self.get_is_button_clicked_list() num_columns = len(play_list[0]) for column_index in range(num_columns): column_to_play = self.get_column_from_matrix( play_list, column_index) for i, item in enumerate(column_to_play): if item: sound_filename = self.get_drum_file_path(i) self.play_sound(sound_filename) time.sleep(self.time_to_play_each_column()) if not self.now_playing: break if not self.loop: break self.now_playing = False self.toggle_play_button_state() def time_to_play_each_column(self): beats_per_second = self.beats_per_minute / 60 time_to_play_each_column = 1 / beats_per_second return time_to_play_each_column def on_loop_button_toggled(self): self.loop = self.loopbuttonvar.get() def on_beats_per_minute_changed(self): self.beats_per_minute = int(self.beats_per_minute_widget.get()) def init_is_button_clicked_list(self, num_of_rows, num_of_columns): return [[False] * num_of_columns for x in range(num_of_rows)] def get_button_value(self, row, col): return self.all_patterns[self.current_pattern_index][ 'is_button_clicked_list'][row][col] def find_number_of_columns(self): return int(self.number_of_units_widget.get()) * int(self.bpu_widget.get()) def process_button_clicked(self, row, col): self.set_button_value(row, col, not self.get_button_value(row, col)) self.display_button_color(row, col) def set_button_value(self, row, col, bool_value): self.all_patterns[self.current_pattern_index][ 'is_button_clicked_list'][row][col] = bool_value def on_button_clicked(self, row, col): def event_handler(): self.process_button_clicked(row, col) return event_handler def display_all_button_colors(self): number_of_columns = self.find_number_of_columns() for r in range(MAX_NUMBER_OF_DRUM_SAMPLES): for c in range(number_of_columns): self.display_button_color(r, c) def display_button_color(self, row, col): bpu = int(self.bpu_widget.get()) original_color = COLOR_1 if ((col//bpu) % 2) else COLOR_2 button_color = BUTTON_CLICKED_COLOR if self.get_button_value( row, col) else original_color self.buttons[row][col].config(background=button_color) def create_play_bar(self): playbar_frame = Frame(self.root, height=15) start_row = MAX_NUMBER_OF_DRUM_SAMPLES + 10 playbar_frame.grid(row=start_row, columnspan=13, sticky=W + E, padx=15, pady=10) self.play_icon = PhotoImage(file="images/play.gif") self.play_button = Button( playbar_frame, text='Play', image=self.play_icon, compound='left', command=self.on_play_button_clicked) self.play_button.grid(row=start_row, column=1, padx=2) Button(playbar_frame, text='Stop', command=self.on_stop_button_clicked).grid( row=start_row, column=3, padx=2) self.loopbuttonvar = BooleanVar() self.loopbuttonvar.set(True) self.loopbutton = Checkbutton( playbar_frame, text='Loop', command=self.on_loop_button_toggled, variable=self.loopbuttonvar) self.loopbutton.grid(row=start_row, column=16, padx=5) Label(playbar_frame, text='Beats Per Minute').grid( row=start_row, column=25) self.beats_per_minute_widget = Spinbox(playbar_frame, from_=MIN_BEATS_PER_MINUTE, to=MAX_BEATS_PER_MINUTE, width=5, increment=5.0, command=self.on_beats_per_minute_changed) self.beats_per_minute_widget.grid(row=start_row, column=30) self.beats_per_minute_widget.delete(0,"end") self.beats_per_minute_widget.insert(0,INITIAL_BEATS_PER_MINUTE) photo = PhotoImage(file='images/signature.gif') label = Label(playbar_frame, image=photo) label.image = photo label.grid(row=start_row, column=50, padx=1, sticky='w') def create_right_button_matrix(self): right_frame = Frame(self.root) right_frame.grid(row=10, column=6, sticky=W + E + N + S, padx=15, pady=4) self.buttons = [[None for x in range( self.find_number_of_columns())] for x in range(MAX_NUMBER_OF_DRUM_SAMPLES)] for row in range(MAX_NUMBER_OF_DRUM_SAMPLES): for col in range(self.find_number_of_columns()): self.buttons[row][col] = Button( right_frame, command=self.on_button_clicked(row, col)) self.buttons[row][col].grid(row=row, column=col) self.display_button_color(row, col) def create_left_drum_loader(self): left_frame = Frame(self.root) left_frame.grid(row=10, column=0, columnspan=6, sticky=W + E + N + S) open_file_icon = PhotoImage(file='images/openfile.gif') for i in range(MAX_NUMBER_OF_DRUM_SAMPLES): open_file_button = Button(left_frame, image=open_file_icon, command=self.on_open_file_button_clicked(i)) open_file_button.image = open_file_icon open_file_button.grid(row=i, column=0, padx=5, pady=4) self.drum_load_entry_widget[i] = Entry(left_frame) self.drum_load_entry_widget[i].grid( row=i, column=4, padx=7, pady=4) def create_top_bar(self): topbar_frame = Frame(self.root, height=25) topbar_frame.grid(row=0, columnspan=12, rowspan=10, padx=5, pady=5) Label(topbar_frame, text='Pattern Number:').grid(row=0, column=1) self.pattern_index_widget = Spinbox(topbar_frame, from_=0, to=MAX_NUMBER_OF_PATTERNS - 1, width=5, command=self.on_pattern_changed) self.pattern_index_widget.grid(row=0, column=2) self.current_pattern_name_widget = Entry(topbar_frame) self.current_pattern_name_widget.grid(row=0, column=3, padx=7, pady=2) Label(topbar_frame, text='Number of Units:').grid(row=0, column=4) self.number_of_units_widget = Spinbox(topbar_frame, from_=1, to=MAX_NUMBER_OF_UNITS, width=5, command=self.on_number_of_units_changed) self.number_of_units_widget.delete(0,"end") self.number_of_units_widget.insert(0,INITIAL_NUMBER_OF_UNITS) self.number_of_units_widget.grid(row=0, column=5) Label(topbar_frame, text='BPUs:').grid(row=0, column=6) self.bpu_widget = Spinbox(topbar_frame, from_=1, to=MAX_BPU, width=5, command=self.on_bpu_changed) self.bpu_widget.grid(row=0, column=7) self.bpu_widget.delete(0,"end") self.bpu_widget.insert(0,INITIAL_BPU) self.display_pattern_name() def create_top_menu(self): self.menu_bar = Menu(self.root) self.file_menu = Menu(self.menu_bar, tearoff=0) self.file_menu.add_command(label="Load Project") self.file_menu.add_command(label="Save Project") self.file_menu.add_separator() self.file_menu.add_command(label="Exit") self.menu_bar.add_cascade(label="File", menu=self.file_menu) self.about_menu = Menu(self.menu_bar, tearoff=0) self.about_menu.add_command(label="About") self.menu_bar.add_cascade(label="About", menu=self.about_menu) self.root.config(menu=self.menu_bar) def init_gui(self): self.create_top_menu() self.create_top_bar() self.create_left_drum_loader() self.create_right_button_matrix() self.create_play_bar() if __name__ == '__main__': root = Tk() DrumMachine(root) root.mainloop() ================================================ FILE: Chapter 03/3.09.py ================================================ """ Code illustration: 3.09 Saving Drum Patterns | Object Persistence: pickling and unpickling New modules imported here: - pickle New methods created here: - load_project() - save_project() - reconstruct_first_pattern() - show_about() Methods modified here: - create_top_menu() added command callback to load_project and save_project Chapter 3 : Programmable Drum Machine Tkinter GUI Application Development Blueprints """ import os import time import pickle import threading from tkinter import Tk, Entry, W, E, N, S, PhotoImage, Checkbutton, Button, \ Menu, Frame, Label, Spinbox, END, BooleanVar, messagebox from tkinter import filedialog import pygame PROGRAM_NAME = ' Explosion Drum Machine ' MAX_NUMBER_OF_PATTERNS = 10 MAX_NUMBER_OF_DRUM_SAMPLES = 5 MAX_NUMBER_OF_UNITS = 5 MAX_BPU = 5 INITIAL_NUMBER_OF_UNITS = 4 INITIAL_BPU = 4 INITIAL_BEATS_PER_MINUTE = 240 MIN_BEATS_PER_MINUTE = 80 MAX_BEATS_PER_MINUTE = 360 COLOR_1 = 'grey55' COLOR_2 = 'khaki' BUTTON_CLICKED_COLOR = 'green' class DrumMachine: def __init__(self, root): self.root = root self.root.title(PROGRAM_NAME) self.all_patterns = [None] * MAX_NUMBER_OF_PATTERNS self.beats_per_minute = INITIAL_BEATS_PER_MINUTE self.current_pattern_index = 0 self.loop = True self.now_playing = False self.drum_load_entry_widget = [None] * MAX_NUMBER_OF_DRUM_SAMPLES self.init_all_patterns() self.init_gui() def on_open_file_button_clicked(self, drum_index): def event_handler(): file_path = filedialog.askopenfilename(defaultextension=".wav", filetypes=[("Wave Files", "*.wav"), ("OGG Files", "*.ogg")]) if not file_path: return self.set_drum_file_path(drum_index, file_path) self.display_all_drum_file_names() return event_handler def display_all_drum_file_names(self): for i, drum_name in enumerate(self.get_list_of_drum_files()): self.display_drum_name(i, drum_name) def display_drum_name(self, text_widget_num, file_path): if file_path is None: return drum_name = os.path.basename(file_path) self.drum_load_entry_widget[text_widget_num].delete(0, END) self.drum_load_entry_widget[text_widget_num].insert(0, drum_name) # # getters and setters begins # def get_current_pattern_dict(self): return self.all_patterns[self.current_pattern_index] def get_bpu(self): return self.get_current_pattern_dict()['bpu'] def set_bpu(self): self.get_current_pattern_dict()['bpu'] = int(self.bpu_widget.get()) def get_number_of_units(self): return self.get_current_pattern_dict()['number_of_units'] def set_number_of_units(self): self.get_current_pattern_dict( )['number_of_units'] = int(self.number_of_units_widget.get()) def get_list_of_drum_files(self): return self.get_current_pattern_dict()['list_of_drum_files'] def get_drum_file_path(self, drum_index): return self.get_list_of_drum_files()[drum_index] def set_drum_file_path(self, drum_index, file_path): self.get_list_of_drum_files()[drum_index] = file_path def get_is_button_clicked_list(self): return self.get_current_pattern_dict()['is_button_clicked_list'] def set_is_button_clicked_list(self, num_of_rows, num_of_columns): self.get_current_pattern_dict()['is_button_clicked_list'] = [ [False] * num_of_columns for x in range(num_of_rows)] def init_all_patterns(self): self.all_patterns = [ { 'list_of_drum_files': [None] * MAX_NUMBER_OF_DRUM_SAMPLES, 'number_of_units': INITIAL_NUMBER_OF_UNITS, 'bpu': INITIAL_BPU, 'is_button_clicked_list': self.init_is_button_clicked_list( MAX_NUMBER_OF_DRUM_SAMPLES, INITIAL_NUMBER_OF_UNITS * INITIAL_BPU ) } for k in range(MAX_NUMBER_OF_PATTERNS)] def display_pattern_name(self): self.current_pattern_name_widget.config(state='normal') self.current_pattern_name_widget.delete(0, 'end') self.current_pattern_name_widget.insert(0, 'Pattern {}'.format(self.current_pattern_index)) self.current_pattern_name_widget.config(state='readonly') def on_pattern_changed(self): self.change_pattern() def change_pattern(self): self.current_pattern_index = int(self.pattern_index_widget.get()) self.display_pattern_name() self.create_left_drum_loader() self.display_all_drum_file_names() self.create_right_button_matrix() self.display_all_button_colors() def on_number_of_units_changed(self): self.set_number_of_units() self.set_is_button_clicked_list(MAX_NUMBER_OF_DRUM_SAMPLES, self.find_number_of_columns()) self.create_right_button_matrix() def on_bpu_changed(self): self.set_bpu() self.set_is_button_clicked_list(MAX_NUMBER_OF_DRUM_SAMPLES, self.find_number_of_columns()) self.create_right_button_matrix() def play_in_thread(self): self.thread = threading.Thread(target = self.play_pattern) self.thread.start() def on_play_button_clicked(self): self.start_play() self.toggle_play_button_state() def start_play(self): self.init_pygame() self.play_in_thread() def on_stop_button_clicked(self): self.stop_play() self.toggle_play_button_state() def toggle_play_button_state(self): if self.now_playing: self.play_button.config(state="disabled") else: self.play_button.config(state="normal") def exit_app(self): self.now_playing = False if messagebox.askokcancel("Quit", "Really quit?"): self.root.destroy() def stop_play(self): self.now_playing = False def init_pygame(self): pygame.mixer.pre_init(44100, -16, 1, 512) pygame.init() def play_sound(self, sound_filename): if sound_filename is not None: pygame.mixer.Sound(sound_filename).play() def get_column_from_matrix(self, matrix, i): return [row[i] for row in matrix] def play_pattern(self): self.now_playing = True self.toggle_play_button_state() while self.now_playing: play_list = self.get_is_button_clicked_list() num_columns = len(play_list[0]) for column_index in range(num_columns): column_to_play = self.get_column_from_matrix( play_list, column_index) for i, item in enumerate(column_to_play): if item: sound_filename = self.get_drum_file_path(i) self.play_sound(sound_filename) time.sleep(self.time_to_play_each_column()) if not self.now_playing: break if not self.loop: break self.now_playing = False self.toggle_play_button_state() def time_to_play_each_column(self): beats_per_second = self.beats_per_minute / 60 time_to_play_each_column = 1 / beats_per_second return time_to_play_each_column def on_loop_button_toggled(self): self.loop = self.loopbuttonvar.get() def on_beats_per_minute_changed(self): self.beats_per_minute = int(self.beats_per_minute_widget.get()) def init_is_button_clicked_list(self, num_of_rows, num_of_columns): return [[False] * num_of_columns for x in range(num_of_rows)] def get_button_value(self, row, col): return self.all_patterns[self.current_pattern_index][ 'is_button_clicked_list'][row][col] def find_number_of_columns(self): return int(self.number_of_units_widget.get()) * int(self.bpu_widget.get()) def process_button_clicked(self, row, col): self.set_button_value(row, col, not self.get_button_value(row, col)) self.display_button_color(row, col) def set_button_value(self, row, col, bool_value): self.all_patterns[self.current_pattern_index][ 'is_button_clicked_list'][row][col] = bool_value def on_button_clicked(self, row, col): def event_handler(): self.process_button_clicked(row, col) return event_handler def display_all_button_colors(self): number_of_columns = self.find_number_of_columns() for r in range(MAX_NUMBER_OF_DRUM_SAMPLES): for c in range(number_of_columns): self.display_button_color(r, c) def display_button_color(self, row, col): bpu = int(self.bpu_widget.get()) original_color = COLOR_1 if ((col//bpu) % 2) else COLOR_2 button_color = BUTTON_CLICKED_COLOR if self.get_button_value( row, col) else original_color self.buttons[row][col].config(background=button_color) def create_play_bar(self): playbar_frame = Frame(self.root, height=15) start_row = MAX_NUMBER_OF_DRUM_SAMPLES + 10 playbar_frame.grid(row=start_row, columnspan=13, sticky=W + E, padx=15, pady=10) self.play_icon = PhotoImage(file="images/play.gif") self.play_button = Button( playbar_frame, text='Play', image=self.play_icon, compound='left', command=self.on_play_button_clicked) self.play_button.grid(row=start_row, column=1, padx=2) Button(playbar_frame, text='Stop', command=self.on_stop_button_clicked).grid( row=start_row, column=3, padx=2) self.loopbuttonvar = BooleanVar() self.loopbuttonvar.set(True) self.loopbutton = Checkbutton( playbar_frame, text='Loop', command=self.on_loop_button_toggled, variable=self.loopbuttonvar) self.loopbutton.grid(row=start_row, column=16, padx=5) Label(playbar_frame, text='Beats Per Minute').grid( row=start_row, column=25) self.beats_per_minute_widget = Spinbox(playbar_frame, from_=MIN_BEATS_PER_MINUTE, to=MAX_BEATS_PER_MINUTE, width=5, increment=5.0, command=self.on_beats_per_minute_changed) self.beats_per_minute_widget.grid(row=start_row, column=30) self.beats_per_minute_widget.delete(0,"end") self.beats_per_minute_widget.insert(0,INITIAL_BEATS_PER_MINUTE) photo = PhotoImage(file='images/signature.gif') label = Label(playbar_frame, image=photo) label.image = photo label.grid(row=start_row, column=50, padx=1, sticky='w') def create_right_button_matrix(self): right_frame = Frame(self.root) right_frame.grid(row=10, column=6, sticky=W + E + N + S, padx=15, pady=4) self.buttons = [[None for x in range( self.find_number_of_columns())] for x in range(MAX_NUMBER_OF_DRUM_SAMPLES)] for row in range(MAX_NUMBER_OF_DRUM_SAMPLES): for col in range(self.find_number_of_columns()): self.buttons[row][col] = Button( right_frame, command=self.on_button_clicked(row, col)) self.buttons[row][col].grid(row=row, column=col) self.display_button_color(row, col) def create_left_drum_loader(self): left_frame = Frame(self.root) left_frame.grid(row=10, column=0, columnspan=6, sticky=W + E + N + S) open_file_icon = PhotoImage(file='images/openfile.gif') for i in range(MAX_NUMBER_OF_DRUM_SAMPLES): open_file_button = Button(left_frame, image=open_file_icon, command=self.on_open_file_button_clicked(i)) open_file_button.image = open_file_icon open_file_button.grid(row=i, column=0, padx=5, pady=4) self.drum_load_entry_widget[i] = Entry(left_frame) self.drum_load_entry_widget[i].grid( row=i, column=4, padx=7, pady=4) def create_top_bar(self): topbar_frame = Frame(self.root, height=25) topbar_frame.grid(row=0, columnspan=12, rowspan=10, padx=5, pady=5) Label(topbar_frame, text='Pattern Number:').grid(row=0, column=1) self.pattern_index_widget = Spinbox(topbar_frame, from_=0, to=MAX_NUMBER_OF_PATTERNS - 1, width=5, command=self.on_pattern_changed) self.pattern_index_widget.grid(row=0, column=2) self.current_pattern_name_widget = Entry(topbar_frame) self.current_pattern_name_widget.grid(row=0, column=3, padx=7, pady=2) Label(topbar_frame, text='Number of Units:').grid(row=0, column=4) self.number_of_units_widget = Spinbox(topbar_frame, from_=1, to=MAX_NUMBER_OF_UNITS, width=5, command=self.on_number_of_units_changed) self.number_of_units_widget.delete(0,"end") self.number_of_units_widget.insert(0,INITIAL_NUMBER_OF_UNITS) self.number_of_units_widget.grid(row=0, column=5) Label(topbar_frame, text='BPUs:').grid(row=0, column=6) self.bpu_widget = Spinbox(topbar_frame, from_=1, to=MAX_BPU, width=5, command=self.on_bpu_changed) self.bpu_widget.grid(row=0, column=7) self.bpu_widget.delete(0,"end") self.bpu_widget.insert(0,INITIAL_BPU) self.display_pattern_name() def load_project(self): file_path = filedialog.askopenfilename( filetypes=[('Explosion Beat File', '*.ebt')], title='Load Project') if not file_path: return pickled_file_object = open(file_path, "rb") try: self.all_patterns = pickle.load(pickled_file_object) except EOFError: messagebox.showerror("Error", "Explosion Beat file seems corrupted or invalid !") pickled_file_object.close() try: self.change_pattern() self.root.title(os.path.basename(file_path) + PROGRAM_NAME) except: messagebox.showerror("Error", "An unexpected error occurred trying to process the beat file") def save_project(self): saveas_file_name = filedialog.asksaveasfilename( filetypes=[('Explosion Beat File', '*.ebt')], title="Save project as...") if saveas_file_name is None: return pickle.dump(self.all_patterns, open(saveas_file_name, "wb")) self.root.title(os.path.basename(saveas_file_name) + PROGRAM_NAME) def show_about(self): messagebox.showinfo(PROGRAM_NAME, "Tkinter GUI Application\n Development Blueprints") def create_top_menu(self): self.menu_bar = Menu(self.root) self.file_menu = Menu(self.menu_bar, tearoff=0) self.file_menu.add_command( label="Load Project", command=self.load_project) self.file_menu.add_command( label="Save Project", command=self.save_project) self.file_menu.add_separator() self.file_menu.add_command(label="Exit", command=self.exit_app) self.menu_bar.add_cascade(label="File", menu=self.file_menu) self.about_menu = Menu(self.menu_bar, tearoff=0) self.about_menu.add_command(label="About", command=self.show_about) self.menu_bar.add_cascade(label="About", menu=self.about_menu) self.root.config(menu=self.menu_bar) def init_gui(self): self.create_top_menu() self.create_top_bar() self.create_left_drum_loader() self.create_right_button_matrix() self.create_play_bar() if __name__ == '__main__': root = Tk() DrumMachine(root) root.mainloop() ================================================ FILE: Chapter 03/3.10.py ================================================ """ Code illustration: 3.10.py 1. tkinter versus ttk Themed Widgets 2. new widgets introduced in ttk Chapter 3 : Programmable Drum Machine Tkinter GUI Application Development Blueprints """ from tkinter import Tk, Button, Label, Checkbutton, Entry, PanedWindow, \ Radiobutton, Scale, VERTICAL, HORIZONTAL, W from tkinter import ttk root = Tk() style = ttk.Style() print(style.theme_names()) # style.theme_use('default') root.title('Tkinter Versus ttk Themed Widgets') ttk.Separator(root, orient=VERTICAL).grid( row=0, rowspan=8, column=1, sticky="wns") Label(root, text='Tkinter Versus').grid(row=0, columnspan=2, sticky='ew') ttk.Label(root, text='ttk').grid(row=0, column=1) Button(root, text='tk Button').grid(row=1, column=0) ttk.Button(root, text='ttk Button').grid(row=1, column=1) Checkbutton(root, text='tk CheckButton').grid(row=2, column=0) ttk.Checkbutton(root, text='ttk CheckButton').grid(row=2, column=1) Entry(root).grid(row=3, column=0) ttk.Entry(root).grid(row=3, column=1) PanedWindow(root).grid(row=4, column=0) ttk.PanedWindow(root).grid(row=4, column=1) Radiobutton(root, text='tk Radio').grid(row=5, column=0) ttk.Radiobutton(root, text='ttk Radio').grid(row=5, column=1) Scale(root, orient=HORIZONTAL).grid(row=6, column=0) ttk.Scale(root).grid(row=6, column=1) ttk.Separator(root, orient=HORIZONTAL).grid(row=7, columnspan=2, sticky="ew") ttk.Label(root, text='NEW WIDGETS INTRODUCED IN ttk').grid(row=8, columnspan=2) ttk.Separator(root, orient=HORIZONTAL).grid(row=9, columnspan=2, sticky="ew") ttk.Combobox(root).grid(row=11, column=0) my_notebook = ttk.Notebook(root) my_notebook.grid(row=12, column=1) frame_1 = ttk.Frame(my_notebook) frame_2 = ttk.Frame(my_notebook) my_notebook.add(frame_1, text='Tab One') my_notebook.add(frame_2, text='Tab Two') ttk.Progressbar(root, length=140, value=65).grid(row=13, column=0) my_tree = ttk.Treeview(root, height=2, columns=2) my_tree.grid(row=14, columnspan=2) my_tree.heading('#0', text='Column A', anchor=W) my_tree.heading(2, text='Column B', anchor=W) my_tree.column(2, stretch=0, width=70) root.mainloop() ================================================ FILE: Chapter 03/3.11.py ================================================ """ Code illustration: 3.11.py ttk widgets styling and theming explained Chapter 3 : Programmable Drum Machine Tkinter GUI Application Development Blueprints """ from tkinter import Tk from tkinter import ttk root = Tk() style = ttk.Style() # defining the global style - applied when no other style is defined style.configure('.', font='Arial 14', foreground='brown', background='yellow') # this label inherits the global style as style option not specified for it ttk.Label(root, text='I have no style of my own').pack() # defining a new style named danger and configuring its style only for the # button widget style.configure('danger.TButton', font='Times 12', foreground='red', padding=1) ttk.Button(root, text='Styled Dangerously', style='danger.TButton').pack() # Different styling for different widget states style.map("new_state_new_style.TButton", foreground=[ ('pressed', 'red'), ('active', 'blue')]) ttk.Button(text="Different Style for different states", style="new_state_new_style.TButton").pack() # Overriding current theme styles for the Entry widget current_theme = style.theme_use() style.theme_settings( current_theme, {"TEntry": {"configure": {"padding": 10}, "map": { "foreground": [("focus", "red")] } } } ) print(style.theme_names()) print(style.theme_use()) # this is effected by change of themes even though no style specified ttk.Entry().pack() root.mainloop() ================================================ FILE: Chapter 03/3.12.py ================================================ """ Code illustration: 3.12 applying ttk themes to our play button, stop button, loopbutton adding separators new imports here: - from tkinter import ttk methods modified here: - create_play_bar() Chapter 3 : Programmable Drum Machine Tkinter GUI Application Development Blueprints """ import os import time import pickle import threading from tkinter import Tk, Entry, W, E, N, S, PhotoImage, Checkbutton, Button, \ Menu, Frame, Label, Spinbox, END, BooleanVar, messagebox from tkinter import filedialog from tkinter import messagebox from tkinter import ttk import pygame PROGRAM_NAME = ' Explosion Drum Machine ' MAX_NUMBER_OF_PATTERNS = 10 MAX_NUMBER_OF_DRUM_SAMPLES = 5 MAX_NUMBER_OF_UNITS = 5 MAX_BPU = 5 INITIAL_NUMBER_OF_UNITS = 4 INITIAL_BPU = 4 INITIAL_BEATS_PER_MINUTE = 240 MIN_BEATS_PER_MINUTE = 80 MAX_BEATS_PER_MINUTE = 360 COLOR_1 = 'grey55' COLOR_2 = 'khaki' BUTTON_CLICKED_COLOR = 'green' class DrumMachine: def __init__(self, root): self.root = root self.root.title(PROGRAM_NAME) self.all_patterns = [None] * MAX_NUMBER_OF_PATTERNS self.current_pattern_index = 0 self.loop = True self.now_playing = False self.beats_per_minute = INITIAL_BEATS_PER_MINUTE self.drum_load_entry_widget = [None] * MAX_NUMBER_OF_DRUM_SAMPLES self.root.protocol('WM_DELETE_WINDOW', self.exit_app) self.init_all_patterns() self.init_gui() def get_current_pattern_dict(self): return self.all_patterns[self.current_pattern_index] def get_bpu(self): return self.get_current_pattern_dict()['bpu'] def set_bpu(self): self.get_current_pattern_dict()['bpu'] = int(self.bpu_widget.get()) def get_number_of_units(self): return self.get_current_pattern_dict()['number_of_units'] def set_number_of_units(self): self.get_current_pattern_dict( )['number_of_units'] = int(self.number_of_units_widget.get()) def get_list_of_drum_files(self): return self.get_current_pattern_dict()['list_of_drum_files'] def get_drum_file_path(self, drum_index): return self.get_list_of_drum_files()[drum_index] def set_drum_file_path(self, drum_index, file_path): self.get_list_of_drum_files()[drum_index] = file_path def get_is_button_clicked_list(self): return self.get_current_pattern_dict()['is_button_clicked_list'] def set_is_button_clicked_list(self, num_of_rows, num_of_columns): self.get_current_pattern_dict()['is_button_clicked_list'] = [ [False] * num_of_columns for x in range(num_of_rows)] def set_beats_per_minute(self): self.get_current_pattern_dict( )['beats_per_minute'] = self.beats_per_minute_widget.get() def init_all_patterns(self): self.all_patterns = [ { 'list_of_drum_files': [None] * MAX_NUMBER_OF_DRUM_SAMPLES, 'number_of_units': INITIAL_NUMBER_OF_UNITS, 'bpu': INITIAL_BPU, 'is_button_clicked_list': self.init_is_button_clicked_list( MAX_NUMBER_OF_DRUM_SAMPLES, INITIAL_NUMBER_OF_UNITS * INITIAL_BPU ) } for k in range(MAX_NUMBER_OF_PATTERNS)] def init_is_button_clicked_list(self, num_of_rows, num_of_columns): return [[False] * num_of_columns for x in range(num_of_rows)] def load_project(self): file_path = filedialog.askopenfilename( filetypes=[('Explosion Beat File', '*.ebt')], title='Load Project') if not file_path: return pickled_file_object = open(file_path, "rb") try: self.all_patterns = pickle.load(pickled_file_object) except EOFError: messagebox.showerror("Error", "Explosion Beat file seems corrupted or invalid !") pickled_file_object.close() try: self.change_pattern() self.root.title(os.path.basename(file_path) + PROGRAM_NAME) except: messagebox.showerror("Error", "An unexpected error occurred trying to process the beat file") def save_project(self): saveas_file_name = filedialog.asksaveasfilename( filetypes=[('Explosion Beat File', '*.ebt')], title="Save project as...") if saveas_file_name is None: return pickle.dump(self.all_patterns, open(saveas_file_name, "wb")) self.root.title(os.path.basename(saveas_file_name) + PROGRAM_NAME) def show_about(self): messagebox.showinfo(PROGRAM_NAME, "Tkinter GUI Application\n Development Blueprints") def exit_app(self): self.now_playing = False if messagebox.askokcancel("Quit", "Really quit?"): self.root.destroy() def display_pattern_name(self): self.current_pattern_name_widget.config(state='normal') self.current_pattern_name_widget.delete(0, 'end') self.current_pattern_name_widget.insert(0, 'Pattern {}'.format(self.current_pattern_index)) self.current_pattern_name_widget.config(state='readonly') def on_pattern_changed(self): self.change_pattern() def change_pattern(self): self.current_pattern_index = int(self.pattern_index_widget.get()) self.display_pattern_name() self.create_left_drum_loader() self.display_all_drum_file_names() self.create_right_button_matrix() self.display_all_button_colors() def on_number_of_units_changed(self): self.set_number_of_units() self.set_is_button_clicked_list(MAX_NUMBER_OF_DRUM_SAMPLES, self.find_number_of_columns()) self.create_right_button_matrix() def on_bpu_changed(self): self.set_bpu() self.set_is_button_clicked_list(MAX_NUMBER_OF_DRUM_SAMPLES, self.find_number_of_columns()) self.create_right_button_matrix() def on_open_file_button_clicked(self, drum_index): def event_handler(): file_path = filedialog.askopenfilename(defaultextension=".wav", filetypes=[("Wave Files", "*.wav"), ("OGG Files", "*.ogg")]) if not file_path: return self.set_drum_file_path(drum_index, file_path) self.display_all_drum_file_names() return event_handler def display_all_drum_file_names(self): for i, drum_name in enumerate(self.get_list_of_drum_files()): self.display_drum_name(i, drum_name) def display_drum_name(self, text_widget_num, file_path): if file_path is None: return drum_name = os.path.basename(file_path) self.drum_load_entry_widget[text_widget_num].delete(0, END) self.drum_load_entry_widget[text_widget_num].insert(0, drum_name) def play_in_thread(self): self.thread = threading.Thread(target = self.play_pattern) self.thread.start() def on_play_button_clicked(self): self.start_play() self.toggle_play_button_state() def start_play(self): self.init_pygame() self.play_in_thread() def on_stop_button_clicked(self): self.stop_play() self.toggle_play_button_state() def stop_play(self): self.now_playing = False def init_pygame(self): pygame.mixer.pre_init(44100, -16, 1, 512) pygame.init() def play_sound(self, sound_filename): if sound_filename is not None: pygame.mixer.Sound(sound_filename).play() def get_column_from_matrix(self, matrix, i): return [row[i] for row in matrix] def toggle_play_button_state(self): if self.now_playing: self.play_button.config(state="disabled") else: self.play_button.config(state="normal") def play_pattern(self): self.now_playing = True self.toggle_play_button_state() while self.now_playing: play_list = self.get_is_button_clicked_list() num_columns = len(play_list[0]) for column_index in range(num_columns): column_to_play = self.get_column_from_matrix( play_list, column_index) for i, item in enumerate(column_to_play): if item: sound_filename = self.get_drum_file_path(i) self.play_sound(sound_filename) time.sleep(self.time_to_play_each_column()) if not self.now_playing: break if not self.loop: break self.now_playing = False self.toggle_play_button_state() def time_to_play_each_column(self): beats_per_second = self.beats_per_minute / 60 time_to_play_each_column = 1 / beats_per_second return time_to_play_each_column def on_loop_button_toggled(self): self.loop = self.loopbutton.instate(['selected']) def on_beats_per_minute_changed(self): self.beats_per_minute = int(self.beats_per_minute_widget.get()) def get_button_value(self, row, col): return self.all_patterns[self.current_pattern_index][ 'is_button_clicked_list'][row][col] def find_number_of_columns(self): return int(self.number_of_units_widget.get()) * int(self.bpu_widget.get()) def process_button_clicked(self, row, col): self.set_button_value(row, col, not self.get_button_value(row, col)) self.display_button_color(row, col) def set_button_value(self, row, col, bool_value): self.all_patterns[self.current_pattern_index][ 'is_button_clicked_list'][row][col] = bool_value def on_button_clicked(self, row, col): def event_handler(): self.process_button_clicked(row, col) return event_handler def display_all_button_colors(self): number_of_columns = self.find_number_of_columns() for r in range(MAX_NUMBER_OF_DRUM_SAMPLES): for c in range(number_of_columns): self.display_button_color(r, c) def display_button_color(self, row, col): bpu = int(self.bpu_widget.get()) original_color = COLOR_1 if ((col//bpu) % 2) else COLOR_2 button_color = BUTTON_CLICKED_COLOR if self.get_button_value( row, col) else original_color self.buttons[row][col].config(background=button_color) def create_play_bar(self): playbar_frame = Frame(self.root, height=15) start_row = MAX_NUMBER_OF_DRUM_SAMPLES + 10 playbar_frame.grid(row=start_row, columnspan=13, sticky=W + E, padx=15, pady=10) self.play_icon = PhotoImage(file="images/play.gif") self.play_button = ttk.Button( playbar_frame, text='Play', image=self.play_icon, compound='left', command=self.on_play_button_clicked) self.play_button.grid(row=start_row, column=1, padx=2) ttk.Button(playbar_frame, text='Stop', command=self.on_stop_button_clicked).grid( row=start_row, column=3, padx=2) ttk.Separator(playbar_frame, orient='vertical').grid( row=start_row, column=5, sticky="ns", padx=5) self.loopbutton = ttk.Checkbutton( playbar_frame, text='Loop', command=self.on_loop_button_toggled) self.loopbutton.grid(row=start_row, column=16, padx=5) self.loopbutton.state(['selected']) ttk.Separator(playbar_frame, orient='vertical').grid( row=start_row, column=20, sticky="ns", padx=5) Label(playbar_frame, text='Beats Per Minute').grid( row=start_row, column=25) self.beats_per_minute_widget = Spinbox(playbar_frame, from_=MIN_BEATS_PER_MINUTE, to=MAX_BEATS_PER_MINUTE, width=5, increment=5.0, command=self.on_beats_per_minute_changed) self.beats_per_minute_widget.grid(row=start_row, column=30) self.beats_per_minute_widget.delete(0,"end") self.beats_per_minute_widget.insert(0,INITIAL_BEATS_PER_MINUTE) ttk.Separator(playbar_frame, orient='vertical').grid( row=start_row, column=35, sticky="ns", padx=5) photo = PhotoImage(file='images/signature.gif') label = Label(playbar_frame, image=photo) label.image = photo label.grid(row=start_row, column=50, padx=1, sticky='w') def create_right_button_matrix(self): right_frame = Frame(self.root) right_frame.grid(row=10, column=6, sticky=W + E + N + S, padx=15, pady=4) self.buttons = [[None for x in range( self.find_number_of_columns())] for x in range(MAX_NUMBER_OF_DRUM_SAMPLES)] for row in range(MAX_NUMBER_OF_DRUM_SAMPLES): for col in range(self.find_number_of_columns()): self.buttons[row][col] = Button( right_frame, command=self.on_button_clicked(row, col)) self.buttons[row][col].grid(row=row, column=col) self.display_button_color(row, col) def create_left_drum_loader(self): left_frame = Frame(self.root) left_frame.grid(row=10, column=0, columnspan=6, sticky=W + E + N + S) open_file_icon = PhotoImage(file='images/openfile.gif') for i in range(MAX_NUMBER_OF_DRUM_SAMPLES): open_file_button = Button(left_frame, image=open_file_icon, command=self.on_open_file_button_clicked(i)) open_file_button.image = open_file_icon open_file_button.grid(row=i, column=0, padx=5, pady=4) self.drum_load_entry_widget[i] = Entry(left_frame) self.drum_load_entry_widget[i].grid( row=i, column=4, padx=7, pady=4) def create_top_bar(self): topbar_frame = Frame(self.root, height=25) topbar_frame.grid(row=0, columnspan=12, rowspan=10, padx=5, pady=5) Label(topbar_frame, text='Pattern Number:').grid(row=0, column=1) self.pattern_index_widget = Spinbox(topbar_frame, from_=0, to=MAX_NUMBER_OF_PATTERNS - 1, width=5, command=self.on_pattern_changed) self.pattern_index_widget.grid(row=0, column=2) self.current_pattern_name_widget = Entry(topbar_frame) self.current_pattern_name_widget.grid(row=0, column=3, padx=7, pady=2) Label(topbar_frame, text='Number of Units:').grid(row=0, column=4) self.number_of_units_widget = Spinbox(topbar_frame, from_=1, to=MAX_NUMBER_OF_UNITS, width=5, command=self.on_number_of_units_changed) self.number_of_units_widget.delete(0,"end") self.number_of_units_widget.insert(0,INITIAL_NUMBER_OF_UNITS) self.number_of_units_widget.grid(row=0, column=5) Label(topbar_frame, text='BPUs:').grid(row=0, column=6) self.bpu_widget = Spinbox(topbar_frame, from_=1, to=MAX_BPU, width=5, command=self.on_bpu_changed) self.bpu_widget.grid(row=0, column=7) self.bpu_widget.delete(0,"end") self.bpu_widget.insert(0,INITIAL_BPU) self.display_pattern_name() def create_top_menu(self): self.menu_bar = Menu(self.root) self.file_menu = Menu(self.menu_bar, tearoff=0) self.file_menu.add_command( label="Load Project", command=self.load_project) self.file_menu.add_command( label="Save Project", command=self.save_project) self.file_menu.add_separator() self.file_menu.add_command(label="Exit", command=self.exit_app) self.menu_bar.add_cascade(label="File", menu=self.file_menu) self.about_menu = Menu(self.menu_bar, tearoff=0) self.about_menu.add_command(label="About", command=self.show_about) self.menu_bar.add_cascade(label="About", menu=self.about_menu) self.root.config(menu=self.menu_bar) def init_gui(self): self.create_top_menu() self.create_top_bar() self.create_left_drum_loader() self.create_right_button_matrix() self.create_play_bar() if __name__ == '__main__': root = Tk() DrumMachine(root) root.mainloop() ================================================ FILE: Chapter 03/readme.txt ================================================ ==================================================================== Code Readme Tkinter GUI Application Development Blueprints Chapter 3: Drum Machine ==================================================================== List of code samples: 3.01: Creating OOP Based GUI structure 3.02: Defining & Initializing the Data Structure 3.03: Displaying All Visual Elements 3.04: Adding all the getter & setter methods for our data structure 3.05: Loading drum samples 3.06: Playing Audio 3.07: Tkinter and Threading 3.08: Adding support for multiple beat patterns 3.09: Object Persistence: pickling and unpickling 3.10: tkinter versus ttk Themed Widgets / new widgets introduced in ttk 3.11: ttk widgets styling and theming explained 3.12: applying ttk themes to our play button, stop button, loopbutton / adding separators ================================================ FILE: Chapter 04/4.01/configurations.py ================================================ """ Code illustration: 4.01 @ Tkinter GUI Application Development Blueprints """ NUMBER_OF_ROWS = 8 NUMBER_OF_COLUMNS = 8 DIMENSION_OF_EACH_SQUARE = 64 # denoting 64 pixels BOARD_COLOR_1 = "#e6a803" BOARD_COLOR_2 = "#8b8350" ================================================ FILE: Chapter 04/4.01/controller.py ================================================ """ Code illustration: 4.01 @ Tkinter GUI Application Development Blueprints """ from configurations import * import model class Controller(): def __init__(self): self.init_model() def init_model(self): self.model = model.Model() ================================================ FILE: Chapter 04/4.01/exceptions.py ================================================ """ Code illustration: 4.01 @ Tkinter GUI Application Development Blueprints """ class ChessError(Exception): pass ================================================ FILE: Chapter 04/4.01/model.py ================================================ """ Code illustration: 4.01 @ Tkinter GUI Application Development Blueprints """ from configurations import * class Model(): def __init__(self): pass ================================================ FILE: Chapter 04/4.01/view.py ================================================ """ Code illustration: 4.01 @ Tkinter GUI Application Development Blueprints """ from tkinter import Tk, Menu, Label, Frame, Canvas, RIGHT, messagebox import controller from configurations import * class View(): board_color_1 = BOARD_COLOR_1 board_color_2 = BOARD_COLOR_2 def __init__(self, parent, controller): self.controller = controller self.parent = parent self.create_chess_base() self.canvas.bind("", self.on_square_clicked) def create_chess_base(self): self.create_top_menu() self.create_canvas() self.draw_board() self.create_bottom_frame() def create_top_menu(self): self.menu_bar = Menu(self.parent) self.create_file_menu() self.create_edit_menu() self.create_about_menu() def create_bottom_frame(self): self.bottom_frame = Frame(self.parent, height=64) self.info_label = Label( self.bottom_frame, text=" White to Start the Game ", fg=BOARD_COLOR_2) self.info_label.pack(side=RIGHT, padx=8, pady=5) self.bottom_frame.pack(fill="x", side="bottom") def on_about_menu_clicked(self): messagebox.showinfo("From the Book:", "Tkinter GUI Application\n Development Blueprints") def on_new_game_menu_clicked(self): pass def on_preference_menu_clicked(self): pass def create_file_menu(self): self.file_menu = Menu(self.menu_bar, tearoff=0) self.file_menu.add_command( label="New Game", command=self.on_new_game_menu_clicked) self.menu_bar.add_cascade(label="File", menu=self.file_menu) self.parent.config(menu=self.menu_bar) def create_edit_menu(self): self.edit_menu = Menu(self.menu_bar, tearoff=0) self.edit_menu.add_command( label="Preferences", command=self.on_preference_menu_clicked) self.menu_bar.add_cascade(label="Edit", menu=self.edit_menu) self.parent.config(menu=self.menu_bar) def create_about_menu(self): self.about_menu = Menu(self.menu_bar, tearoff=0) self.about_menu.add_command( label="About", command=self.on_about_menu_clicked) self.menu_bar.add_cascade(label="About", menu=self.about_menu) self.parent.config(menu=self.menu_bar) def create_canvas(self): canvas_width = NUMBER_OF_COLUMNS * DIMENSION_OF_EACH_SQUARE canvas_height = NUMBER_OF_ROWS * DIMENSION_OF_EACH_SQUARE self.canvas = Canvas( self.parent, width=canvas_width, height=canvas_height) self.canvas.pack(padx=8, pady=8) def draw_board(self): current_color = BOARD_COLOR_2 for row in range(NUMBER_OF_ROWS): current_color = self.get_alternate_color(current_color) for col in range(NUMBER_OF_COLUMNS): x1, y1 = self.get_x_y_coordinate(row, col) x2, y2 = x1 + DIMENSION_OF_EACH_SQUARE, y1 + DIMENSION_OF_EACH_SQUARE self.canvas.create_rectangle( x1, y1, x2, y2, fill=current_color) current_color = self.get_alternate_color(current_color) def get_alternate_color(self, current_color): if current_color == self.board_color_2: next_color = self.board_color_1 else: next_color = self.board_color_2 return next_color def on_square_clicked(self, event): clicked_row, clicked_column = self.get_clicked_row_column(event) print("Hey you clicked on", clicked_row, clicked_column) def get_clicked_row_column(self, event): col_size = row_size = DIMENSION_OF_EACH_SQUARE clicked_column = event.x // col_size clicked_row = 7 - (event.y // row_size) return (clicked_row, clicked_column) def get_x_y_coordinate(self, row, col): x = (col * DIMENSION_OF_EACH_SQUARE) y = ((7 - row) * DIMENSION_OF_EACH_SQUARE) return (x, y) def main(controller): root = Tk() root.title("Chess") View(root, controller) root.mainloop() def init_new_game(): game_controller = controller.Controller() main(game_controller) if __name__ == "__main__": init_new_game() ================================================ FILE: Chapter 04/4.02/configurations.py ================================================ """ Code illustration: 4.01 @ Tkinter GUI Application Development Blueprints """ NUMBER_OF_ROWS = 8 NUMBER_OF_COLUMNS = 8 DIMENSION_OF_EACH_SQUARE = 64 BOARD_COLOR_1 = "#e6a803" BOARD_COLOR_2 = "#8b8350" X_AXIS_LABELS = ('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H') Y_AXIS_LABELS = (1, 2, 3, 4, 5, 6, 7, 8) # remember capital letters - White pieces, Small letters - Black pieces START_PIECES_POSITION = { "A8": "r", "B8": "n", "C8": "b", "D8": "q", "E8": "k", "F8": "b", "G8": "n", "H8": "r", "A7": "p", "B7": "p", "C7": "p", "D7": "p", "E7": "p", "F7": "p", "G7": "p", "H7": "p", "A2": "P", "B2": "P", "C2": "P", "D2": "P", "E2": "P", "F2": "P", "G2": "P", "H2": "P", "A1": "R", "B1": "N", "C1": "B", "D1": "Q", "E1": "K", "F1": "B", "G1": "N", "H1": "R" } SHORT_NAME = { 'R': 'Rook', 'N': 'Knight', 'B': 'Bishop', 'Q': 'Queen', 'K': 'King', 'P': 'Pawn' } ================================================ FILE: Chapter 04/4.02/controller.py ================================================ """ Code illustration: 4.01 @ Tkinter GUI Application Development Blueprints """ from configurations import * import model class Controller(): def __init__(self): self.init_model() def init_model(self): self.model = model.Model() ================================================ FILE: Chapter 04/4.02/exceptions.py ================================================ """ Code illustration: 4.02 @ Tkinter GUI Application Development Blueprints """ class ChessError(Exception): pass ================================================ FILE: Chapter 04/4.02/model.py ================================================ """ Code illustration: 4.01 @ Tkinter GUI Application Development Blueprints """ from configurations import * import piece class Model(dict): captured_pieces = {'white': [], 'black': []} player_turn = None halfmove_clock = 0 fullmove_number = 1 history = [] def __init__(self): pass def get_piece_at(self, position): return self.get(position) def get_alphanumeric_position(self, rowcol): if self.is_on_board(rowcol): row, col = rowcol return "{}{}".format(X_AXIS_LABELS[col], Y_AXIS_LABELS[row]) def is_on_board(self, rowcol): row, col = rowcol return 0 <= row <= 7 and 0 <= col <= 7 ================================================ FILE: Chapter 04/4.02/piece.py ================================================ """ Code illustration: 4.02 @ Tkinter GUI Application Development Blueprints """ from configurations import * import exceptions def create_piece (piece, color='white'): if isinstance(piece, str): if piece.upper() in SHORT_NAME.keys(): color = "white" if piece.isupper() else "black" piece = SHORT_NAME[piece.upper()] piece = piece.capitalize() if piece in SHORT_NAME.values(): return eval("{classname}(color)".format(classname=piece)) raise exceptions.ChessError("invalid piece name: '{}'".format(piece)) class Piece(): def __init__(self, color): self.name = self.__class__.__name__.lower() if color == 'black': self.name = self.name.lower() elif color == 'white': self.name = self.name.upper() self.color = color def keep_reference(self, model): self.model = model class King(Piece): pass class Queen(Piece): pass class Rook(Piece): pass class Bishop(Piece): pass class Knight(Piece): pass class Pawn(Piece): pass ================================================ FILE: Chapter 04/4.02/view.py ================================================ """ Code illustration: 4.01 @ Tkinter GUI Application Development Blueprints """ from tkinter import Tk, Menu, Label, Frame, Canvas, RIGHT, messagebox import controller from configurations import * class View(): board_color_1 = BOARD_COLOR_1 board_color_2 = BOARD_COLOR_2 def __init__(self, parent, controller): self.controller = controller self.parent = parent self.create_chess_base() self.canvas.bind("", self.on_square_clicked) def create_chess_base(self): self.create_top_menu() self.create_canvas() self.draw_board() self.create_bottom_frame() def create_top_menu(self): self.menu_bar = Menu(self.parent) self.create_file_menu() self.create_edit_menu() self.create_about_menu() def create_bottom_frame(self): self.bottom_frame = Frame(self.parent, height=64) self.info_label = Label( self.bottom_frame, text=" White to Start the Game ", fg="black") self.info_label.pack(side=RIGHT, padx=8, pady=5) self.bottom_frame.pack(fill="x", side="bottom") def on_about_menu_clicked(self): messagebox.showinfo("From the Book:", "Tkinter GUI Application\n Development Blueprints") def on_new_game_menu_clicked(self): pass def on_preference_menu_clicked(self): pass def create_file_menu(self): self.file_menu = Menu(self.menu_bar, tearoff=0) self.file_menu.add_command( label="New Game", command=self.on_new_game_menu_clicked) self.menu_bar.add_cascade(label="File", menu=self.file_menu) self.parent.config(menu=self.menu_bar) def create_edit_menu(self): self.edit_menu = Menu(self.menu_bar, tearoff=0) self.edit_menu.add_command( label="Preferences", command=self.on_preference_menu_clicked) self.menu_bar.add_cascade(label="Edit", menu=self.edit_menu) self.parent.config(menu=self.menu_bar) def create_about_menu(self): self.about_menu = Menu(self.menu_bar, tearoff=0) self.about_menu.add_command( label="About", command=self.on_about_menu_clicked) self.menu_bar.add_cascade(label="About", menu=self.about_menu) self.parent.config(menu=self.menu_bar) def create_canvas(self): canvas_width = NUMBER_OF_COLUMNS * DIMENSION_OF_EACH_SQUARE canvas_height = NUMBER_OF_ROWS * DIMENSION_OF_EACH_SQUARE self.canvas = Canvas( self.parent, width=canvas_width, height=canvas_height) self.canvas.pack(padx=8, pady=8) def draw_board(self): current_color = BOARD_COLOR_2 for row in range(NUMBER_OF_ROWS): current_color = self.get_alternate_color(current_color) for col in range(NUMBER_OF_COLUMNS): x1, y1 = self.get_x_y_coordinate(row, col) x2, y2 = x1 + DIMENSION_OF_EACH_SQUARE, y1 + DIMENSION_OF_EACH_SQUARE self.canvas.create_rectangle( x1, y1, x2, y2, fill=current_color) current_color = self.get_alternate_color(current_color) def on_square_clicked(self, event): clicked_row, clicked_column = self.get_clicked_row_column(event) print("Hey you clicked on", clicked_row, clicked_column) def get_clicked_row_column(self, event): col_size = row_size = DIMENSION_OF_EACH_SQUARE clicked_column = event.x // col_size clicked_row = 7 - (event.y // row_size) return (clicked_row, clicked_column) def get_x_y_coordinate(self, row, col): x = (col * DIMENSION_OF_EACH_SQUARE) y = ((7 - row) * DIMENSION_OF_EACH_SQUARE) return (x, y) def get_alternate_color(self, current_color): if current_color == BOARD_COLOR_1: next_color = BOARD_COLOR_2 else: next_color = BOARD_COLOR_1 return next_color def main(controller): root = Tk() root.title("Chess") View(root, controller) root.mainloop() def init_new_game(): game_controller = controller.Controller() main(game_controller) if __name__ == "__main__": init_new_game() ================================================ FILE: Chapter 04/4.03/configurations.py ================================================ """ Code illustration: 4.03 @ Tkinter GUI Application Development Blueprints """ NUMBER_OF_ROWS = 8 NUMBER_OF_COLUMNS = 8 DIMENSION_OF_EACH_SQUARE = 64 BOARD_COLOR_1 = "#e6a803" BOARD_COLOR_2 = "#8b8350" X_AXIS_LABELS = ('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H') Y_AXIS_LABELS = (1, 2, 3, 4, 5, 6, 7, 8) SHORT_NAME = { 'R': 'Rook', 'N': 'Knight', 'B': 'Bishop', 'Q': 'Queen', 'K': 'King', 'P': 'Pawn' } # remember capital letters - White pieces, Small letters - Black pieces START_PIECES_POSITION = { "A8": "r", "B8": "n", "C8": "b", "D8": "q", "E8": "k", "F8": "b", "G8": "n", "H8": "r", "A7": "p", "B7": "p", "C7": "p", "D7": "p", "E7": "p", "F7": "p", "G7": "p", "H7": "p", "A2": "P", "B2": "P", "C2": "P", "D2": "P", "E2": "P", "F2": "P", "G2": "P", "H2": "P", "A1": "R", "B1": "N", "C1": "B", "D1": "Q", "E1": "K", "F1": "B", "G1": "N", "H1": "R" } ================================================ FILE: Chapter 04/4.03/controller.py ================================================ """ Code illustration: 4.03 New method added here: get_numeric_notation(position) get_all_peices_on_chess_board() reset_game_data() reset_to_initial_locations() @ Tkinter GUI Application Development Blueprints """ from configurations import * import model import piece class Controller(): def __init__(self): self.init_model() def init_model(self): self.model = model.Model() def get_all_peices_on_chess_board(self): return self.model.items() def reset_game_data(self): self.model.reset_game_data() def reset_to_initial_locations(self): self.model.reset_to_initial_locations() def get_numeric_notation(self, position): return piece.get_numeric_notation(position) ================================================ FILE: Chapter 04/4.03/exceptions.py ================================================ """ Code illustration: 4.03 @ Tkinter GUI Application Development Blueprints """ class ChessError(Exception): pass ================================================ FILE: Chapter 04/4.03/model.py ================================================ """ Code illustration: 4.03 Methods modified here: __init__ method (added a call to reset_to_initial_locations()) Methods defined here: reset_to_initial_locations() reset_game_data() @ Tkinter GUI Application Development Blueprints """ from configurations import * import piece class Model(dict): captured_pieces = {'white': [], 'black': []} player_turn = None halfmove_clock = 0 fullmove_number = 1 history = [] def __init__(self): self.reset_to_initial_locations() def reset_game_data(self): captured_pieces = {'white': [], 'black': []} player_turn = None halfmove_clock = 0 fullmove_number = 1 history = [] def reset_to_initial_locations(self): self.clear() for position, value in START_PIECES_POSITION.items(): self[position] = piece.create_piece(value) self[position].keep_reference(self) self.player_turn = 'white' def get_piece_at(self, position): return self.get(position) def get_alphanumeric_position(self, rowcol): if self.is_on_board(rowcol): row, col = rowcol return "{}{}".format(X_AXIS_LABELS[col], Y_AXIS_LABELS[row]) def is_on_board(self, rowcol): row, col = rowcol return 0 <= row <= 7 and 0 <= col <= 7 ================================================ FILE: Chapter 04/4.03/piece.py ================================================ """ Code illustration: 4.03 New Imports import sys Methods Added - all classes and methods @ Tkinter GUI Application Development Blueprints """ from configurations import * import sys import exceptions def create_piece (piece, color='white'): if isinstance(piece, str): if piece.upper() in SHORT_NAME.keys(): color = "white" if piece.isupper() else "black" piece = SHORT_NAME[piece.upper()] piece = piece.capitalize() if piece in SHORT_NAME.values(): return eval("{classname}(color)".format(classname=piece)) raise exceptions.ChessError("invalid piece name: '{}'".format(piece)) def get_numeric_notation(rowcol): row, col = rowcol return int(col)-1, X_AXIS_LABELS.index(row) class Piece(): def __init__(self, color): self.name = self.__class__.__name__.lower() if color == 'black': self.name = self.name.lower() elif color == 'white': self.name = self.name.upper() self.color = color def keep_reference(self, model): self.model = model class King(Piece): pass class Queen(Piece): pass class Rook(Piece): pass class Bishop(Piece): pass class Knight(Piece): pass class Pawn(Piece): pass ================================================ FILE: Chapter 04/4.03/view.py ================================================ """ Code illustration: 4.03 New attributes added here: images = {} New methods defined here: draw_single_piece(position, piece) calculate_piece_coordinate(x, y) draw_all_pieces() start_new_game() @ Tkinter GUI Application Development Blueprints """ from tkinter import Tk, Menu, Label, Frame, Canvas, RIGHT, PhotoImage, messagebox import controller from configurations import * class View(): images = {} board_color_1 = BOARD_COLOR_1 board_color_2 = BOARD_COLOR_2 def __init__(self, parent, controller): self.controller = controller self.parent = parent self.create_chess_base() self.canvas.bind("", self.on_square_clicked) self.start_new_game() def create_chess_base(self): self.create_top_menu() self.create_canvas() self.draw_board() self.create_bottom_frame() def create_top_menu(self): self.menu_bar = Menu(self.parent) self.create_file_menu() self.create_edit_menu() self.create_about_menu() def create_bottom_frame(self): self.bottom_frame = Frame(self.parent, height=64) self.info_label = Label( self.bottom_frame, text=" White to Start the Game ", fg="black") self.info_label.pack(side=RIGHT, padx=8, pady=5) self.bottom_frame.pack(fill="x", side="bottom") def on_about_menu_clicked(self): messagebox.showinfo("From the Book:", "Tkinter GUI Application\n Development Blueprints") def on_new_game_menu_clicked(self): pass def on_preference_menu_clicked(self): pass def create_file_menu(self): self.file_menu = Menu(self.menu_bar, tearoff=0) self.file_menu.add_command( label="New Game", command=self.on_new_game_menu_clicked) self.menu_bar.add_cascade(label="File", menu=self.file_menu) self.parent.config(menu=self.menu_bar) def create_edit_menu(self): self.edit_menu = Menu(self.menu_bar, tearoff=0) self.edit_menu.add_command( label="Preferences", command=self.on_preference_menu_clicked) self.menu_bar.add_cascade(label="Edit", menu=self.edit_menu) self.parent.config(menu=self.menu_bar) def create_about_menu(self): self.about_menu = Menu(self.menu_bar, tearoff=0) self.about_menu.add_command( label="About", command=self.on_about_menu_clicked) self.menu_bar.add_cascade(label="About", menu=self.about_menu) self.parent.config(menu=self.menu_bar) def create_canvas(self): canvas_width = NUMBER_OF_COLUMNS * DIMENSION_OF_EACH_SQUARE canvas_height = NUMBER_OF_ROWS * DIMENSION_OF_EACH_SQUARE self.canvas = Canvas( self.parent, width=canvas_width, height=canvas_height) self.canvas.pack(padx=8, pady=8) def draw_board(self): current_color = BOARD_COLOR_2 for row in range(NUMBER_OF_ROWS): current_color = self.get_alternate_color(current_color) for col in range(NUMBER_OF_COLUMNS): x1, y1 = self.get_x_y_coordinate(row, col) x2, y2 = x1 + DIMENSION_OF_EACH_SQUARE, y1 + DIMENSION_OF_EACH_SQUARE self.canvas.create_rectangle( x1, y1, x2, y2, fill=current_color) current_color = self.get_alternate_color(current_color) def on_square_clicked(self, event): clicked_row, clicked_column = self.get_clicked_row_column(event) print("Hey you clicked on", clicked_row, clicked_column) def get_clicked_row_column(self, event): col_size = row_size = DIMENSION_OF_EACH_SQUARE clicked_column = event.x // col_size clicked_row = 7 - (event.y // row_size) return (clicked_row, clicked_column) def get_x_y_coordinate(self, row, col): x = (col * DIMENSION_OF_EACH_SQUARE) y = ((7 - row) * DIMENSION_OF_EACH_SQUARE) return (x, y) def calculate_piece_coordinate(self, row, col): x0 = (col * DIMENSION_OF_EACH_SQUARE) + \ int(DIMENSION_OF_EACH_SQUARE / 2) y0 = ((7 - row) * DIMENSION_OF_EACH_SQUARE) + \ int(DIMENSION_OF_EACH_SQUARE / 2) return (x0, y0) def draw_single_piece(self, position, piece): x, y = self.controller.get_numeric_notation(position) if piece: filename = "../pieces_image/{}_{}.png".format( piece.name.lower(), piece.color) if filename not in self.images: self.images[filename] = PhotoImage(file=filename) x0, y0 = self.calculate_piece_coordinate(x, y) self.canvas.create_image(x0, y0, image=self.images[ filename], tags=("occupied"), anchor="c") def draw_all_pieces(self): self.canvas.delete("occupied") for position, piece in self.controller.get_all_peices_on_chess_board(): self.draw_single_piece(position, piece) def start_new_game(self): self.controller.reset_game_data() self.controller.reset_to_initial_locations() self.draw_all_pieces() self.info_label.config(text=" White to Start the Game ") def get_alternate_color(self, current_color): if current_color == self.board_color_2: next_color = self.board_color_1 else: next_color = self.board_color_2 return next_color def main(controller): root = Tk() root.title("Chess") View(root, controller) root.mainloop() def init_new_game(): game_controller = controller.Controller() main(game_controller) if __name__ == "__main__": init_new_game() ================================================ FILE: Chapter 04/4.04/configurations.py ================================================ """ Code illustration: 4.04 @ Tkinter GUI Application Development Blueprints """ NUMBER_OF_ROWS = 8 NUMBER_OF_COLUMNS = 8 DIMENSION_OF_EACH_SQUARE = 64 BOARD_COLOR_1 = "#e6a803" BOARD_COLOR_2 = "#8b8350" X_AXIS_LABELS = ('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H') Y_AXIS_LABELS = (1, 2, 3, 4, 5, 6, 7, 8) SHORT_NAME = { 'R':'Rook', 'N':'Knight', 'B':'Bishop', 'Q':'Queen', 'K':'King', 'P':'Pawn' } # remember capital letters - White pieces, Small letters - Black pieces START_PIECES_POSITION = { "A8": "r", "B8": "n", "C8": "b", "D8": "q", "E8": "k", "F8": "b", "G8": "n", "H8": "r", "A7": "p", "B7": "p", "C7": "p", "D7": "p", "E7": "p", "F7": "p", "G7": "p", "H7": "p", "A2": "P", "B2": "P", "C2": "P", "D2": "P", "E2": "P", "F2": "P", "G2": "P", "H2": "P", "A1": "R", "B1": "N", "C1": "B", "D1": "Q", "E1": "K", "F1": "B", "G1": "N", "H1": "R" } ORTHOGONAL_POSITIONS = ((-1,0),(0,1),(1,0),(0, -1)) DIAGONAL_POSITIONS = ((-1,-1),(-1,1),(1,-1),(1,1)) KNIGHT_POSITIONS = ((-2,-1),(-2,1),(-1,-2),(-1,2),(1,-2),(1,2),(2,-1),(2,1)) ================================================ FILE: Chapter 04/4.04/controller.py ================================================ """ Code illustration: 4.04 @ Tkinter GUI Application Development Blueprints """ from configurations import * import model import piece class Controller(): def __init__(self): self.init_model() def init_model(self): self.model = model.Model() def get_all_peices_on_chess_board(self): return self.model.items() def reset_game_data(self): self.model.reset_game_data() def reset_to_initial_locations(self): self.model.reset_to_initial_locations() def get_numeric_notation(self, position): return piece.get_numeric_notation(position) ================================================ FILE: Chapter 04/4.04/exceptions.py ================================================ """ Code illustration: 4.04 @ Tkinter GUI Application Development Blueprints """ class ChessError(Exception): pass ================================================ FILE: Chapter 04/4.04/model.py ================================================ """ Code illustration: 4.04 New methods added here: all_positions_occupied_by_color(color) all_occupied_positions() @ Tkinter GUI Application Development Blueprints """ from configurations import * import piece class Model(dict): captured_pieces = {'white': [], 'black': []} player_turn = None halfmove_clock = 0 fullmove_number = 1 history = [] def __init__(self): self.reset_to_initial_locations() def all_positions_occupied_by_color(self, color): result = [] for position in self.keys(): piece = self.get_piece_at(position) if piece.color == color: result.append(position) return result def all_occupied_positions(self): return self.all_positions_occupied_by_color('white') + self.all_positions_occupied_by_color('black') def reset_game_data(self): captured_pieces = {'white': [], 'black': []} player_turn = None halfmove_clock = 0 fullmove_number = 1 history = [] def reset_to_initial_locations(self): self.clear() for position, value in START_PIECES_POSITION.items(): self[position] = piece.create_piece(value) self[position].keep_reference(self) self.player_turn = 'white' def get_piece_at(self, position): return self.get(position) def get_alphanumeric_position(self, rowcol): if self.is_on_board(rowcol): row, col = rowcol return "{}{}".format(X_AXIS_LABELS[col], Y_AXIS_LABELS[row]) def is_on_board(self, rowcol): row, col = rowcol return 0 <= row <= 7 and 0 <= col <= 7 ================================================ FILE: Chapter 04/4.04/piece.py ================================================ """ Code illustration: 4.04 Methods Added - moves_available(current_position, directions, distance) Classes Modified: all classes @ Tkinter GUI Application Development Blueprints """ from configurations import * import sys import exceptions def create_piece(piece, color='white'): if isinstance(piece, str): if piece.upper() in SHORT_NAME.keys(): color = "white" if piece.isupper() else "black" piece = SHORT_NAME[piece.upper()] piece = piece.capitalize() if piece in SHORT_NAME.values(): return eval("{classname}(color)".format(classname=piece)) raise exceptions.ChessError("invalid piece name: '{}'".format(piece)) def get_numeric_notation(rowcol): row, col = rowcol return int(col) - 1, X_AXIS_LABELS.index(row) class Piece(): def __init__(self, color): self.name = self.__class__.__name__.lower() if color == 'black': self.name = self.name.lower() elif color == 'white': self.name = self.name.upper() self.color = color def keep_reference(self, model): self.model = model def moves_available(self, current_position, directions, distance): model = self.model allowed_moves = [] piece = self start_row, start_column = get_numeric_notation(current_position) for x, y in directions: collision = False for step in range(1, distance + 1): if collision: break destination = start_row + step * x, start_column + step * y if self.possible_position(destination) not in model.all_occupied_positions(): allowed_moves.append(destination) elif self.possible_position(destination) in model.all_positions_occupied_by_color(piece.color): collision = True else: allowed_moves.append(destination) collision = True allowed_moves = filter(model.is_on_board, allowed_moves) return map(model.get_alphanumeric_position, allowed_moves) def possible_position(self, destination): return self.model.get_alphanumeric_position(destination) class King(Piece): directions = ORTHOGONAL_POSITIONS + DIAGONAL_POSITIONS max_distance = 1 def moves_available(self, current_position): return super().moves_available(current_position.upper(), self.directions, self.max_distance) class Queen(Piece): directions = ORTHOGONAL_POSITIONS + DIAGONAL_POSITIONS max_distance = 8 def moves_available(self, current_position): return super().moves_available(current_position.upper(), self.directions, self.max_distance) class Rook(Piece): directions = ORTHOGONAL_POSITIONS max_distance = 8 def moves_available(self, current_position): return super().moves_available(current_position.upper(), self.directions, self.max_distance) class Bishop(Piece): directions = DIAGONAL_POSITIONS max_distance = 8 def moves_available(self, current_position): return super().moves_available(current_position.upper(), self.directions, self.max_distance) class Knight(Piece): def moves_available(self, current_position): model = self.model allowed_moves = [] start_position = get_numeric_notation(current_position.upper()) piece = model.get(pos.upper()) for x, y in KNIGHT_POSITIONS: destination = start_position[0] + x, start_position[1] + y if(model.get_alphanumeric_position(destination) not in model.all_positions_occupied_by_color(piece.color)): allowed_moves.append(destination) allowed_moves = filter(model.is_on_board, allowed_moves) return map(model.get_alphanumeric_position, allowed_moves) class Pawn(Piece): def moves_available(self, current_position): model = self.model piece = self if self.color == 'white': initial_position, direction, enemy = 1, 1, 'black' else: initial_position, direction, enemy = 6, -1, 'white' allowed_moves = [] # Moving prohibited = model.all_occupied_positions() start_position = get_numeric_notation(current_position.upper()) forward = start_position[0] + direction, start_position[1] if model.get_alphanumeric_position(forward) not in prohibited: allowed_moves.append(forward) if start_position[0] == initial_position: # If pawn is in starting position allow double moves double_forward = (forward[0] + direction, forward[1]) if model.get_alphanumeric_position(double_forward) not in prohibited: allowed_moves.append(double_forward) # Attacking for a in range(-1, 2, 2): attack = start_position[0] + direction, start_position[1] + a if model.get_alphanumeric_position(attack) in model.all_positions_occupied_by_color(enemy): allowed_moves.append(attack) allowed_moves = filter(model.is_on_board, allowed_moves) return map(model.get_alphanumeric_position, allowed_moves) ================================================ FILE: Chapter 04/4.04/view.py ================================================ """ Code illustration: 4.04 New attributes added here: images = {} New methods defined here: draw_single_piece(position, piece) calculate_piece_coordinate(x, y) draw_all_pieces() start_new_game() @ Tkinter GUI Application Development Blueprints """ from tkinter import Tk, Menu, Label, Frame, Canvas, RIGHT, PhotoImage, messagebox import controller from configurations import * class View(): images = {} board_color_1 = BOARD_COLOR_1 board_color_2 = BOARD_COLOR_2 def __init__(self, parent, controller): self.controller = controller self.parent = parent self.create_chess_base() self.canvas.bind("", self.on_square_clicked) self.start_new_game() def create_chess_base(self): self.create_top_menu() self.create_canvas() self.draw_board() self.create_bottom_frame() def create_top_menu(self): self.menu_bar = Menu(self.parent) self.create_file_menu() self.create_edit_menu() self.create_about_menu() def create_bottom_frame(self): self.bottom_frame = Frame(self.parent, height=64) self.info_label = Label( self.bottom_frame, text=" White to Start the Game ", fg="black") self.info_label.pack(side=RIGHT, padx=8, pady=5) self.bottom_frame.pack(fill="x", side="bottom") def on_about_menu_clicked(self): messagebox.showinfo("From the Book:", "Tkinter GUI Application\n Development Blueprints") def on_new_game_menu_clicked(self): pass def on_preference_menu_clicked(self): pass def create_file_menu(self): self.file_menu = Menu(self.menu_bar, tearoff=0) self.file_menu.add_command( label="New Game", command=self.on_new_game_menu_clicked) self.menu_bar.add_cascade(label="File", menu=self.file_menu) self.parent.config(menu=self.menu_bar) def create_edit_menu(self): self.edit_menu = Menu(self.menu_bar, tearoff=0) self.edit_menu.add_command( label="Preferences", command=self.on_preference_menu_clicked) self.menu_bar.add_cascade(label="Edit", menu=self.edit_menu) self.parent.config(menu=self.menu_bar) def create_about_menu(self): self.about_menu = Menu(self.menu_bar, tearoff=0) self.about_menu.add_command( label="About", command=self.on_about_menu_clicked) self.menu_bar.add_cascade(label="About", menu=self.about_menu) self.parent.config(menu=self.menu_bar) def create_canvas(self): canvas_width = NUMBER_OF_COLUMNS * DIMENSION_OF_EACH_SQUARE canvas_height = NUMBER_OF_ROWS * DIMENSION_OF_EACH_SQUARE self.canvas = Canvas( self.parent, width=canvas_width, height=canvas_height) self.canvas.pack(padx=8, pady=8) def draw_board(self): current_color = BOARD_COLOR_2 for row in range(NUMBER_OF_ROWS): current_color = self.get_alternate_color(current_color) for col in range(NUMBER_OF_COLUMNS): x1, y1 = self.get_x_y_coordinate(row, col) x2, y2 = x1 + DIMENSION_OF_EACH_SQUARE, y1 + DIMENSION_OF_EACH_SQUARE self.canvas.create_rectangle( x1, y1, x2, y2, fill=current_color) current_color = self.get_alternate_color(current_color) def on_square_clicked(self, event): clicked_row, clicked_column = self.get_clicked_row_column(event) print("Hey you clicked on", clicked_row, clicked_column) def get_clicked_row_column(self, event): col_size = row_size = DIMENSION_OF_EACH_SQUARE clicked_column = event.x // col_size clicked_row = 7 - (event.y // row_size) return (clicked_row, clicked_column) def get_x_y_coordinate(self, row, col): x = (col * DIMENSION_OF_EACH_SQUARE) y = ((7 - row) * DIMENSION_OF_EACH_SQUARE) return (x, y) def calculate_piece_coordinate(self, row, col): x0 = (col * DIMENSION_OF_EACH_SQUARE) + \ int(DIMENSION_OF_EACH_SQUARE / 2) y0 = ((7 - row) * DIMENSION_OF_EACH_SQUARE) + \ int(DIMENSION_OF_EACH_SQUARE / 2) return (x0, y0) def draw_single_piece(self, position, piece): x, y = self.controller.get_numeric_notation(position) if piece: filename = "../pieces_image/{}_{}.png".format( piece.name.lower(), piece.color) if filename not in self.images: self.images[filename] = PhotoImage(file=filename) x0, y0 = self.calculate_piece_coordinate(x, y) self.canvas.create_image(x0, y0, image=self.images[ filename], tags=("occupied"), anchor="c") def draw_all_pieces(self): self.canvas.delete("occupied") for position, piece in self.controller.get_all_peices_on_chess_board(): self.draw_single_piece(position, piece) def start_new_game(self): self.controller.reset_game_data() self.controller.reset_to_initial_locations() self.draw_all_pieces() self.info_label.config(text=" White to Start the Game ") def get_alternate_color(self, current_color): if current_color == self.board_color_2: next_color = self.board_color_1 else: next_color = self.board_color_2 return next_color def main(controller): root = Tk() root.title("Chess") View(root, controller) root.mainloop() def init_new_game(): game_controller = controller.Controller() main(game_controller) if __name__ == "__main__": init_new_game() ================================================ FILE: Chapter 04/4.05/configurations.py ================================================ """ Code illustration: 4.05 @ Tkinter GUI Application Development Blueprints """ NUMBER_OF_ROWS = 8 NUMBER_OF_COLUMNS = 8 DIMENSION_OF_EACH_SQUARE = 64 BOARD_COLOR_1 = "#e6a803" BOARD_COLOR_2 = "#8b8350" X_AXIS_LABELS = ('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H') Y_AXIS_LABELS = (1, 2, 3, 4, 5, 6, 7, 8) SHORT_NAME = { 'R': 'Rook', 'N': 'Knight', 'B': 'Bishop', 'Q': 'Queen', 'K': 'King', 'P': 'Pawn' } # remember capital letters - White pieces, Small letters - Black pieces START_PIECES_POSITION = { "A8": "r", "B8": "n", "C8": "b", "D8": "q", "E8": "k", "F8": "b", "G8": "n", "H8": "r", "A7": "p", "B7": "p", "C7": "p", "D7": "p", "E7": "p", "F7": "p", "G7": "p", "H7": "p", "A2": "P", "B2": "P", "C2": "P", "D2": "P", "E2": "P", "F2": "P", "G2": "P", "H2": "P", "A1": "R", "B1": "N", "C1": "B", "D1": "Q", "E1": "K", "F1": "B", "G1": "N", "H1": "R" } ORTHOGONAL_POSITIONS = ((-1, 0), (0, 1), (1, 0), (0, -1)) DIAGONAL_POSITIONS = ((-1, -1), (-1, 1), (1, -1), (1, 1)) KNIGHT_POSITIONS = ((-2, -1), (-2, 1), (-1, -2), (-1, 2), (1, -2), (1, 2), (2, -1), (2, 1)) ================================================ FILE: Chapter 04/4.05/controller.py ================================================ """ Code illustration: 4.05 @ Tkinter GUI Application Development Blueprints """ from configurations import * import model import piece class Controller(): def __init__(self): self.init_model() def init_model(self): self.model = model.Model() def get_all_peices_on_chess_board(self): return self.model.items() def reset_game_data(self): self.model.reset_game_data() def reset_to_initial_locations(self): self.model.reset_to_initial_locations() def get_numeric_notation(self, position): return piece.get_numeric_notation(position) ================================================ FILE: Chapter 04/4.05/exceptions.py ================================================ """ Code illustration: 4.05 @ Tkinter GUI Application Development Blueprints """ class ChessError(Exception): pass ================================================ FILE: Chapter 04/4.05/model.py ================================================ """ Code illustration: 4.05 New methods added here: get_all_available_moves(color) @ Tkinter GUI Application Development Blueprints """ from configurations import * import piece class Model(dict): captured_pieces = {'white': [], 'black': []} player_turn = None halfmove_clock = 0 fullmove_number = 1 history = [] def __init__(self): self.reset_to_initial_locations() def get_all_available_moves(self, color): result = [] for position in self.keys(): piece = self.get_piece_at(position) if piece and piece.color == color: moves = piece.moves_available(position) if moves: result.extend(moves) return result def all_positions_occupied_by_color(self, color): result = [] for position in self.keys(): piece = self.get_piece_at(position) if piece.color == color: result.append(position) return result def all_occupied_positions(self): return self.all_positions_occupied_by_color('white') + self.all_positions_occupied_by_color('black') def reset_game_data(self): captured_pieces = {'white': [], 'black': []} player_turn = None halfmove_clock = 0 fullmove_number = 1 history = [] def reset_to_initial_locations(self): self.clear() for position, value in START_PIECES_POSITION.items(): self[position] = piece.create_piece(value) self[position].keep_reference(self) self.player_turn = 'white' def get_piece_at(self, position): return self.get(position) def get_alphanumeric_position(self, rowcol): if self.is_on_board(rowcol): row, col = rowcol return "{}{}".format(X_AXIS_LABELS[col], Y_AXIS_LABELS[row]) def is_on_board(self, rowcol): row, col = rowcol return 0 <= row <= 7 and 0 <= col <= 7 def get_alphanumeric_position_of_king(self, color): for position in self.keys(): this_piece = self.get_piece_at(position) if isinstance(this_piece, piece.King) and this_piece.color == color: return position def is_king_under_check(self, color): position_of_king = self.get_alphanumeric_position_of_king(color) opponent = ('black' if color == 'white' else 'white') return position_of_king in self.get_all_available_moves(opponent) ================================================ FILE: Chapter 04/4.05/piece.py ================================================ """ Code illustration: 4.05 @ Tkinter GUI Application Development Blueprints """ from configurations import * import sys import exceptions def create_piece(piece, color='white'): if isinstance(piece, str): if piece.upper() in SHORT_NAME.keys(): color = "white" if piece.isupper() else "black" piece = SHORT_NAME[piece.upper()] piece = piece.capitalize() if piece in SHORT_NAME.values(): return eval("{classname}(color)".format(classname=piece)) raise exceptions.ChessError("invalid piece name: '{}'".format(piece)) def get_numeric_notation(rowcol): row, col = rowcol return int(col) - 1, X_AXIS_LABELS.index(row) class Piece(): def __init__(self, color): self.name = self.__class__.__name__.lower() if color == 'black': self.name = self.name.lower() elif color == 'white': self.name = self.name.upper() self.color = color def keep_reference(self, model): self.model = model def moves_available(self, current_position, directions, distance): model = self.model allowed_moves = [] piece = self start_row, start_column = get_numeric_notation(current_position) for x, y in directions: collision = False for step in range(1, distance + 1): if collision: break destination = start_row + step * x, start_column + step * y if self.possible_position(destination) not in model.all_occupied_positions(): allowed_moves.append(destination) elif self.possible_position(destination) in model.all_positions_occupied_by_color(piece.color): collision = True else: allowed_moves.append(destination) collision = True allowed_moves = filter(model.is_on_board, allowed_moves) return map(model.get_alphanumeric_position, allowed_moves) def possible_position(self, destination): return self.model.get_alphanumeric_position(destination) class King(Piece): directions = ORTHOGONAL_POSITIONS + DIAGONAL_POSITIONS max_distance = 1 def moves_available(self, current_position): return super().moves_available(current_position, self.directions, self.max_distance) class Queen(Piece): directions = ORTHOGONAL_POSITIONS + DIAGONAL_POSITIONS max_distance = 8 def moves_available(self, current_position): return super().moves_available(current_position, self.directions, self.max_distance) class Rook(Piece): directions = ORTHOGONAL_POSITIONS max_distance = 8 def moves_available(self, current_position): return super().moves_available(current_position, self.directions, self.max_distance) class Bishop(Piece): directions = DIAGONAL_POSITIONS max_distance = 8 def moves_available(self, current_position): return super().moves_available(current_position, self.directions, self.max_distance) class Knight(Piece): def moves_available(self, current_position): model = self.model allowed_moves = [] start_col, start_row = get_numeric_notation(current_position.upper()) piece = model.get(pos.upper()) for x, y in KNIGHT_POSITIONS: destination = start_col + x, start_row + y if model.get_alphanumeric_position(destination) not in model.all_positions_occupied_by_color(piece.color): allowed_moves.append(destination) allowed_moves = filter(model.is_on_board, allowed_moves) return map(model.get_alphanumeric_position, allowed_moves) class Pawn(Piece): def moves_available(self, current_position): model = self.model piece = self if self.color == 'white': initial_position, direction, enemy = 1, 1, 'black' else: initial_position, direction, enemy = 6, -1, 'white' allowed_moves = [] # Moving prohibited = model.all_occupied_positions() start_position = get_numeric_notation(current_position.upper()) forward = start_position[0] + direction, start_position[1] if model.get_alphanumeric_position(forward) not in prohibited: allowed_moves.append(forward) if start_position[0] == initial_position: # If pawn is in starting position allow double moves double_forward = (forward[0] + direction, forward[1]) if model.get_alphanumeric_position(double_forward) not in prohibited: allowed_moves.append(double_forward) # Attacking for a in range(-1, 2, 2): attack = start_position[0] + direction, start_position[1] + a if model.get_alphanumeric_position(attack) in model.all_positions_occupied_by_color(enemy): allowed_moves.append(attack) allowed_moves = filter(model.is_on_board, allowed_moves) return map(model.get_alphanumeric_position, allowed_moves) ================================================ FILE: Chapter 04/4.05/view.py ================================================ """ Code illustration: 4.05 @ Tkinter GUI Application Development Blueprints """ from tkinter import Tk, Menu, Label, Frame, Canvas, RIGHT, PhotoImage, messagebox import controller from configurations import * class View(): images = {} board_color_1 = BOARD_COLOR_1 board_color_2 = BOARD_COLOR_2 def __init__(self, parent, controller): self.controller = controller self.parent = parent self.create_chess_base() self.canvas.bind("", self.on_square_clicked) self.start_new_game() def create_chess_base(self): self.create_top_menu() self.create_canvas() self.draw_board() self.create_bottom_frame() def create_top_menu(self): self.menu_bar = Menu(self.parent) self.create_file_menu() self.create_edit_menu() self.create_about_menu() def create_bottom_frame(self): self.bottom_frame = Frame(self.parent, height=64) self.info_label = Label( self.bottom_frame, text=" White to Start the Game ", fg="black") self.info_label.pack(side=RIGHT, padx=8, pady=5) self.bottom_frame.pack(fill="x", side="bottom") def on_about_menu_clicked(self): messagebox.showinfo("From the Book:", "Tkinter GUI Application\n Development Blueprints") def on_new_game_menu_clicked(self): pass def on_preference_menu_clicked(self): pass def create_file_menu(self): self.file_menu = Menu(self.menu_bar, tearoff=0) self.file_menu.add_command( label="New Game", command=self.on_new_game_menu_clicked) self.menu_bar.add_cascade(label="File", menu=self.file_menu) self.parent.config(menu=self.menu_bar) def create_edit_menu(self): self.edit_menu = Menu(self.menu_bar, tearoff=0) self.edit_menu.add_command( label="Preferences", command=self.on_preference_menu_clicked) self.menu_bar.add_cascade(label="Edit", menu=self.edit_menu) self.parent.config(menu=self.menu_bar) def create_about_menu(self): self.about_menu = Menu(self.menu_bar, tearoff=0) self.about_menu.add_command( label="About", command=self.on_about_menu_clicked) self.menu_bar.add_cascade(label="About", menu=self.about_menu) self.parent.config(menu=self.menu_bar) def create_canvas(self): canvas_width = NUMBER_OF_COLUMNS * DIMENSION_OF_EACH_SQUARE canvas_height = NUMBER_OF_ROWS * DIMENSION_OF_EACH_SQUARE self.canvas = Canvas( self.parent, width=canvas_width, height=canvas_height) self.canvas.pack(padx=8, pady=8) def draw_board(self): current_color = BOARD_COLOR_2 for row in range(NUMBER_OF_ROWS): current_color = self.get_alternate_color(current_color) for col in range(NUMBER_OF_COLUMNS): x1, y1 = self.get_x_y_coordinate(row, col) x2, y2 = x1 + DIMENSION_OF_EACH_SQUARE, y1 + DIMENSION_OF_EACH_SQUARE self.canvas.create_rectangle( x1, y1, x2, y2, fill=current_color) current_color = self.get_alternate_color(current_color) def on_square_clicked(self, event): clicked_row, clicked_column = self.get_clicked_row_column(event) print("Hey you clicked on", clicked_row, clicked_column) def get_clicked_row_column(self, event): col_size = row_size = DIMENSION_OF_EACH_SQUARE clicked_column = event.x // col_size clicked_row = 7 - (event.y // row_size) return (clicked_row, clicked_column) def get_x_y_coordinate(self, row, col): x = (col * DIMENSION_OF_EACH_SQUARE) y = ((7 - row) * DIMENSION_OF_EACH_SQUARE) return (x, y) def calculate_piece_coordinate(self, row, col): x0 = (col * DIMENSION_OF_EACH_SQUARE) + \ int(DIMENSION_OF_EACH_SQUARE / 2) y0 = ((7 - row) * DIMENSION_OF_EACH_SQUARE) + \ int(DIMENSION_OF_EACH_SQUARE / 2) return (x0, y0) def draw_single_piece(self, position, piece): x, y = self.controller.get_numeric_notation(position) if piece: filename = "../pieces_image/{}_{}.png".format( piece.name.lower(), piece.color) if filename not in self.images: self.images[filename] = PhotoImage(file=filename) x0, y0 = self.calculate_piece_coordinate(x, y) self.canvas.create_image(x0, y0, image=self.images[ filename], tags=("occupied"), anchor="c") def draw_all_pieces(self): self.canvas.delete("occupied") for position, piece in self.controller.get_all_peices_on_chess_board(): self.draw_single_piece(position, piece) def start_new_game(self): self.controller.reset_game_data() self.controller.reset_to_initial_locations() self.draw_all_pieces() self.info_label.config(text=" White to Start the Game ") def get_alternate_color(self, current_color): if current_color == self.board_color_2: next_color = self.board_color_1 else: next_color = self.board_color_2 return next_color def main(controller): root = Tk() root.title("Chess") View(root, controller) root.mainloop() def init_new_game(): game_controller = controller.Controller() main(game_controller) if __name__ == "__main__": init_new_game() ================================================ FILE: Chapter 04/4.06/configurations.py ================================================ """ Code illustration: 4.06 added constant HIGHLIGHT_COLOR = "#2EF70D" @ Tkinter GUI Application Development Blueprints """ NUMBER_OF_ROWS = 8 NUMBER_OF_COLUMNS = 8 DIMENSION_OF_EACH_SQUARE = 64 BOARD_COLOR_1 = "#e6a803" BOARD_COLOR_2 = "#8b8350" HIGHLIGHT_COLOR = "#2EF70D" X_AXIS_LABELS = ('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H') Y_AXIS_LABELS = (1, 2, 3, 4, 5, 6, 7, 8) SHORT_NAME = { 'R': 'Rook', 'N': 'Knight', 'B': 'Bishop', 'Q': 'Queen', 'K': 'King', 'P': 'Pawn' } # remember capital letters - White pieces, Small letters - Black pieces START_PIECES_POSITION = { "A8": "r", "B8": "n", "C8": "b", "D8": "q", "E8": "k", "F8": "b", "G8": "n", "H8": "r", "A7": "p", "B7": "p", "C7": "p", "D7": "p", "E7": "p", "F7": "p", "G7": "p", "H7": "p", "A2": "P", "B2": "P", "C2": "P", "D2": "P", "E2": "P", "F2": "P", "G2": "P", "H2": "P", "A1": "R", "B1": "N", "C1": "B", "D1": "Q", "E1": "K", "F1": "B", "G1": "N", "H1": "R" } ORTHOGONAL_POSITIONS = ((-1, 0), (0, 1), (1, 0), (0, -1)) DIAGONAL_POSITIONS = ((-1, -1), (-1, 1), (1, -1), (1, 1)) KNIGHT_POSITIONS = ((-2, -1), (-2, 1), (-1, -2), (-1, 2), (1, -2), (1, 2), (2, -1), (2, 1)) ================================================ FILE: Chapter 04/4.06/controller.py ================================================ """ Code illustration: 4.06 New methods added here: shift( start_pos,end_pos) get_all_peices_on_chess_board() player_turn() moves_available( position) @ Tkinter GUI Application Development Blueprints """ import model import piece class Controller(): def __init__(self): self.init_model() def init_model(self): self.model = model.Model() def reset_game_data(self): self.model.reset_game_data() def reset_to_initial_locations(self): self.model.reset_to_initial_locations() def get_alphanumeric_position(self, rowcolumntuple): return self.model.get_alphanumeric_position(rowcolumntuple) def get_numeric_notation(self, rowcol): return piece.get_numeric_notation(rowcol) def get_piece_at(self, position_of_click): return self.model.get_piece_at(position_of_click) def pre_move_validation(self, start_pos, end_pos): return self.model.pre_move_validation(start_pos, end_pos) def get_all_peices_on_chess_board(self): return self.model.items() def player_turn(self): return self.model.player_turn def moves_available(self, position): return self.model.moves_available(position) ================================================ FILE: Chapter 04/4.06/exceptions.py ================================================ """ Code illustration: 4.06 New classes added here: InvalidMove(ChessError) CheckMate(ChessError) Draw(ChessError) NotYourTurn(ChessError) InvalidCoord(ChessError) @ Tkinter GUI Application Development Blueprints """ class ChessError(Exception): pass class Check(ChessError): pass class InvalidMove(ChessError): pass class CheckMate(ChessError): pass class Draw(ChessError): pass class NotYourTurn(ChessError): pass class InvalidCoord(ChessError): pass ================================================ FILE: Chapter 04/4.06/model.py ================================================ """ Code illustration: 4.06 New imports from copy import deepcopy import exceptions New methods added pre_move_validation(self, initial_pos, final_pos) is_king_under_check(color) update_game_statistics(piece, dest, p1, p2) move(start_pos, final_pos) will_move_cause_check(p1, p2) get_alphanumeric_position_of_king(color) @ Tkinter GUI Application Development Blueprints """ from copy import deepcopy import exceptions import piece from configurations import * class Model(dict): captured_pieces = {'white': [], 'black': []} player_turn = None halfmove_clock = 0 fullmove_number = 1 history = [] def __init__(self): self.reset_to_initial_locations() def update_game_statistics(self, piece, dest, start_pos, end_pos): if piece.color == 'black': self.fullmove_number += 1 self.halfmove_clock += 1 abbr = piece.name if abbr == 'pawn': abbr = '' self.halfmove_clock = 0 if dest is None: move_text = abbr + end_pos.lower() else: move_text = abbr + 'x' + end_pos.lower() self.halfmove_clock = 0 self.history.append(move_text) def change_player_turn(self, color): enemy = ('white' if color == 'black' else 'black') self.player_turn = enemy def pre_move_validation(self, initial_pos, final_pos): initial_pos, final_pos = initial_pos.upper(), final_pos.upper() piece = self.get_piece_at(initial_pos) try: piece_at_destination = self.get_piece_at(final_pos) except: piece_at_destination = None if self.player_turn != piece.color: raise exceptions.NotYourTurn("Not " + piece.color + "'s turn!") enemy = ('white' if piece.color == 'black' else 'black') moves_available = piece.moves_available(initial_pos) if final_pos not in moves_available: raise exceptions.InvalidMove if self.get_all_available_moves(enemy): if self.will_move_cause_check(initial_pos, final_pos): raise exceptions.Check if not moves_available and self.is_king_under_check(piece.color): raise exceptions.CheckMate elif not moves_available: raise exceptions.Draw else: self.move(initial_pos, final_pos) self.update_game_statistics( piece, piece_at_destination, initial_pos, final_pos) self.change_player_turn(piece.color) def move(self, start_pos, final_pos): self[final_pos] = self.pop(start_pos, None) def will_move_cause_check(self, start_position, end_position): tmp = deepcopy(self) tmp.move(start_position, end_position) return tmp.is_king_under_check(self[start_position].color) def get_all_available_moves(self, color): result = [] for position in self.keys(): piece = self.get_piece_at(position) if piece and piece.color == color: moves = piece.moves_available(position) if moves: result.extend(moves) return result def is_king_under_check(self, color): position_of_king = self.get_alphanumeric_position_of_king(color) opponent = ('black' if color == 'white' else 'white') return position_of_king in self.get_all_available_moves(opponent) def get_alphanumeric_position_of_king(self, color): for position in self.keys(): this_piece = self.get_piece_at(position) if isinstance(this_piece, piece.King) and this_piece.color == color: return position def all_positions_occupied_by_color(self, color): result = [] for position in self.keys(): piece = self.get_piece_at(position) if piece.color == color: result.append(position) return result def all_occupied_positions(self): return self.all_positions_occupied_by_color('white') + self.all_positions_occupied_by_color('black') def reset_game_data(self): captured_pieces = {'white': [], 'black': []} player_turn = None halfmove_clock = 0 fullmove_number = 1 history = [] def reset_to_initial_locations(self): self.clear() for position, value in START_PIECES_POSITION.items(): self[position] = piece.create_piece(value) self[position].keep_reference(self) self.player_turn = 'white' def get_piece_at(self, position): return self.get(position) def get_alphanumeric_position(self, rowcol): if self.is_on_board(rowcol): row, col = rowcol return "{}{}".format(X_AXIS_LABELS[col], Y_AXIS_LABELS[row]) def is_on_board(self, rowcol): row, col = rowcol return 0 <= row <= 7 and 0 <= col <= 7 ================================================ FILE: Chapter 04/4.06/piece.py ================================================ """ Code illustration: 4.06 @ Tkinter GUI Application Development Blueprints """ from configurations import * import sys import exceptions def create_piece(piece, color='white'): if isinstance(piece, str): if piece.upper() in SHORT_NAME.keys(): color = "white" if piece.isupper() else "black" piece = SHORT_NAME[piece.upper()] piece = piece.capitalize() if piece in SHORT_NAME.values(): return eval("{classname}(color)".format(classname=piece)) raise exceptions.ChessError("invalid piece name: '{}'".format(piece)) def get_numeric_notation(rowcol): row, col = rowcol return int(col) - 1, X_AXIS_LABELS.index(row) class Piece(): def __init__(self, color): self.name = self.__class__.__name__.lower() if color == 'black': self.name = self.name.lower() elif color == 'white': self.name = self.name.upper() self.color = color def keep_reference(self, model): self.model = model def moves_available(self, current_position, directions, distance): model = self.model allowed_moves = [] piece = self start_row, start_column = get_numeric_notation(current_position) for x, y in directions: collision = False for step in range(1, distance + 1): if collision: break destination = start_row + step * x, start_column + step * y if self.possible_position(destination) not in model.all_occupied_positions(): allowed_moves.append(destination) elif self.possible_position(destination) in model.all_positions_occupied_by_color(piece.color): collision = True else: allowed_moves.append(destination) collision = True allowed_moves = filter(model.is_on_board, allowed_moves) return map(model.get_alphanumeric_position, allowed_moves) def possible_position(self, destination): return self.model.get_alphanumeric_position(destination) class King(Piece): directions = ORTHOGONAL_POSITIONS + DIAGONAL_POSITIONS max_distance = 1 def moves_available(self, current_position): return super().moves_available(current_position, self.directions, self.max_distance) class Queen(Piece): directions = ORTHOGONAL_POSITIONS + DIAGONAL_POSITIONS max_distance = 8 def moves_available(self, current_position): return super().moves_available(current_position, self.directions, self.max_distance) class Rook(Piece): directions = ORTHOGONAL_POSITIONS max_distance = 8 def moves_available(self, current_position): return super().moves_available(current_position, self.directions, self.max_distance) class Bishop(Piece): directions = DIAGONAL_POSITIONS max_distance = 8 def moves_available(self, current_position): return super().moves_available(current_position, self.directions, self.max_distance) class Knight(Piece): def moves_available(self, current_position): model = self.model allowed_moves = [] start_col, start_row = get_numeric_notation(current_position.upper()) piece = model.get(current_position) for x, y in KNIGHT_POSITIONS: destination = start_col + x, start_row + y if model.get_alphanumeric_position(destination) not in model.all_positions_occupied_by_color(piece.color): allowed_moves.append(destination) allowed_moves = filter(model.is_on_board, allowed_moves) return map(model.get_alphanumeric_position, allowed_moves) class Pawn(Piece): def moves_available(self, current_position): model = self.model piece = self if self.color == 'white': initial_position, direction, enemy = 1, 1, 'black' else: initial_position, direction, enemy = 6, -1, 'white' allowed_moves = [] # Moving prohibited = model.all_occupied_positions() start_position = get_numeric_notation(current_position.upper()) forward = start_position[0] + direction, start_position[1] if model.get_alphanumeric_position(forward) not in prohibited: allowed_moves.append(forward) if start_position[0] == initial_position: # If pawn is in starting position allow double moves double_forward = (forward[0] + direction, forward[1]) if model.get_alphanumeric_position(double_forward) not in prohibited: allowed_moves.append(double_forward) # Attacking for a in range(-1, 2, 2): attack = start_position[0] + direction, start_position[1] + a if model.get_alphanumeric_position(attack) in model.all_positions_occupied_by_color(enemy): allowed_moves.append(attack) allowed_moves = filter(model.is_on_board, allowed_moves) return map(model.get_alphanumeric_position, allowed_moves) ================================================ FILE: Chapter 04/4.06/view.py ================================================ """ Code illustration: 4.06 Attributes added selected_piece_position = None all_squares_to_be_highlighted = [] Methods added update_label(piece, start_pos, end_pos) update_highlight_list(position) shift( start_pos, end_pos) Method modified on_square_clicked(event) @ Tkinter GUI Application Development Blueprints """ from tkinter import Tk, Menu, Label, Frame, Canvas, RIGHT, PhotoImage, messagebox import controller import exceptions from configurations import * class View(): selected_piece_position = None all_squares_to_be_highlighted = [] images = {} board_color_1 = BOARD_COLOR_1 board_color_2 = BOARD_COLOR_2 highlight_color = HIGHLIGHT_COLOR def __init__(self, parent, controller): self.controller = controller self.parent = parent self.create_chess_base() self.canvas.bind("", self.on_square_clicked) self.start_new_game() def create_bottom_frame(self): self.bottom_frame = Frame(self.parent, height=64) self.info_label = Label( self.bottom_frame, text=" White to Start the Game ", fg="black") self.info_label.pack(side=RIGHT, padx=8, pady=5) self.bottom_frame.pack(fill="x", side="bottom") def create_top_menu(self): self.menu_bar = Menu(self.parent) self.create_file_menu() self.create_edit_menu() self.create_about_menu() def create_file_menu(self): self.file_menu = Menu(self.menu_bar, tearoff=0) self.file_menu.add_command( label="New Game", command=self.on_new_game_menu_clicked) self.menu_bar.add_cascade(label="File", menu=self.file_menu) self.parent.config(menu=self.menu_bar) def create_edit_menu(self): self.edit_menu = Menu(self.menu_bar, tearoff=0) self.edit_menu.add_command( label="Preferences", command=self.on_preference_menu_clicked) self.menu_bar.add_cascade(label="Edit", menu=self.edit_menu) self.parent.config(menu=self.menu_bar) def create_about_menu(self): self.about_menu = Menu(self.menu_bar, tearoff=0) self.about_menu.add_command( label="About", command=self.on_about_menu_clicked) self.menu_bar.add_cascade(label="About", menu=self.about_menu) self.parent.config(menu=self.menu_bar) def on_preference_menu_clicked(self): self.show_prefereces_window() def show_prefereces_window(self): preferenceswindow.PreferencesWindow(self.parent) def create_canvas(self): canvas_width = NUMBER_OF_COLUMNS * DIMENSION_OF_EACH_SQUARE canvas_height = NUMBER_OF_ROWS * DIMENSION_OF_EACH_SQUARE self.canvas = Canvas( self.parent, width=canvas_width, height=canvas_height) self.canvas.pack(padx=8, pady=8) def create_chess_base(self): self.create_top_menu() self.create_canvas() self.draw_board() self.create_bottom_frame() def start_new_game(self): self.controller.reset_game_data() self.controller.reset_to_initial_locations() self.draw_all_pieces() self.info_label.config(text=" White to Start the Game ") def reset_board_state(self): self.selected_piece_position = None self.all_squares_to_be_highlighted = [] self.images = {} def on_new_game_menu_clicked(self): self.start_new_game() def get_clicked_row_column(self, event): col_size = row_size = DIMENSION_OF_EACH_SQUARE clicked_column = event.x // col_size clicked_row = 7 - (event.y // row_size) return (clicked_row, clicked_column) def on_square_clicked(self, event): clicked_row, clicked_column = self.get_clicked_row_column(event) position_of_click = self.controller.get_alphanumeric_position( (clicked_row, clicked_column)) # on second click if self.selected_piece_position: self.shift(self.selected_piece_position, position_of_click) self.selected_piece_position = None self.update_highlight_list(position_of_click) self.draw_board() self.draw_all_pieces() def shift(self, start_pos, end_pos): selected_piece = self.controller.get_piece_at(start_pos) piece_at_destination = self.controller.get_piece_at(end_pos) if not piece_at_destination or piece_at_destination.color != selected_piece.color: try: self.controller.pre_move_validation(start_pos, end_pos) except exceptions.ChessError as error: self.info_label["text"] = error.__class__.__name__ else: self.update_label(selected_piece, start_pos, end_pos) def update_label(self, piece, start_pos, end_pos): turn = ('white' if piece.color == 'black' else 'black') self.info_label["text"] = '' + piece.color.capitalize() + " : " + \ start_pos + end_pos + ' ' + turn.capitalize() + '\'s turn' def update_highlight_list(self, position): self.all_squares_to_be_highlighted = None try: piece = self.controller.get_piece_at(position) except: piece = None if piece and (piece.color == self.controller.player_turn()): self.selected_piece_position = position self.all_squares_to_be_highlighted = list(map( self.controller.get_numeric_notation, self.controller.get_piece_at(position).moves_available(position))) def get_x_y_coordinate(self, row, col): x = (col * DIMENSION_OF_EACH_SQUARE) y = ((7 - row) * DIMENSION_OF_EACH_SQUARE) return (x, y) def draw_board(self): current_color = BOARD_COLOR_2 for row in range(NUMBER_OF_ROWS): current_color = self.get_alternate_color(current_color) for col in range(NUMBER_OF_COLUMNS): x1, y1 = self.get_x_y_coordinate(row, col) x2, y2 = x1 + DIMENSION_OF_EACH_SQUARE, y1 + DIMENSION_OF_EACH_SQUARE if(self.all_squares_to_be_highlighted and (row, col) in self.all_squares_to_be_highlighted): self.canvas.create_rectangle( x1, y1, x2, y2, fill=HIGHLIGHT_COLOR) else: self.canvas.create_rectangle( x1, y1, x2, y2, fill=current_color) current_color = self.get_alternate_color(current_color) def calculate_piece_coordinate(self, row, col): x0 = (col * DIMENSION_OF_EACH_SQUARE) + \ int(DIMENSION_OF_EACH_SQUARE / 2) y0 = ((7 - row) * DIMENSION_OF_EACH_SQUARE) + \ int(DIMENSION_OF_EACH_SQUARE / 2) return (x0, y0) def draw_single_piece(self, position, piece): x, y = self.controller.get_numeric_notation(position) if piece: filename = "../pieces_image/{}_{}.png".format( piece.name.lower(), piece.color) if filename not in self.images: self.images[filename] = PhotoImage(file=filename) x0, y0 = self.calculate_piece_coordinate(x, y) self.canvas.create_image(x0, y0, image=self.images[ filename], tags=("occupied"), anchor="c") def draw_all_pieces(self): self.canvas.delete("occupied") for position, piece in self.controller.get_all_peices_on_chess_board(): self.draw_single_piece(position, piece) def on_about_menu_clicked(self): messagebox.showinfo("From the Book:", "Tkinter GUI Application\n Development Blueprints") def get_alternate_color(self, current_color): if current_color == self.board_color_2: next_color = self.board_color_1 else: next_color = self.board_color_2 return next_color def main(model): root = Tk() root.title("Chess") View(root, model) root.mainloop() def init_new_game(): initial_game_data = controller.Controller() main(initial_game_data) if __name__ == "__main__": init_new_game() ================================================ FILE: Chapter 04/4.07/chess_options.ini ================================================ [chess_colors] board_color_1 = #59a803 board_color_2 = #8b83d9 highlight_color = #b92129 ================================================ FILE: Chapter 04/4.07/configurations.py ================================================ """ Code illustration: 4.07 @ Tkinter GUI Application Development Blueprints """ from configparser import ConfigParser NUMBER_OF_ROWS = 8 NUMBER_OF_COLUMNS = 8 DIMENSION_OF_EACH_SQUARE = 64 X_AXIS_LABELS = ('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H') Y_AXIS_LABELS = (1, 2, 3, 4, 5, 6, 7, 8) START_PIECES_POSITION = { "A8": "r", "B8": "n", "C8": "b", "D8": "q", "E8": "k", "F8": "b", "G8": "n", "H8": "r", "A7": "p", "B7": "p", "C7": "p", "D7": "p", "E7": "p", "F7": "p", "G7": "p", "H7": "p", "A2": "P", "B2": "P", "C2": "P", "D2": "P", "E2": "P", "F2": "P", "G2": "P", "H2": "P", "A1": "R", "B1": "N", "C1": "B", "D1": "Q", "E1": "K", "F1": "B", "G1": "N", "H1": "R" } SHORT_NAME = {'R': 'Rook', 'N': 'Knight', 'B': 'Bishop', 'Q': 'Queen', 'K': 'King', 'P': 'Pawn'} ORTHOGONAL_POSITIONS = ((-1, 0), (0, 1), (1, 0), (0, -1)) DIAGONAL_POSITIONS = ((-1, -1), (-1, 1), (1, -1), (1, 1)) KNIGHT_POSITIONS = ((-2, -1), (-2, 1), (-1, -2), (-1, 2), (1, -2), (1, 2), (2, -1), (2, 1)) ''' User Modifiable Options ''' config = ConfigParser() config.read('chess_options.ini') BOARD_COLOR_1 = config.get('chess_colors', 'board_color_1', fallback="#e6a803") BOARD_COLOR_2 = config.get('chess_colors', 'board_color_2', fallback="#8b8350") HIGHLIGHT_COLOR = config.get( 'chess_colors', 'highlight_color', fallback="#2EF70D") ================================================ FILE: Chapter 04/4.07/controller.py ================================================ """ Code illustration: 4.07 @ Tkinter GUI Application Development Blueprints """ import model import piece class Controller(): def __init__(self): self.init_model() def init_model(self): self.model = model.Model() def reset_game_data(self): self.model.reset_game_data() def reset_to_initial_locations(self): self.model.reset_to_initial_locations() def get_alphanumeric_position(self, rowcolumntuple): return self.model.get_alphanumeric_position(rowcolumntuple) def get_numeric_notation(self, rowcol): return piece.get_numeric_notation(rowcol) def get_piece_at(self, position_of_click): return self.model.get_piece_at(position_of_click) def pre_move_validation(self, start_pos, end_pos): return self.model.pre_move_validation(start_pos, end_pos) def get_all_peices_on_chess_board(self): return self.model.items() def player_turn(self): return self.model.player_turn def moves_available(self, position): return self.model.moves_available(position) ================================================ FILE: Chapter 04/4.07/exceptions.py ================================================ """ Code illustration: 4.07 @ Tkinter GUI Application Development Blueprints """ class ChessError(Exception): pass class Check(ChessError): pass class InvalidMove(ChessError): pass class CheckMate(ChessError): pass class Draw(ChessError): pass class NotYourTurn(ChessError): pass class InvalidCoord(ChessError): pass ================================================ FILE: Chapter 04/4.07/model.py ================================================ """ Code illustration: 4.07 @ Tkinter GUI Application Development Blueprints """ from copy import deepcopy import exceptions import piece from configurations import * class Model(dict): captured_pieces = {'white': [], 'black': []} player_turn = None halfmove_clock = 0 fullmove_number = 1 history = [] def __init__(self): self.reset_to_initial_locations() def update_game_statistics(self, piece, dest, p1, p2): if piece.color == 'black': self.fullmove_number += 1 self.halfmove_clock += 1 abbr = piece.name if abbr == 'pawn': abbr = '' self.halfmove_clock = 0 if dest is None: movetext = abbr + p2.lower() else: movetext = abbr + 'x' + p2.lower() self.halfmove_clock = 0 self.history.append(movetext) def change_player_turn(self, color): enemy = ('white' if color == 'black' else 'black') self.player_turn = enemy def pre_move_validation(self, initial_pos, final_pos): initial_pos, final_pos = initial_pos.upper(), final_pos.upper() piece = self.get_piece_at(initial_pos) try: piece_at_destination = self.get_piece_at(final_pos) except: piece_at_destination = None if self.player_turn != piece.color: raise exceptions.NotYourTurn("Not " + piece.color + "'s turn!") enemy = ('white' if piece.color == 'black' else 'black') moves_available = piece.moves_available(initial_pos) if final_pos not in moves_available: raise exceptions.InvalidMove if self.get_all_available_moves(enemy): if self.will_move_cause_check(initial_pos, final_pos): raise exceptions.Check if not moves_available and self.is_king_under_check(piece.color): raise exceptions.CheckMate elif not moves_available: raise exceptions.Draw else: self.move(initial_pos, final_pos) self.update_game_statistics( piece, piece_at_destination, initial_pos, final_pos) self.change_player_turn(piece.color) def move(self, start_pos, final_pos): self[final_pos] = self.pop(start_pos, None) def will_move_cause_check(self, start_position, end_position): tmp = deepcopy(self) tmp.move(start_position, end_position) return tmp.is_king_under_check(self[start_position].color) def get_all_available_moves(self, color): result = [] for position in self.keys(): piece = self.get_piece_at(position) if piece and piece.color == color: moves = piece.moves_available(position) if moves: result.extend(moves) return result def is_king_under_check(self, color): position_of_king = self.get_alphanumeric_position_of_king(color) opponent = ('black' if color == 'white' else 'white') return position_of_king in self.get_all_available_moves(opponent) def get_alphanumeric_position_of_king(self, color): for position in self.keys(): this_piece = self.get_piece_at(position) if isinstance(this_piece, piece.King) and this_piece.color == color: return position def all_positions_occupied_by_color(self, color): result = [] for position in self.keys(): piece = self.get_piece_at(position) if piece.color == color: result.append(position) return result def all_occupied_positions(self): return self.all_positions_occupied_by_color('white') + self.all_positions_occupied_by_color('black') def reset_game_data(self): captured_pieces = {'white': [], 'black': []} player_turn = None halfmove_clock = 0 fullmove_number = 1 history = [] def reset_to_initial_locations(self): self.clear() for position, value in START_PIECES_POSITION.items(): self[position] = piece.create_piece(value) self[position].keep_reference(self) self.player_turn = 'white' def get_piece_at(self, position): return self.get(position) def get_alphanumeric_position(self, rowcol): if self.is_on_board(rowcol): row, col = rowcol return "{}{}".format(X_AXIS_LABELS[col], Y_AXIS_LABELS[row]) def is_on_board(self, rowcol): row, col = rowcol return 0 <= row <= 7 and 0 <= col <= 7 ================================================ FILE: Chapter 04/4.07/piece.py ================================================ """ Code illustration: 4.07 @ Tkinter GUI Application Development Blueprints """ from configurations import * import sys import exceptions def create_piece(piece, color='white'): if isinstance(piece, str): if piece.upper() in SHORT_NAME.keys(): color = "white" if piece.isupper() else "black" piece = SHORT_NAME[piece.upper()] piece = piece.capitalize() if piece in SHORT_NAME.values(): return eval("{classname}(color)".format(classname=piece)) raise exceptions.ChessError("invalid piece name: '{}'".format(piece)) def get_numeric_notation(rowcol): row, col = rowcol return int(col) - 1, X_AXIS_LABELS.index(row) class Piece(): def __init__(self, color): self.name = self.__class__.__name__.lower() if color == 'black': self.name = self.name.lower() elif color == 'white': self.name = self.name.upper() self.color = color def keep_reference(self, model): self.model = model def moves_available(self, current_position, directions, distance): model = self.model allowed_moves = [] piece = self start_row, start_column = get_numeric_notation(current_position) for x, y in directions: collision = False for step in range(1, distance + 1): if collision: break destination = start_row + step * x, start_column + step * y if self.possible_position(destination) not in model.all_occupied_positions(): allowed_moves.append(destination) elif self.possible_position(destination) in model.all_positions_occupied_by_color(piece.color): collision = True else: allowed_moves.append(destination) collision = True allowed_moves = filter(model.is_on_board, allowed_moves) return map(model.get_alphanumeric_position, allowed_moves) def possible_position(self, destination): return self.model.get_alphanumeric_position(destination) class King(Piece): directions = ORTHOGONAL_POSITIONS + DIAGONAL_POSITIONS max_distance = 1 def moves_available(self, current_position): return super().moves_available(current_position, self.directions, self.max_distance) class Queen(Piece): directions = ORTHOGONAL_POSITIONS + DIAGONAL_POSITIONS max_distance = 8 def moves_available(self, current_position): return super().moves_available(current_position, self.directions, self.max_distance) class Rook(Piece): directions = ORTHOGONAL_POSITIONS max_distance = 8 def moves_available(self, current_position): return super().moves_available(current_position, self.directions, self.max_distance) class Bishop(Piece): directions = DIAGONAL_POSITIONS max_distance = 8 def moves_available(self, current_position): return super().moves_available(current_position, self.directions, self.max_distance) class Knight(Piece): def moves_available(self, current_position): model = self.model allowed_moves = [] start_col, start_row = get_numeric_notation(current_position) piece = model.get(current_position.upper()) for x, y in KNIGHT_POSITIONS: destination = start_col + x, start_row + y if model.get_alphanumeric_position(destination) not in model.all_positions_occupied_by_color(piece.color): allowed_moves.append(destination) allowed_moves = filter(model.is_on_board, allowed_moves) return map(model.get_alphanumeric_position, allowed_moves) class Pawn(Piece): def moves_available(self, current_position): model = self.model piece = self if self.color == 'white': initial_row_position, direction, enemy = 1, 1, 'black' else: initial_row_position, direction, enemy = 6, -1, 'white' allowed_moves = [] # Moving prohibited = model.all_occupied_positions() start_col, start_row = get_numeric_notation(current_position) forward = start_col + direction, start_row if model.get_alphanumeric_position(forward) not in prohibited: allowed_moves.append(forward) if start_col == initial_row_position: # If pawn is in starting position allow double moves double_forward = (forward[0] + direction, forward[1]) if model.get_alphanumeric_position(double_forward) not in prohibited: allowed_moves.append(double_forward) # Attacking for a in range(-1, 2, 2): attack = start_col + direction, start_row + a if model.get_alphanumeric_position(attack) in model.all_positions_occupied_by_color(enemy): allowed_moves.append(attack) allowed_moves = filter(model.is_on_board, allowed_moves) return map(model.get_alphanumeric_position, allowed_moves) ================================================ FILE: Chapter 04/4.07/preferenceswindow.py ================================================ """ Code illustration: 4.07 this entire file added here @ Tkinter GUI Application Development Blueprints """ import sys from tkinter import * from tkinter import messagebox from tkinter.ttk import * from tkinter.colorchooser import * from configparser import ConfigParser import configurations class PreferencesWindow(): def __init__(self, view): self.parent = view.parent self.fill_preference_colors() self.view = view self.create_prefereces_window() def fill_preference_colors(self): self.board_color_1 = configurations.BOARD_COLOR_1 self.board_color_2 = configurations.BOARD_COLOR_2 self.highlight_color = configurations.HIGHLIGHT_COLOR def set_color_1(self): self.board_color_1 = askcolor(initialcolor=self.board_color_1)[-1] def set_color_2(self): self.board_color_2 = askcolor(initialcolor=self.board_color_2)[-1] def set_highlight_color(self): self.highlight_color = askcolor(initialcolor=self.highlight_color)[-1] def create_prefereces_window(self): self.pref_window = Toplevel(self.parent) self.pref_window.title("set chess preferences") self.create_prefereces_list() self.pref_window.transient(self.parent) def create_prefereces_list(self): Label(self.pref_window, text="Board Color 1").grid( row=1, sticky=W, padx=5, pady=5) Label(self.pref_window, text="Board Color 2").grid( row=2, sticky=W, padx=5, pady=5) Label(self.pref_window, text="Highlight Color").grid( row=3, sticky=W, padx=5, pady=5) self.board_color_1_button = Button( self.pref_window, text='Select Board Color 1', command=self.set_color_1) self.board_color_1_button.grid( row=1, column=1, columnspan=2, sticky=E, padx=5, pady=5) self.board_color_2_button = Button( self.pref_window, text='Select Board Color 2', command=self.set_color_2) self.board_color_2_button.grid( row=2, column=1, columnspan=2, sticky=E, padx=5, pady=5) self.highlight_color_button = Button( self.pref_window, text='Select Highlight Color', command=self.set_highlight_color) self.highlight_color_button.grid( row=3, column=1, columnspan=2, sticky=E, padx=5, pady=5) Button(self.pref_window, text="Save", command=self.on_save_button_clicked).grid( row=4, column=2, sticky=E, padx=5, pady=5) Button(self.pref_window, text="Cancel", command=self.on_cancel_button_clicked).grid( row=4, column=1, sticky=E, padx=5, pady=5) def on_save_button_clicked(self): self.set_new_values() self.pref_window.destroy() self.view.reload_colors( self.board_color_1, self.board_color_2, self.highlight_color) def set_new_values(self): config = ConfigParser() config.read('chess_options.ini') config.set('chess_colors', 'board_color_1', self.board_color_1) config.set('chess_colors', 'board_color_2', self.board_color_2) config.set('chess_colors', 'highlight_color', self.highlight_color) configurations.BOARD_COLOR_1 = self.board_color_1 configurations.BOARD_COLOR_2 = self.board_color_2 configurations.HIGHLIGHT_COLOR = self.highlight_color with open('chess_options.ini', 'w') as config_file: config.write(config_file) def on_cancel_button_clicked(self): self.pref_window.destroy() ================================================ FILE: Chapter 04/4.07/view.py ================================================ """ Code illustration: 4.07 new import import preferenceswindow new attributes added here: board_color_1 = BOARD_COLOR_1 board_color_2 = BOARD_COLOR_2 highlight_color = HIGHLIGHT_COLOR new methods added here: reload_colors(color_1, color_2, highlight_color) methods modified here: replaces all color constants with color instance variables in these two methods: draw_board() alternate_color(current_color) @ Tkinter GUI Application Development Blueprints """ from tkinter import Tk, Menu, Label, Frame, Canvas, RIGHT, PhotoImage, messagebox import controller import exceptions from configurations import * import preferenceswindow class View(): selected_piece_position = None all_squares_to_be_highlighted = [] images = {} board_color_1 = BOARD_COLOR_1 board_color_2 = BOARD_COLOR_2 highlight_color = HIGHLIGHT_COLOR def __init__(self, parent, controller): self.controller = controller self.parent = parent self.create_chess_base() self.canvas.bind("", self.on_square_clicked) self.start_new_game() def reload_colors(self, color_1, color_2, highlight_color): self.board_color_1 = color_1 self.board_color_2 = color_2 self.highlight_color = highlight_color self.draw_board() self.draw_all_pieces() def on_preference_menu_clicked(self): self.show_preferences_window() def show_preferences_window(self): preferenceswindow.PreferencesWindow(self) def create_bottom_frame(self): self.bottom_frame = Frame(self.parent, height=64) self.info_label = Label( self.bottom_frame, text=" White to Start the Game ") self.info_label.pack(side=RIGHT, padx=8, pady=5) self.bottom_frame.pack(fill="x", side="bottom") def create_top_menu(self): self.menu_bar = Menu(self.parent) self.create_file_menu() self.create_edit_menu() self.create_about_menu() def create_file_menu(self): self.file_menu = Menu(self.menu_bar, tearoff=0) self.file_menu.add_command( label="New Game", command=self.on_new_game_menu_clicked) self.menu_bar.add_cascade(label="File", menu=self.file_menu) self.parent.config(menu=self.menu_bar) def create_edit_menu(self): self.edit_menu = Menu(self.menu_bar, tearoff=0) self.edit_menu.add_command( label="Preferences", command=self.on_preference_menu_clicked) self.menu_bar.add_cascade(label="Edit", menu=self.edit_menu) self.parent.config(menu=self.menu_bar) def create_about_menu(self): self.about_menu = Menu(self.menu_bar, tearoff=0) self.about_menu.add_command( label="About", command=self.on_about_menu_clicked) self.menu_bar.add_cascade(label="About", menu=self.about_menu) self.parent.config(menu=self.menu_bar) def create_canvas(self): canvas_width = NUMBER_OF_COLUMNS * DIMENSION_OF_EACH_SQUARE canvas_height = NUMBER_OF_ROWS * DIMENSION_OF_EACH_SQUARE self.canvas = Canvas( self.parent, width=canvas_width, height=canvas_height) self.canvas.pack(padx=8, pady=8) def create_chess_base(self): self.create_top_menu() self.create_canvas() self.draw_board() self.create_bottom_frame() def start_new_game(self): self.controller.reset_game_data() self.controller.reset_to_initial_locations() self.draw_all_pieces() self.info_label.config(text=" White to Start the Game ") def reset_board_state(self): self.selected_piece_position = None self.all_squares_to_be_highlighted = [] self.images = {} def on_new_game_menu_clicked(self): self.start_new_game() def get_clicked_row_column(self, event): col_size = row_size = DIMENSION_OF_EACH_SQUARE clicked_column = event.x // col_size clicked_row = 7 - (event.y // row_size) return (clicked_row, clicked_column) def on_square_clicked(self, event): clicked_row, clicked_column = self.get_clicked_row_column(event) position_of_click = self.controller.get_alphanumeric_position( (clicked_row, clicked_column)) # on second click if self.selected_piece_position: self.shift(self.selected_piece_position, position_of_click) self.selected_piece_position = None self.update_highlight_list(position_of_click) self.draw_board() self.draw_all_pieces() def shift(self, start_pos, end_pos): selected_piece = self.controller.get_piece_at(start_pos) piece_at_destination = self.controller.get_piece_at(end_pos) if not piece_at_destination or piece_at_destination.color != selected_piece.color: try: self.controller.pre_move_validation(start_pos, end_pos) except exceptions.ChessError as error: self.info_label["text"] = error.__class__.__name__ else: self.update_label(selected_piece, start_pos, end_pos) def update_label(self, piece, start_pos, end_pos): turn = ('white' if piece.color == 'black' else 'black') self.info_label["text"] = '' + piece.color.capitalize() + " : " + \ start_pos + end_pos + ' ' + turn.capitalize() + '\'s turn' def update_highlight_list(self, position): self.all_squares_to_be_highlighted = None try: piece = self.controller.get_piece_at(position) except: piece = None if piece and (piece.color == self.controller.player_turn()): self.selected_piece_position = position self.all_squares_to_be_highlighted = list(map( self.controller.get_numeric_notation, self.controller.get_piece_at(position).moves_available(position))) def get_x_y_coordinate(self, row, col): x = (col * DIMENSION_OF_EACH_SQUARE) y = ((7 - row) * DIMENSION_OF_EACH_SQUARE) return (x, y) def draw_board(self): current_color = self.board_color_2 for row in range(NUMBER_OF_ROWS): current_color = self.get_alternate_color(current_color) for col in range(NUMBER_OF_COLUMNS): x1, y1 = self.get_x_y_coordinate(row, col) x2, y2 = x1 + DIMENSION_OF_EACH_SQUARE, y1 + DIMENSION_OF_EACH_SQUARE if(self.all_squares_to_be_highlighted and (row, col) in self.all_squares_to_be_highlighted): self.canvas.create_rectangle( x1, y1, x2, y2, fill=self.highlight_color) else: self.canvas.create_rectangle( x1, y1, x2, y2, fill=current_color) current_color = self.get_alternate_color(current_color) def get_alternate_color(self, current_color): if current_color == self.board_color_2: next_color = self.board_color_1 else: next_color = self.board_color_2 return next_color def calculate_piece_coordinate(self, row, col): x0 = (col * DIMENSION_OF_EACH_SQUARE) + \ int(DIMENSION_OF_EACH_SQUARE / 2) y0 = ((7 - row) * DIMENSION_OF_EACH_SQUARE) + \ int(DIMENSION_OF_EACH_SQUARE / 2) return (x0, y0) def draw_single_piece(self, position, piece): x, y = self.controller.get_numeric_notation(position) if piece: filename = "../pieces_image/{}_{}.png".format( piece.name.lower(), piece.color) if filename not in self.images: self.images[filename] = PhotoImage(file=filename) x0, y0 = self.calculate_piece_coordinate(x, y) self.canvas.create_image(x0, y0, image=self.images[ filename], tags=("occupied"), anchor="c") def draw_all_pieces(self): self.canvas.delete("occupied") for position, piece in self.controller.get_all_peices_on_chess_board(): self.draw_single_piece(position, piece) def on_about_menu_clicked(self): messagebox.showinfo("From the Book:", "Tkinter GUI Application\n Development Blueprints") def main(model): root = Tk() root.title("Chess") View(root, model) root.mainloop() def init_new_game(): initial_game_data = controller.Controller() main(initial_game_data) if __name__ == "__main__": init_new_game() ================================================ FILE: Chapter 04/readme.txt ================================================ ==================================================================== Code Readme Tkinter GUI Application Development Blueprints Chapter 4: Game of Chess ==================================================================== List of code samples: Code 4.01: Defining the file structure and creating the chess Board Code 4.02: Defining data structure Code 4.03: Adding pieces on board Code 4.04: Enforcing rules for pieces Code 4.05: Enforcing chess board logic Code 4.06: Making the game functional Code 4.07: Using ConfigParser to save user preferences Directory pieces_image contains all images used in the program ================================================ FILE: Chapter 05/5.01/model.py ================================================ """ Code illustration: 5.01 @Tkinter GUI Application Development Blueprints """ class Model: def __init__(self): pass ================================================ FILE: Chapter 05/5.01/player.py ================================================ """ Code illustration: 5.01 @Tkinter GUI Application Development Blueprints """ import pyglet class Player: def __init__(self): pass ================================================ FILE: Chapter 05/5.01/view.py ================================================ """ Code illustration: 5.01 @Tkinter GUI Application Development Blueprints """ import tkinter as tk import tkinter.filedialog import tkinter.messagebox import tkinter.ttk import model import player AUDIO_PLAYER_NAME = "Achtung Baby" class View: loop_choices = [("No Loop", 1), ("Loop Current", 2), ("Loop All", 3)] def __init__(self, root, model, player): self.root = root self.model = model self.player = player self.create_gui() def create_gui(self): self.root.title(AUDIO_PLAYER_NAME) self.create_top_display() self.create_button_frame() self.create_list_box() self.create_bottom_frame() self.create_context_menu() def create_top_display(self): frame = tk.Frame(self.root) glass_frame_image = tk.PhotoImage(file='../icons/glass_frame.gif') self.canvas = tk.Canvas(frame, width=370, height=90) self.canvas.image = glass_frame_image self.canvas.grid(row=1) self.console = self.canvas.create_image( 0, 10, anchor=tk.NW, image=glass_frame_image) self.clock = self.canvas.create_text(125, 68, anchor=tk.W, fill='#CBE4F6', text="00:00") self.track_length_text = self.canvas.create_text(167, 68, anchor=tk.W, fill='#CBE4F6', text="of 00:00") self.track_name = self.canvas.create_text(50, 35, anchor=tk.W, fill='#9CEDAC', text='\"Currently playing: none \"') frame.grid(row=1, pady=1, padx=0) def create_button_frame(self): frame = tk.Frame(self.root) previous_track_icon = tk.PhotoImage(file='../icons/previous_track.gif') previous_track_button = tk.Button( frame, image=previous_track_icon, borderwidth=0, padx=0, command=self.on_previous_track_button_clicked) previous_track_button.image = previous_track_icon previous_track_button.grid(row=3, column=1, sticky='w') rewind_icon = tk.PhotoImage(file='../icons/rewind.gif') rewind_button = tk.Button( frame, image=rewind_icon, borderwidth=0, padx=0, command=self.on_rewind_button_clicked) rewind_button.image = rewind_icon rewind_button.grid(row=3, column=2, sticky='w') self.play_icon = tk.PhotoImage(file='../icons/play.gif') self.stop_icon = tk.PhotoImage(file='../icons/stop.gif') self.play_stop_button = tk.Button( frame, image=self.play_icon, borderwidth=0, padx=0, command=self.on_play_stop_button_clicked) self.play_stop_button.image = self.play_icon self.play_stop_button.grid(row=3, column=3) pause_icon = tk.PhotoImage(file='../icons/pause.gif') pause_unpause_button = tk.Button( frame, image=pause_icon, borderwidth=0, padx=0, command=self.on_pause_unpause_button_clicked) pause_unpause_button.image = pause_icon pause_unpause_button.grid(row=3, column=4) fast_forward_icon = tk.PhotoImage(file='../icons/fast_forward.gif') fast_forward_button = tk.Button( frame, image=fast_forward_icon, borderwidth=0, padx=0, command=self.on_fast_forward_button_clicked) fast_forward_button.image = fast_forward_icon fast_forward_button.grid(row=3, column=5) next_track_icon = tk.PhotoImage(file='../icons/next_track.gif') next_track_button = tk.Button( frame, image=next_track_icon, borderwidth=0, padx=0, command=self.on_next_track_button_clicked) next_track_button.image = next_track_icon next_track_button.grid(row=3, column=6) self.mute_icon = tk.PhotoImage(file='../icons/mute.gif') self.unmute_icon = tk.PhotoImage(file='../icons/unmute.gif') self.mute_unmute_button = tk.Button( frame, image=self.unmute_icon, text='unmute', borderwidth=0, padx=0, command=self.on_mute_unmute_button_clicked) self.mute_unmute_button.image = self.unmute_icon self.mute_unmute_button.grid(row=3, column=7) self.volume_scale = tkinter.ttk.Scale( frame, from_=0.0, to=1.0, command=self.on_volume_scale_changed) self.volume_scale.set(0.6) self.volume_scale.grid(row=3, column=8, padx=5) frame.grid(row=3, columnspan=5, sticky='w', pady=4, padx=5) def create_list_box(self): frame = tk.Frame(self.root) self.list_box = tk.Listbox(frame, activestyle='none', cursor='hand2', bg='#1C3D7D', fg='#A0B9E9', selectmode=tk.EXTENDED, height=10) self.list_box.pack(side=tk.LEFT, fill=tk.BOTH, expand=1) self.list_box.bind( "", self.on_play_list_double_clicked) self.list_box.bind("", self.show_context_menu) scroll_bar = tk.Scrollbar(frame) scroll_bar.pack(side=tk.RIGHT, fill=tk.BOTH) self.list_box.config(yscrollcommand=scroll_bar.set) scroll_bar.config(command=self.list_box.yview) frame.grid(row=4, padx=5, columnspan=10, sticky='ew') def create_bottom_frame(self): frame = tk.Frame(self.root) add_file_icon = tk.PhotoImage(file='../icons/add_file.gif') add_file_button = tk.Button(frame, image=add_file_icon, borderwidth=0, padx=0, text='Add File', command=self.on_add_file_button_clicked) add_file_button.image = add_file_icon add_file_button.grid(row=5, column=1) remove_selected_icon = tk.PhotoImage( file='../icons/delete_selected.gif') remove_selected_button = tk.Button( frame, image=remove_selected_icon, borderwidth=0, padx=0, text='Delete', command=self.on_remove_selected_button_clicked) remove_selected_button.image = remove_selected_icon remove_selected_button.grid(row=5, column=2) add_directory_icon = tk.PhotoImage(file='../icons/add_directory.gif') add_directory_button = tk.Button(frame, image=add_directory_icon, borderwidth=0, padx=0, text='Add Dir', command=self.on_add_directory_button_clicked) add_directory_button.image = add_directory_icon add_directory_button.grid(row=5, column=3) empty_play_list_icon = tk.PhotoImage( file='../icons/clear_play_list.gif') empty_play_list_button = tk.Button(frame, image=empty_play_list_icon, borderwidth=0, padx=0, text='Clear All', command=self.on_clear_play_list_button_clicked) empty_play_list_button.image = empty_play_list_icon empty_play_list_button.grid(row=5, column=4) self.loop_value = tk.IntVar() self.loop_value.set(3) for txt, val in self.loop_choices: tk.Radiobutton(frame, text=txt, variable=self.loop_value, value=val).grid( row=5, column=4 + val, pady=3) frame.grid(row=5, sticky='w', padx=5) def create_context_menu(self): self.context_menu = tk.Menu(self.list_box, tearoff=0) self.context_menu.add_command( label="Delete", command=self.on_remove_selected_context_menu_clicked) def show_context_menu(self, event): self.context_menu.tk_popup(event.x_root, event.y_root) def on_previous_track_button_clicked(self): pass def on_rewind_button_clicked(self): pass def on_play_stop_button_clicked(self): pass def on_pause_unpause_button_clicked(self): pass def on_mute_unmute_button_clicked(self): pass def on_fast_forward_button_clicked(self): pass def on_next_track_button_clicked(self): pass def on_volume_scale_changed(self, value): pass def on_add_file_button_clicked(self): pass def on_remove_selected_button_clicked(self): pass def on_add_directory_button_clicked(self): pass def on_clear_play_list_button_clicked(self): pass def on_remove_selected_context_menu_clicked(self): pass def on_play_list_double_clicked(self, event=None): pass if __name__ == '__main__': root = tk.Tk() root.resizable(width=False, height=False) model = model.Model() player = player.Player() app = View(root, model, player) root.mainloop() ================================================ FILE: Chapter 05/5.02/model.py ================================================ """ Code illustration: 5.02 @Tkinter GUI Application Development Blueprints """ class Model: def __init__(self): self.__play_list = [] @property def play_list(self): return self.__play_list def get_file_to_play(self, file_index): return self.__play_list[file_index] def clear_play_list(self): self.__play_list.clear() def add_to_play_list(self, file_name): self.__play_list.append(file_name) def remove_item_from_play_list_at_index(self, index): del self.__play_list[index] ================================================ FILE: Chapter 05/5.02/player.py ================================================ """ Code illustration: 5.02 @Tkinter GUI Application Development Blueprints """ import pyglet FORWARD_REWIND_JUMP_TIME = 20 class Player: def __init__(self): self.player = pyglet.media.Player() self.player.volume = 0.6 def play_media(self, audio_file): self.reset_player() self.player = pyglet.media.Player() self.source = pyglet.media.load(audio_file) self.player.queue(self.source) self.player.play() def reset_player(self): self.player.pause() self.player.delete() def is_playing(self): try: elapsed_time = int(self.player.time) is_playing = elapsed_time < int(self.track_length) except: is_playing = False return is_playing def seek(self, time): try: self.player.seek(time) except AttributeError: pass @property def track_length(self): try: return self.source.duration except AttributeError: return 0 @property def volume(self): return self.player.volume @property def elapsed_play_duration(self): return self.player.time @volume.setter def volume(self, volume): self.player.volume = volume def unpause(self): self.player.play() def pause(self): self.player.pause() def stop(self): self.reset_player() def mute(self): self.player.volume = 0.0 def unmute(self, newvolume_level): self.player.volume = newvolume_level def fast_forward(self): time = self.player.time + FORWARD_REWIND_JUMP_TIME try: if self.source.duration > time: self.seek(time) else: self.seek(self.source.duration) except AttributeError: pass def rewind(self): time = self.player.time - FORWARD_REWIND_JUMP_TIME try: self.seek(time) except: self.seek(0) ================================================ FILE: Chapter 05/5.02/view.py ================================================ """ Code illustration: 5.02 @Tkinter GUI Application Development Blueprints """ import tkinter as tk import tkinter.filedialog import tkinter.messagebox import tkinter.ttk import model import player AUDIO_PLAYER_NAME = "Achtung Baby" class View: loop_choices = [("No Loop", 1), ("Loop Current", 2), ("Loop All", 3)] def __init__(self, root, model, player): self.root = root self.model = model self.player = player self.create_gui() def create_gui(self): self.root.title(AUDIO_PLAYER_NAME) self.create_top_display() self.create_button_frame() self.create_list_box() self.create_bottom_frame() self.create_context_menu() def create_top_display(self): frame = tk.Frame(self.root) glass_frame_image = tk.PhotoImage(file='../icons/glass_frame.gif') self.canvas = tk.Canvas(frame, width=370, height=90) self.canvas.image = glass_frame_image self.canvas.grid(row=1) self.console = self.canvas.create_image( 0, 10, anchor=tk.NW, image=glass_frame_image) self.clock = self.canvas.create_text(125, 68, anchor=tk.W, fill='#CBE4F6', text="00:00") self.track_length_text = self.canvas.create_text(167, 68, anchor=tk.W, fill='#CBE4F6', text="of 00:00") self.track_name = self.canvas.create_text(50, 35, anchor=tk.W, fill='#9CEDAC', text='\"Currently playing: none \"') frame.grid(row=1, pady=1, padx=0) def create_button_frame(self): frame = tk.Frame(self.root) previous_track_icon = tk.PhotoImage(file='../icons/previous_track.gif') previous_track_button = tk.Button( frame, image=previous_track_icon, borderwidth=0, padx=0, command=self.on_previous_track_button_clicked) previous_track_button.image = previous_track_icon previous_track_button.grid(row=3, column=1, sticky='w') rewind_icon = tk.PhotoImage(file='../icons/rewind.gif') rewind_button = tk.Button( frame, image=rewind_icon, borderwidth=0, padx=0, command=self.on_rewind_button_clicked) rewind_button.image = rewind_icon rewind_button.grid(row=3, column=2, sticky='w') self.play_icon = tk.PhotoImage(file='../icons/play.gif') self.stop_icon = tk.PhotoImage(file='../icons/stop.gif') self.play_stop_button = tk.Button( frame, image=self.play_icon, borderwidth=0, padx=0, command=self.on_play_stop_button_clicked) self.play_stop_button.image = self.play_icon self.play_stop_button.grid(row=3, column=3) pause_icon = tk.PhotoImage(file='../icons/pause.gif') pause_unpause_button = tk.Button( frame, image=pause_icon, borderwidth=0, padx=0, command=self.on_pause_unpause_button_clicked) pause_unpause_button.image = pause_icon pause_unpause_button.grid(row=3, column=4) fast_forward_icon = tk.PhotoImage(file='../icons/fast_forward.gif') fast_forward_button = tk.Button( frame, image=fast_forward_icon, borderwidth=0, padx=0, command=self.on_fast_forward_button_clicked) fast_forward_button.image = fast_forward_icon fast_forward_button.grid(row=3, column=5) next_track_icon = tk.PhotoImage(file='../icons/next_track.gif') next_track_button = tk.Button( frame, image=next_track_icon, borderwidth=0, padx=0, command=self.on_next_track_button_clicked) next_track_button.image = next_track_icon next_track_button.grid(row=3, column=6) self.mute_icon = tk.PhotoImage(file='../icons/mute.gif') self.unmute_icon = tk.PhotoImage(file='../icons/unmute.gif') self.mute_unmute_button = tk.Button( frame, image=self.unmute_icon, text='unmute', borderwidth=0, padx=0, command=self.on_mute_unmute_button_clicked) self.mute_unmute_button.image = self.unmute_icon self.mute_unmute_button.grid(row=3, column=7) self.volume_scale = tkinter.ttk.Scale( frame, from_=0.0, to=1.0, command=self.on_volume_scale_changed) self.volume_scale.set(0.6) self.volume_scale.grid(row=3, column=8, padx=5) frame.grid(row=3, columnspan=5, sticky='w', pady=4, padx=5) def create_list_box(self): frame = tk.Frame(self.root) self.list_box = tk.Listbox(frame, activestyle='none', cursor='hand2', bg='#1C3D7D', fg='#A0B9E9', selectmode=tk.EXTENDED, height=10) self.list_box.pack(side=tk.LEFT, fill=tk.BOTH, expand=1) self.list_box.bind( "", self.on_play_list_double_clicked) self.list_box.bind("", self.show_context_menu) scroll_bar = tk.Scrollbar(frame) scroll_bar.pack(side=tk.RIGHT, fill=tk.BOTH) self.list_box.config(yscrollcommand=scroll_bar.set) scroll_bar.config(command=self.list_box.yview) frame.grid(row=4, padx=5, columnspan=10, sticky='ew') def create_bottom_frame(self): frame = tk.Frame(self.root) add_file_icon = tk.PhotoImage(file='../icons/add_file.gif') add_file_button = tk.Button(frame, image=add_file_icon, borderwidth=0, padx=0, text='Add File', command=self.on_add_file_button_clicked) add_file_button.image = add_file_icon add_file_button.grid(row=5, column=1) remove_selected_icon = tk.PhotoImage( file='../icons/delete_selected.gif') remove_selected_button = tk.Button( frame, image=remove_selected_icon, borderwidth=0, padx=0, text='Delete', command=self.on_remove_selected_button_clicked) remove_selected_button.image = remove_selected_icon remove_selected_button.grid(row=5, column=2) add_directory_icon = tk.PhotoImage(file='../icons/add_directory.gif') add_directory_button = tk.Button(frame, image=add_directory_icon, borderwidth=0, padx=0, text='Add Dir', command=self.on_add_directory_button_clicked) add_directory_button.image = add_directory_icon add_directory_button.grid(row=5, column=3) empty_play_list_icon = tk.PhotoImage( file='../icons/clear_play_list.gif') empty_play_list_button = tk.Button(frame, image=empty_play_list_icon, borderwidth=0, padx=0, text='Clear All', command=self.on_clear_play_list_button_clicked) empty_play_list_button.image = empty_play_list_icon empty_play_list_button.grid(row=5, column=4) self.loop_value = tk.IntVar() self.loop_value.set(3) for txt, val in self.loop_choices: tk.Radiobutton(frame, text=txt, variable=self.loop_value, value=val).grid( row=5, column=4 + val, pady=3) frame.grid(row=5, sticky='w', padx=5) def create_context_menu(self): self.context_menu = tk.Menu(self.list_box, tearoff=0) self.context_menu.add_command( label="Delete", command=self.on_remove_selected_context_menu_clicked) def show_context_menu(self, event): self.context_menu.tk_popup(event.x_root, event.y_root) def on_previous_track_button_clicked(self): pass def on_rewind_button_clicked(self): pass def on_play_stop_button_clicked(self): pass def on_pause_unpause_button_clicked(self): pass def on_mute_unmute_button_clicked(self): pass def on_fast_forward_button_clicked(self): pass def on_next_track_button_clicked(self): pass def on_volume_scale_changed(self, value): pass def on_add_file_button_clicked(self): pass def on_remove_selected_button_clicked(self): pass def on_add_directory_button_clicked(self): pass def on_clear_play_list_button_clicked(self): pass def on_remove_selected_context_menu_clicked(self): pass def on_play_list_double_clicked(self, event=None): pass if __name__ == '__main__': root = tk.Tk() root.resizable(width=False, height=False) model = model.Model() player = player.Player() app = View(root, model, player) root.mainloop() ================================================ FILE: Chapter 05/5.03/model.py ================================================ """ Code illustration: 5.03 @Tkinter GUI Application Development Blueprints """ class Model: def __init__(self): self.__play_list = [] @property def play_list(self): return self.__play_list def get_file_to_play(self, file_index): return self.__play_list[file_index] def clear_play_list(self): self.__play_list.clear() def add_to_play_list(self, file_name): self.__play_list.append(file_name) def remove_item_from_play_list_at_index(self, index): del self.__play_list[index] ================================================ FILE: Chapter 05/5.03/player.py ================================================ """ Code illustration: 5.03 @Tkinter GUI Application Development Blueprints """ import pyglet FORWARD_REWIND_JUMP_TIME = 20 class Player: def __init__(self): self.player = pyglet.media.Player() self.player.volume = 0.6 def play_media(self, audio_file): self.reset_player() self.player = pyglet.media.Player() self.source = pyglet.media.load(audio_file) self.player.queue(self.source) self.player.play() def reset_player(self): self.player.pause() self.player.delete() def is_playing(self): try: elapsed_time = int(self.player.time) is_playing = elapsed_time < int(self.track_length) except: is_playing = False return is_playing def seek(self, time): try: self.player.seek(time) except AttributeError: pass @property def track_length(self): try: return self.source.duration except AttributeError: return 0 @property def volume(self): return self.player.volume @property def elapsed_play_duration(self): return self.player.time @volume.setter def volume(self, volume): self.player.volume = volume def unpause(self): self.player.play() def pause(self): self.player.pause() def stop(self): self.reset_player() def mute(self): self.player.volume = 0.0 def unmute(self, newvolume_level): self.player.volume = newvolume_level def fast_forward(self): time = self.player.time + FORWARD_REWIND_JUMP_TIME try: if self.source.duration > time: self.seek(time) else: self.seek(self.source.duration) except AttributeError: pass def rewind(self): time = self.player.time - FORWARD_REWIND_JUMP_TIME try: self.seek(time) except: self.seek(0) ================================================ FILE: Chapter 05/5.03/view.py ================================================ """ Code illustration: 5.03 New imports here: import os Methods modified here: on_add_file_button_clicked() on_remove_selected_button_clicked() on_add_directory_button_clicked() on_clear_play_list_button_clicked() New methods added here: add_audio_file() remove_selected_files() add_all_audio_files_from_directory() get_all_audio_file_from_directory(directory_path) clear_play_list() @Tkinter GUI Application Development Blueprints """ import tkinter as tk import tkinter.filedialog import tkinter.messagebox import tkinter.ttk import os import model import player AUDIO_PLAYER_NAME = "Achtung Baby" class View: loop_choices = [("No Loop", 1), ("Loop Current", 2), ("Loop All", 3)] def __init__(self, root, model, player): self.root = root self.model = model self.player = player self.create_gui() def create_gui(self): self.root.title(AUDIO_PLAYER_NAME) self.create_top_display() self.create_button_frame() self.create_list_box() self.create_bottom_frame() self.create_context_menu() def create_top_display(self): frame = tk.Frame(self.root) glass_frame_image = tk.PhotoImage(file='../icons/glass_frame.gif') self.canvas = tk.Canvas(frame, width=370, height=90) self.canvas.image = glass_frame_image self.canvas.grid(row=1) self.console = self.canvas.create_image( 0, 10, anchor=tk.NW, image=glass_frame_image) self.clock = self.canvas.create_text(125, 68, anchor=tk.W, fill='#CBE4F6', text="00:00") self.track_length_text = self.canvas.create_text(167, 68, anchor=tk.W, fill='#CBE4F6', text="of 00:00") self.track_name = self.canvas.create_text(50, 35, anchor=tk.W, fill='#9CEDAC', text='\"Currently playing: none \"') frame.grid(row=1, pady=1, padx=0) def create_button_frame(self): frame = tk.Frame(self.root) previous_track_icon = tk.PhotoImage(file='../icons/previous_track.gif') previous_track_button = tk.Button( frame, image=previous_track_icon, borderwidth=0, padx=0, command=self.on_previous_track_button_clicked) previous_track_button.image = previous_track_icon previous_track_button.grid(row=3, column=1, sticky='w') rewind_icon = tk.PhotoImage(file='../icons/rewind.gif') rewind_button = tk.Button( frame, image=rewind_icon, borderwidth=0, padx=0, command=self.on_rewind_button_clicked) rewind_button.image = rewind_icon rewind_button.grid(row=3, column=2, sticky='w') self.play_icon = tk.PhotoImage(file='../icons/play.gif') self.stop_icon = tk.PhotoImage(file='../icons/stop.gif') self.play_stop_button = tk.Button( frame, image=self.play_icon, borderwidth=0, padx=0, command=self.on_play_stop_button_clicked) self.play_stop_button.image = self.play_icon self.play_stop_button.grid(row=3, column=3) pause_icon = tk.PhotoImage(file='../icons/pause.gif') pause_unpause_button = tk.Button( frame, image=pause_icon, borderwidth=0, padx=0, command=self.on_pause_unpause_button_clicked) pause_unpause_button.image = pause_icon pause_unpause_button.grid(row=3, column=4) fast_forward_icon = tk.PhotoImage(file='../icons/fast_forward.gif') fast_forward_button = tk.Button( frame, image=fast_forward_icon, borderwidth=0, padx=0, command=self.on_fast_forward_button_clicked) fast_forward_button.image = fast_forward_icon fast_forward_button.grid(row=3, column=5) next_track_icon = tk.PhotoImage(file='../icons/next_track.gif') next_track_button = tk.Button( frame, image=next_track_icon, borderwidth=0, padx=0, command=self.on_next_track_button_clicked) next_track_button.image = next_track_icon next_track_button.grid(row=3, column=6) self.mute_icon = tk.PhotoImage(file='../icons/mute.gif') self.unmute_icon = tk.PhotoImage(file='../icons/unmute.gif') self.mute_unmute_button = tk.Button( frame, image=self.unmute_icon, text='unmute', borderwidth=0, padx=0, command=self.on_mute_unmute_button_clicked) self.mute_unmute_button.image = self.unmute_icon self.mute_unmute_button.grid(row=3, column=7) self.volume_scale = tkinter.ttk.Scale( frame, from_=0.0, to=1.0, command=self.on_volume_scale_changed) self.volume_scale.set(0.6) self.volume_scale.grid(row=3, column=8, padx=5) frame.grid(row=3, columnspan=5, sticky='w', pady=4, padx=5) def create_list_box(self): frame = tk.Frame(self.root) self.list_box = tk.Listbox(frame, activestyle='none', cursor='hand2', bg='#1C3D7D', fg='#A0B9E9', selectmode=tk.EXTENDED, height=10) self.list_box.pack(side=tk.LEFT, fill=tk.BOTH, expand=1) self.list_box.bind( "", self.on_play_list_double_clicked) self.list_box.bind("", self.show_context_menu) scroll_bar = tk.Scrollbar(frame) scroll_bar.pack(side=tk.RIGHT, fill=tk.BOTH) self.list_box.config(yscrollcommand=scroll_bar.set) scroll_bar.config(command=self.list_box.yview) frame.grid(row=4, padx=5, columnspan=10, sticky='ew') def create_bottom_frame(self): frame = tk.Frame(self.root) add_file_icon = tk.PhotoImage(file='../icons/add_file.gif') add_file_button = tk.Button(frame, image=add_file_icon, borderwidth=0, padx=0, text='Add File', command=self.on_add_file_button_clicked) add_file_button.image = add_file_icon add_file_button.grid(row=5, column=1) remove_selected_icon = tk.PhotoImage( file='../icons/delete_selected.gif') remove_selected_button = tk.Button( frame, image=remove_selected_icon, borderwidth=0, padx=0, text='Delete', command=self.on_remove_selected_button_clicked) remove_selected_button.image = remove_selected_icon remove_selected_button.grid(row=5, column=2) add_directory_icon = tk.PhotoImage(file='../icons/add_directory.gif') add_directory_button = tk.Button(frame, image=add_directory_icon, borderwidth=0, padx=0, text='Add Dir', command=self.on_add_directory_button_clicked) add_directory_button.image = add_directory_icon add_directory_button.grid(row=5, column=3) empty_play_list_icon = tk.PhotoImage( file='../icons/clear_play_list.gif') empty_play_list_button = tk.Button(frame, image=empty_play_list_icon, borderwidth=0, padx=0, text='Clear All', command=self.on_clear_play_list_button_clicked) empty_play_list_button.image = empty_play_list_icon empty_play_list_button.grid(row=5, column=4) self.loop_value = tk.IntVar() self.loop_value.set(3) for txt, val in self.loop_choices: tk.Radiobutton(frame, text=txt, variable=self.loop_value, value=val).grid( row=5, column=4 + val, pady=3) frame.grid(row=5, sticky='w', padx=5) def create_context_menu(self): self.context_menu = tk.Menu(self.list_box, tearoff=0) self.context_menu.add_command( label="Delete", command=self.on_remove_selected_context_menu_clicked) def show_context_menu(self, event): self.context_menu.tk_popup(event.x_root, event.y_root) def on_add_file_button_clicked(self): self.add_audio_file() def on_remove_selected_button_clicked(self): self.remove_selected_files() def on_add_directory_button_clicked(self): self.add_all_audio_files_from_directory() def on_clear_play_list_button_clicked(self): self.clear_play_list() def on_remove_selected_context_menu_clicked(self): self.remove_selected_files() def on_previous_track_button_clicked(self): pass def on_rewind_button_clicked(self): pass def on_play_stop_button_clicked(self): pass def on_pause_unpause_button_clicked(self): pass def on_mute_unmute_button_clicked(self): pass def on_fast_forward_button_clicked(self): pass def on_next_track_button_clicked(self): pass def on_volume_scale_changed(self, value): pass def on_play_list_double_clicked(self, event=None): pass def add_audio_file(self): audio_file = tkinter.filedialog.askopenfilename(filetypes=[( 'All supported', '.mp3 .wav'), ('.mp3 files', '.mp3'), ('.wav files', '.wav')]) if audio_file: self.model.add_to_play_list(audio_file) file_path, file_name = os.path.split(audio_file) self.list_box.insert(tk.END, file_name) def remove_selected_files(self): try: selected_indexes = self.list_box.curselection() for index in reversed(selected_indexes): self.list_box.delete(index) self.model.remove_item_from_play_list_at_index(index) except IndexError: pass def add_all_audio_files_from_directory(self): directory_path = tkinter.filedialog.askdirectory() if not directory_path: return audio_files_in_directory = self.get_all_audio_file_from_directory( directory_path) for audio_file in audio_files_in_directory: self.model.add_to_play_list(audio_file) file_path, file_name = os.path.split(audio_file) self.list_box.insert(tk.END, file_name) def get_all_audio_file_from_directory(self, directory_path): audio_files_in_directory = [] for (dirpath, dirnames, filenames) in os.walk(directory_path): for audio_file in filenames: if audio_file.endswith(".mp3") or audio_file.endswith(".wav"): audio_files_in_directory.append(dirpath + "/" + audio_file) return audio_files_in_directory def clear_play_list(self): self.model.clear_play_list() self.list_box.delete(0, tk.END) if __name__ == '__main__': root = tk.Tk() root.resizable(width=False, height=False) model = model.Model() player = player.Player() app = View(root, model, player) root.mainloop() ================================================ FILE: Chapter 05/5.04/model.py ================================================ """ Code illustration: 5.04 @Tkinter GUI Application Development Blueprints """ class Model: def __init__(self): self.__play_list = [] @property def play_list(self): return self.__play_list def get_file_to_play(self, file_index): return self.__play_list[file_index] def clear_play_list(self): self.__play_list.clear() def add_to_play_list(self, file_name): self.__play_list.append(file_name) def remove_item_from_play_list_at_index(self, index): del self.__play_list[index] ================================================ FILE: Chapter 05/5.04/player.py ================================================ """ Code illustration: 5.04 @Tkinter GUI Application Development Blueprints """ import pyglet FORWARD_REWIND_JUMP_TIME = 20 class Player: def __init__(self): self.player = pyglet.media.Player() self.player.volume = 0.6 def play_media(self, audio_file): self.reset_player() self.player = pyglet.media.Player() self.source = pyglet.media.load(audio_file) self.player.queue(self.source) self.player.play() def reset_player(self): self.player.pause() self.player.delete() def is_playing(self): try: elapsed_time = int(self.player.time) is_playing = elapsed_time < int(self.track_length) except: is_playing = False return is_playing def seek(self, time): try: self.player.seek(time) except AttributeError: pass @property def track_length(self): try: return self.source.duration except AttributeError: return 0 @property def volume(self): return self.player.volume @property def elapsed_play_duration(self): return self.player.time @volume.setter def volume(self, volume): self.player.volume = volume def unpause(self): self.player.play() def pause(self): self.player.pause() def stop(self): self.reset_player() def mute(self): self.player.volume = 0.0 def unmute(self, newvolume_level): self.player.volume = newvolume_level def fast_forward(self): time = self.player.time + FORWARD_REWIND_JUMP_TIME try: if self.source.duration > time: self.seek(time) else: self.seek(self.source.duration) except AttributeError: pass def rewind(self): time = self.player.time - FORWARD_REWIND_JUMP_TIME try: self.seek(time) except: self.seek(0) ================================================ FILE: Chapter 05/5.04/view.py ================================================ """ Code illustration: 5.04 New modules imported here: import itertools New attributes added here: current_track_index toggle_play_stop toggle_pause_unpause toggle_mute_unmute Methods modified here: on_play_stop_button_clicked() on_pause_unpause_button_clicked() on_mute_unmute_button_clicked() on_previous_track_button_clicked() on_rewind_button_clicked() on_fast_forward_button_clicked() on_next_track_button_clicked() on_volume_scale_changed(value) on_play_list_double_clicked() Methods added here: start_play() stop_play() play_previous_track() play_next_track() @Tkinter GUI Application Development Blueprints """ import tkinter as tk import tkinter.filedialog import tkinter.messagebox import tkinter.ttk import os import model import player import itertools AUDIO_PLAYER_NAME = "Achtung Baby" class View: loop_choices = [("No Loop", 1), ("Loop Current", 2), ("Loop All", 3)] current_track_index = 0 toggle_play_stop = itertools.cycle(["play", "stop"]) toggle_pause_unpause = itertools.cycle(["pause", "unpause"]) toggle_mute_unmute = itertools.cycle(["mute", "unmute"]) def __init__(self, root, model, player): self.root = root self.model = model self.player = player self.create_gui() def create_gui(self): self.root.title(AUDIO_PLAYER_NAME) self.create_top_display() self.create_button_frame() self.create_list_box() self.create_bottom_frame() self.create_context_menu() def create_top_display(self): frame = tk.Frame(self.root) glass_frame_image = tk.PhotoImage(file='../icons/glass_frame.gif') self.canvas = tk.Canvas(frame, width=370, height=90) self.canvas.image = glass_frame_image self.canvas.grid(row=1) self.console = self.canvas.create_image( 0, 10, anchor=tk.NW, image=glass_frame_image) self.clock = self.canvas.create_text(125, 68, anchor=tk.W, fill='#CBE4F6', text="00:00") self.track_length_text = self.canvas.create_text(167, 68, anchor=tk.W, fill='#CBE4F6', text="of 00:00") self.track_name = self.canvas.create_text(50, 35, anchor=tk.W, fill='#9CEDAC', text='\"Currently playing: none \"') frame.grid(row=1, pady=1, padx=0) def create_button_frame(self): frame = tk.Frame(self.root) previous_track_icon = tk.PhotoImage(file='../icons/previous_track.gif') previous_track_button = tk.Button( frame, image=previous_track_icon, borderwidth=0, padx=0, command=self.on_previous_track_button_clicked) previous_track_button.image = previous_track_icon previous_track_button.grid(row=3, column=1, sticky='w') rewind_icon = tk.PhotoImage(file='../icons/rewind.gif') rewind_button = tk.Button( frame, image=rewind_icon, borderwidth=0, padx=0, command=self.on_rewind_button_clicked) rewind_button.image = rewind_icon rewind_button.grid(row=3, column=2, sticky='w') self.play_icon = tk.PhotoImage(file='../icons/play.gif') self.stop_icon = tk.PhotoImage(file='../icons/stop.gif') self.play_stop_button = tk.Button( frame, image=self.play_icon, borderwidth=0, padx=0, command=self.on_play_stop_button_clicked) self.play_stop_button.image = self.play_icon self.play_stop_button.grid(row=3, column=3) pause_icon = tk.PhotoImage(file='../icons/pause.gif') pause_unpause_button = tk.Button( frame, image=pause_icon, borderwidth=0, padx=0, command=self.on_pause_unpause_button_clicked) pause_unpause_button.image = pause_icon pause_unpause_button.grid(row=3, column=4) fast_forward_icon = tk.PhotoImage(file='../icons/fast_forward.gif') fast_forward_button = tk.Button( frame, image=fast_forward_icon, borderwidth=0, padx=0, command=self.on_fast_forward_button_clicked) fast_forward_button.image = fast_forward_icon fast_forward_button.grid(row=3, column=5) next_track_icon = tk.PhotoImage(file='../icons/next_track.gif') next_track_button = tk.Button( frame, image=next_track_icon, borderwidth=0, padx=0, command=self.on_next_track_button_clicked) next_track_button.image = next_track_icon next_track_button.grid(row=3, column=6) self.mute_icon = tk.PhotoImage(file='../icons/mute.gif') self.unmute_icon = tk.PhotoImage(file='../icons/unmute.gif') self.mute_unmute_button = tk.Button( frame, image=self.unmute_icon, text='unmute', borderwidth=0, padx=0, command=self.on_mute_unmute_button_clicked) self.mute_unmute_button.image = self.unmute_icon self.mute_unmute_button.grid(row=3, column=7) self.volume_scale = tkinter.ttk.Scale( frame, from_=0.0, to=1.0, command=self.on_volume_scale_changed) self.volume_scale.set(0.6) self.volume_scale.grid(row=3, column=8, padx=5) frame.grid(row=3, columnspan=5, sticky='w', pady=4, padx=5) def create_list_box(self): frame = tk.Frame(self.root) self.list_box = tk.Listbox(frame, activestyle='none', cursor='hand2', bg='#1C3D7D', fg='#A0B9E9', selectmode=tk.EXTENDED, height=10) self.list_box.pack(side=tk.LEFT, fill=tk.BOTH, expand=1) self.list_box.bind( "", self.on_play_list_double_clicked) self.list_box.bind("", self.show_context_menu) scroll_bar = tk.Scrollbar(frame) scroll_bar.pack(side=tk.RIGHT, fill=tk.BOTH) self.list_box.config(yscrollcommand=scroll_bar.set) scroll_bar.config(command=self.list_box.yview) frame.grid(row=4, padx=5, columnspan=10, sticky='ew') def create_bottom_frame(self): frame = tk.Frame(self.root) add_file_icon = tk.PhotoImage(file='../icons/add_file.gif') add_file_button = tk.Button(frame, image=add_file_icon, borderwidth=0, padx=0, text='Add File', command=self.on_add_file_button_clicked) add_file_button.image = add_file_icon add_file_button.grid(row=5, column=1) remove_selected_icon = tk.PhotoImage( file='../icons/delete_selected.gif') remove_selected_button = tk.Button( frame, image=remove_selected_icon, borderwidth=0, padx=0, text='Delete', command=self.on_remove_selected_button_clicked) remove_selected_button.image = remove_selected_icon remove_selected_button.grid(row=5, column=2) add_directory_icon = tk.PhotoImage(file='../icons/add_directory.gif') add_directory_button = tk.Button(frame, image=add_directory_icon, borderwidth=0, padx=0, text='Add Dir', command=self.on_add_directory_button_clicked) add_directory_button.image = add_directory_icon add_directory_button.grid(row=5, column=3) empty_play_list_icon = tk.PhotoImage( file='../icons/clear_play_list.gif') empty_play_list_button = tk.Button(frame, image=empty_play_list_icon, borderwidth=0, padx=0, text='Clear All', command=self.on_clear_play_list_button_clicked) empty_play_list_button.image = empty_play_list_icon empty_play_list_button.grid(row=5, column=4) self.loop_value = tk.IntVar() self.loop_value.set(3) for txt, val in self.loop_choices: tk.Radiobutton(frame, text=txt, variable=self.loop_value, value=val).grid( row=5, column=4 + val, pady=3) frame.grid(row=5, sticky='w', padx=5) def create_context_menu(self): self.context_menu = tk.Menu(self.list_box, tearoff=0) self.context_menu.add_command( label="Delete", command=self.on_remove_selected_context_menu_clicked) def show_context_menu(self, event): self.context_menu.tk_popup(event.x_root, event.y_root) def on_add_file_button_clicked(self): self.add_audio_file() def on_remove_selected_button_clicked(self): self.remove_selected_files() def on_add_directory_button_clicked(self): self.add_all_audio_files_from_directory() def on_clear_play_list_button_clicked(self): self.clear_play_list() def on_remove_selected_context_menu_clicked(self): self.remove_selected_files() def on_play_stop_button_clicked(self): action = next(self.toggle_play_stop) if action == 'play': try: self.current_track_index = self.list_box.curselection()[0] except IndexError: self.current_track_index = 0 self.start_play() elif action == 'stop': self.stop_play() def on_pause_unpause_button_clicked(self): action = next(self.toggle_pause_unpause) if action == 'pause': self.player.pause() elif action == 'unpause': self.player.unpause() def on_mute_unmute_button_clicked(self): action = next(self.toggle_mute_unmute) if action == 'mute': self.volume_at_time_of_mute = self.player.volume self.player.mute() self.volume_scale.set(0) self.mute_unmute_button.config(image=self.mute_icon) elif action == 'unmute': self.player.unmute(self.volume_at_time_of_mute) self.volume_scale.set(self.volume_at_time_of_mute) self.mute_unmute_button.config(image=self.unmute_icon) def on_previous_track_button_clicked(self): self.play_previous_track() def on_rewind_button_clicked(self): self.player.rewind() def on_fast_forward_button_clicked(self): self.player.fast_forward() def on_next_track_button_clicked(self): self.play_next_track() def on_volume_scale_changed(self, value): self.player.volume = self.volume_scale.get() if self.volume_scale.get() == 0.0: self.mute_unmute_button.config(image=self.mute_icon) else: self.mute_unmute_button.config(image=self.unmute_icon) def on_play_list_double_clicked(self, event=None): self.current_track_index = int(self.list_box.curselection()[0]) self.start_play() def play_previous_track(self): self.current_track_index = max(0, self.current_track_index - 1) self.start_play() def play_next_track(self): self.current_track_index = min( self.list_box.size() - 1, self.current_track_index + 1) self.start_play() def start_play(self): try: audio_file = self.model.get_file_to_play(self.current_track_index) except IndexError: return self.play_stop_button.config(image=self.stop_icon) self.player.play_media(audio_file) def stop_play(self): self.play_stop_button.config(image=self.play_icon) self.player.stop() def add_audio_file(self): audio_file = tkinter.filedialog.askopenfilename(filetypes=[( 'All supported', '.mp3 .wav'), ('.mp3 files', '.mp3'), ('.wav files', '.wav')]) if audio_file: self.model.add_to_play_list(audio_file) file_path, file_name = os.path.split(audio_file) self.list_box.insert(tk.END, file_name) def remove_selected_files(self): try: selected_indexes = self.list_box.curselection() for index in reversed(selected_indexes): self.list_box.delete(index) self.model.remove_item_from_play_list_at_index(index) except IndexError: pass def add_all_audio_files_from_directory(self): directory_path = tkinter.filedialog.askdirectory() if not directory_path: return audio_files_in_directory = self.get_all_audio_file_from_directory( directory_path) for audio_file in audio_files_in_directory: self.model.add_to_play_list(audio_file) file_path, file_name = os.path.split(audio_file) self.list_box.insert(tk.END, file_name) def get_all_audio_file_from_directory(self, directory_path): audio_files_in_directory = [] for (dirpath, dirnames, filenames) in os.walk(directory_path): for audio_file in filenames: if audio_file.endswith(".mp3") or audio_file.endswith(".wav"): audio_files_in_directory.append(dirpath + "/" + audio_file) return audio_files_in_directory def clear_play_list(self): self.model.clear_play_list() self.list_box.delete(0, tk.END) if __name__ == '__main__': root = tk.Tk() root.resizable(width=False, height=False) model = model.Model() player = player.Player() app = View(root, model, player) root.mainloop() ================================================ FILE: Chapter 05/5.05/model.py ================================================ """ Code illustration: 5.05 @Tkinter GUI Application Development Blueprints """ class Model: def __init__(self): self.__play_list = [] @property def play_list(self): return self.__play_list def get_file_to_play(self, file_index): return self.__play_list[file_index] def clear_play_list(self): self.__play_list.clear() def add_to_play_list(self, file_name): self.__play_list.append(file_name) def remove_item_from_play_list_at_index(self, index): del self.__play_list[index] ================================================ FILE: Chapter 05/5.05/player.py ================================================ """ Code illustration: 5.05 @Tkinter GUI Application Development Blueprints """ import pyglet FORWARD_REWIND_JUMP_TIME = 20 class Player: def __init__(self): self.player = pyglet.media.Player() self.player.volume = 0.6 def play_media(self, audio_file): self.reset_player() self.player = pyglet.media.Player() self.source = pyglet.media.load(audio_file) self.player.queue(self.source) self.player.play() def reset_player(self): self.player.pause() self.player.delete() def is_playing(self): try: elapsed_time = int(self.player.time) is_playing = elapsed_time < int(self.track_length) except: is_playing = False return is_playing def seek(self, time): try: self.player.seek(time) except AttributeError: pass @property def track_length(self): try: return self.source.duration except AttributeError: return 0 @property def volume(self): return self.player.volume @property def elapsed_play_duration(self): return self.player.time @volume.setter def volume(self, volume): self.player.volume = volume def unpause(self): self.player.play() def pause(self): self.player.pause() def stop(self): self.reset_player() def mute(self): self.player.volume = 0.0 def unmute(self, newvolume_level): self.player.volume = newvolume_level def fast_forward(self): time = self.player.time + FORWARD_REWIND_JUMP_TIME try: if self.source.duration > time: self.seek(time) else: self.seek(self.source.duration) except AttributeError: pass def rewind(self): time = self.player.time - FORWARD_REWIND_JUMP_TIME try: self.seek(time) except: self.seek(0) ================================================ FILE: Chapter 05/5.05/seekbar.py ================================================ """ Code illustration: 5.05 @Tkinter GUI Application Development Blueprints """ import tkinter as tk class Seekbar(tk.Canvas): def __init__(self, parent, **options): tk.Canvas.__init__(self, parent, options) self.parent = parent self.width = options['width'] self.red_rectangle = self.create_rectangle(0, 0, 0, 0, fill="red") self.seekbar_knob_image = tk.PhotoImage(file="../icons/seekbar_knob.gif") self.seekbar_knob = self.create_image( 0, 0, image=self.seekbar_knob_image) self.bind_mouse_button() def bind_mouse_button(self): self.bind('', self.on_seekbar_clicked) self.bind('', self.on_seekbar_clicked) self.tag_bind( self.red_rectangle, '', self.on_seekbar_clicked) self.tag_bind( self.seekbar_knob, '', self.on_seekbar_clicked) def on_seekbar_clicked(self, event=None): if event.x > 0 and event.x < self.width: self.slide_to_position(event.x) def slide_to_position(self, new_position): self.coords(self.red_rectangle, 0, 0, new_position, new_position) self.coords(self.seekbar_knob, new_position, 0) self.event_generate("<>", x=new_position) class TestSeekBar(): def __init__(self): root = tk.Tk() root.bind("<>", self.seek_new_position) frame = tk.Frame(root) frame.grid(row=1, pady=10, padx=10) c = Seekbar( frame, background="blue", width=360, height=10) c.grid(row=2, columnspan=10, sticky='ew', padx=5) root.mainloop() def seek_new_position(self, event): print("Dragged to x:", event.x) if __name__ == '__main__': TestSeekBar() ================================================ FILE: Chapter 05/5.05/view.py ================================================ """ Code illustration: 5.05 New modules imported here: from seekbar import * New constants added here: SEEKBAR_WIDTH = 360 Methods modified here: create_top_display() - added a call to create the newly defined Seekbar widget @Tkinter GUI Application Development Blueprints """ import tkinter as tk import tkinter.filedialog import tkinter.messagebox import tkinter.ttk import os import model import player from seekbar import * import itertools AUDIO_PLAYER_NAME = "Achtung Baby" SEEKBAR_WIDTH = 360 class View: loop_choices = [("No Loop", 1), ("Loop Current", 2), ("Loop All", 3)] current_track_index = 0 toggle_play_stop = itertools.cycle(["play", "stop"]) toggle_pause_unpause = itertools.cycle(["pause", "unpause"]) toggle_mute_unmute = itertools.cycle(["mute", "unmute"]) def __init__(self, root, model, player): self.root = root self.model = model self.player = player self.create_gui() def create_gui(self): self.root.title(AUDIO_PLAYER_NAME) self.create_top_display() self.create_button_frame() self.create_list_box() self.create_bottom_frame() self.create_context_menu() def create_top_display(self): frame = tk.Frame(self.root) glass_frame_image = tk.PhotoImage(file='../icons/glass_frame.gif') self.canvas = tk.Canvas(frame, width=370, height=90) self.canvas.image = glass_frame_image self.canvas.grid(row=1) self.console = self.canvas.create_image( 0, 10, anchor=tk.NW, image=glass_frame_image) self.clock = self.canvas.create_text(125, 68, anchor=tk.W, fill='#CBE4F6', text="00:00") self.track_length_text = self.canvas.create_text(167, 68, anchor=tk.W, fill='#CBE4F6', text="of 00:00") self.track_name = self.canvas.create_text(50, 35, anchor=tk.W, fill='#9CEDAC', text='\"Currently playing: none \"') self.seek_bar = Seekbar( frame, background="blue", width=SEEKBAR_WIDTH, height=10) self.seek_bar.grid(row=2, columnspan=10, sticky='ew', padx=5) frame.grid(row=1, pady=1, padx=0) def create_button_frame(self): frame = tk.Frame(self.root) previous_track_icon = tk.PhotoImage(file='../icons/previous_track.gif') previous_track_button = tk.Button( frame, image=previous_track_icon, borderwidth=0, padx=0, command=self.on_previous_track_button_clicked) previous_track_button.image = previous_track_icon previous_track_button.grid(row=3, column=1, sticky='w') rewind_icon = tk.PhotoImage(file='../icons/rewind.gif') rewind_button = tk.Button( frame, image=rewind_icon, borderwidth=0, padx=0, command=self.on_rewind_button_clicked) rewind_button.image = rewind_icon rewind_button.grid(row=3, column=2, sticky='w') self.play_icon = tk.PhotoImage(file='../icons/play.gif') self.stop_icon = tk.PhotoImage(file='../icons/stop.gif') self.play_stop_button = tk.Button( frame, image=self.play_icon, borderwidth=0, padx=0, command=self.on_play_stop_button_clicked) self.play_stop_button.image = self.play_icon self.play_stop_button.grid(row=3, column=3) pause_icon = tk.PhotoImage(file='../icons/pause.gif') pause_unpause_button = tk.Button( frame, image=pause_icon, borderwidth=0, padx=0, command=self.on_pause_unpause_button_clicked) pause_unpause_button.image = pause_icon pause_unpause_button.grid(row=3, column=4) fast_forward_icon = tk.PhotoImage(file='../icons/fast_forward.gif') fast_forward_button = tk.Button( frame, image=fast_forward_icon, borderwidth=0, padx=0, command=self.on_fast_forward_button_clicked) fast_forward_button.image = fast_forward_icon fast_forward_button.grid(row=3, column=5) next_track_icon = tk.PhotoImage(file='../icons/next_track.gif') next_track_button = tk.Button( frame, image=next_track_icon, borderwidth=0, padx=0, command=self.on_next_track_button_clicked) next_track_button.image = next_track_icon next_track_button.grid(row=3, column=6) self.mute_icon = tk.PhotoImage(file='../icons/mute.gif') self.unmute_icon = tk.PhotoImage(file='../icons/unmute.gif') self.mute_unmute_button = tk.Button( frame, image=self.unmute_icon, text='unmute', borderwidth=0, padx=0, command=self.on_mute_unmute_button_clicked) self.mute_unmute_button.image = self.unmute_icon self.mute_unmute_button.grid(row=3, column=7) self.volume_scale = tkinter.ttk.Scale( frame, from_=0.0, to=1.0, command=self.on_volume_scale_changed) self.volume_scale.set(0.6) self.volume_scale.grid(row=3, column=8, padx=5) frame.grid(row=3, columnspan=5, sticky='w', pady=4, padx=5) def create_list_box(self): frame = tk.Frame(self.root) self.list_box = tk.Listbox(frame, activestyle='none', cursor='hand2', bg='#1C3D7D', fg='#A0B9E9', selectmode=tk.EXTENDED, height=10) self.list_box.pack(side=tk.LEFT, fill=tk.BOTH, expand=1) self.list_box.bind( "", self.on_play_list_double_clicked) self.list_box.bind("", self.show_context_menu) scroll_bar = tk.Scrollbar(frame) scroll_bar.pack(side=tk.RIGHT, fill=tk.BOTH) self.list_box.config(yscrollcommand=scroll_bar.set) scroll_bar.config(command=self.list_box.yview) frame.grid(row=4, padx=5, columnspan=10, sticky='ew') def create_bottom_frame(self): frame = tk.Frame(self.root) add_file_icon = tk.PhotoImage(file='../icons/add_file.gif') add_file_button = tk.Button(frame, image=add_file_icon, borderwidth=0, padx=0, text='Add File', command=self.on_add_file_button_clicked) add_file_button.image = add_file_icon add_file_button.grid(row=5, column=1) remove_selected_icon = tk.PhotoImage( file='../icons/delete_selected.gif') remove_selected_button = tk.Button( frame, image=remove_selected_icon, borderwidth=0, padx=0, text='Delete', command=self.on_remove_selected_button_clicked) remove_selected_button.image = remove_selected_icon remove_selected_button.grid(row=5, column=2) add_directory_icon = tk.PhotoImage(file='../icons/add_directory.gif') add_directory_button = tk.Button(frame, image=add_directory_icon, borderwidth=0, padx=0, text='Add Dir', command=self.on_add_directory_button_clicked) add_directory_button.image = add_directory_icon add_directory_button.grid(row=5, column=3) empty_play_list_icon = tk.PhotoImage( file='../icons/clear_play_list.gif') empty_play_list_button = tk.Button(frame, image=empty_play_list_icon, borderwidth=0, padx=0, text='Clear All', command=self.on_clear_play_list_button_clicked) empty_play_list_button.image = empty_play_list_icon empty_play_list_button.grid(row=5, column=4) self.loop_value = tk.IntVar() self.loop_value.set(3) for txt, val in self.loop_choices: tk.Radiobutton(frame, text=txt, variable=self.loop_value, value=val).grid( row=5, column=4 + val, pady=3) frame.grid(row=5, sticky='w', padx=5) def create_context_menu(self): self.context_menu = tk.Menu(self.list_box, tearoff=0) self.context_menu.add_command( label="Delete", command=self.on_remove_selected_context_menu_clicked) def show_context_menu(self, event): self.context_menu.tk_popup(event.x_root, event.y_root) def on_add_file_button_clicked(self): self.add_audio_file() def on_remove_selected_button_clicked(self): self.remove_selected_files() def on_add_directory_button_clicked(self): self.add_all_audio_files_from_directory() def on_clear_play_list_button_clicked(self): self.clear_play_list() def on_remove_selected_context_menu_clicked(self): self.remove_selected_files() def on_play_stop_button_clicked(self): action = next(self.toggle_play_stop) if action == 'play': try: self.current_track_index = self.list_box.curselection()[0] except IndexError: self.current_track_index = 0 self.start_play() elif action == 'stop': self.stop_play() def on_pause_unpause_button_clicked(self): action = next(self.toggle_pause_unpause) if action == 'pause': self.player.pause() elif action == 'unpause': self.player.unpause() def on_mute_unmute_button_clicked(self): action = next(self.toggle_mute_unmute) if action == 'mute': self.volume_at_time_of_mute = self.player.volume self.player.mute() self.volume_scale.set(0) self.mute_unmute_button.config(image=self.mute_icon) elif action == 'unmute': self.player.unmute(self.volume_at_time_of_mute) self.volume_scale.set(self.volume_at_time_of_mute) self.mute_unmute_button.config(image=self.unmute_icon) def on_previous_track_button_clicked(self): self.play_previous_track() def on_rewind_button_clicked(self): self.player.rewind() def on_fast_forward_button_clicked(self): self.player.fast_forward() def on_next_track_button_clicked(self): self.play_next_track() def on_volume_scale_changed(self, value): self.player.volume = self.volume_scale.get() if self.volume_scale.get() == 0.0: self.mute_unmute_button.config(image=self.mute_icon) else: self.mute_unmute_button.config(image=self.unmute_icon) def on_play_list_double_clicked(self, event=None): self.current_track_index = int(self.list_box.curselection()[0]) self.start_play() def play_previous_track(self): self.current_track_index = max(0, self.current_track_index - 1) self.start_play() def play_next_track(self): self.current_track_index = min( self.list_box.size() - 1, self.current_track_index + 1) self.start_play() def start_play(self): try: audio_file = self.model.get_file_to_play(self.current_track_index) except IndexError: return self.play_stop_button.config(image=self.stop_icon) self.player.play_media(audio_file) def stop_play(self): self.play_stop_button.config(image=self.play_icon) self.player.stop() def add_audio_file(self): audio_file = tkinter.filedialog.askopenfilename(filetypes=[( 'All supported', '.mp3 .wav'), ('.mp3 files', '.mp3'), ('.wav files', '.wav')]) if audio_file: self.model.add_to_play_list(audio_file) file_path, file_name = os.path.split(audio_file) self.list_box.insert(tk.END, file_name) def remove_selected_files(self): try: selected_indexes = self.list_box.curselection() for index in reversed(selected_indexes): self.list_box.delete(index) self.model.remove_item_from_play_list_at_index(index) except IndexError: pass def add_all_audio_files_from_directory(self): directory_path = tkinter.filedialog.askdirectory() if not directory_path: return audio_files_in_directory = self.get_all_audio_file_from_directory( directory_path) for audio_file in audio_files_in_directory: self.model.add_to_play_list(audio_file) file_path, file_name = os.path.split(audio_file) self.list_box.insert(tk.END, file_name) def get_all_audio_file_from_directory(self, directory_path): audio_files_in_directory = [] for (dirpath, dirnames, filenames) in os.walk(directory_path): for audio_file in filenames: if audio_file.endswith(".mp3") or audio_file.endswith(".wav"): audio_files_in_directory.append(dirpath + "/" + audio_file) return audio_files_in_directory def clear_play_list(self): self.model.clear_play_list() self.list_box.delete(0, tk.END) if __name__ == '__main__': root = tk.Tk() root.resizable(width=False, height=False) model = model.Model() player = player.Player() app = View(root, model, player) root.mainloop() ================================================ FILE: Chapter 05/5.06/helpers.py ================================================ """ Code illustration: 5.06 @Tkinter GUI Application Development Blueprints """ def get_time_in_minute_seconds(time_in_seconds): minutes = int(time_in_seconds / 60) seconds = int(time_in_seconds % 60) return (minutes, seconds) def truncate_text(text, truncate_length): truncate_length_plus_two = truncate_length + 2 # account for double dots return (text[:truncate_length_plus_two] + '..') if len(text) > truncate_length else text ================================================ FILE: Chapter 05/5.06/model.py ================================================ """ Code illustration: 5.06 @Tkinter GUI Application Development Blueprints """ class Model: def __init__(self): self.__play_list = [] @property def play_list(self): return self.__play_list def get_file_to_play(self, file_index): return self.__play_list[file_index] def clear_play_list(self): self.__play_list.clear() def add_to_play_list(self, file_name): self.__play_list.append(file_name) def remove_item_from_play_list_at_index(self, index): del self.__play_list[index] ================================================ FILE: Chapter 05/5.06/player.py ================================================ """ Code illustration: 5.06 @Tkinter GUI Application Development Blueprints """ import pyglet FORWARD_REWIND_JUMP_TIME = 20 class Player: def __init__(self): self.player = pyglet.media.Player() self.player.volume = 0.6 def play_media(self, audio_file): self.reset_player() self.player = pyglet.media.Player() self.source = pyglet.media.load(audio_file) self.player.queue(self.source) self.player.play() def reset_player(self): self.player.pause() self.player.delete() def is_playing(self): try: elapsed_time = int(self.player.time) is_playing = elapsed_time < int(self.track_length) except: is_playing = False return is_playing def seek(self, time): try: self.player.seek(time) except AttributeError: pass @property def track_length(self): try: return self.source.duration except AttributeError: return 0 @property def volume(self): return self.player.volume @property def elapsed_play_duration(self): return self.player.time @volume.setter def volume(self, volume): self.player.volume = volume def unpause(self): self.player.play() def pause(self): self.player.pause() def stop(self): self.reset_player() def mute(self): self.player.volume = 0.0 def unmute(self, newvolume_level): self.player.volume = newvolume_level def fast_forward(self): time = self.player.time + FORWARD_REWIND_JUMP_TIME try: if self.source.duration > time: self.seek(time) else: self.seek(self.source.duration) except AttributeError: pass def rewind(self): time = self.player.time - FORWARD_REWIND_JUMP_TIME try: self.seek(time) except: self.seek(0) ================================================ FILE: Chapter 05/5.06/seekbar.py ================================================ """ Code illustration: 5.06 @Tkinter GUI Application Development Blueprints """ import tkinter as tk class Seekbar(tk.Canvas): def __init__(self, parent, **options): tk.Canvas.__init__(self, parent, options) self.parent = parent self.width = options['width'] self.red_rectangle = self.create_rectangle(0, 0, 0, 0, fill="red") self.seekbar_knob_image = tk.PhotoImage(file="../icons/seekbar_knob.gif") self.seekbar_knob = self.create_image( 0, 0, image=self.seekbar_knob_image) self.bind_mouse_button() def bind_mouse_button(self): self.bind('', self.on_seekbar_clicked) self.bind('', self.on_seekbar_clicked) self.tag_bind( self.red_rectangle, '', self.on_seekbar_clicked) self.tag_bind( self.seekbar_knob, '', self.on_seekbar_clicked) def on_seekbar_clicked(self, event=None): if event.x > 0 and event.x < self.width: self.slide_to_position(event.x) def slide_to_position(self, new_position): self.coords(self.red_rectangle, 0, 0, new_position, new_position) self.coords(self.seekbar_knob, new_position, 0) self.event_generate("<>", x=new_position) class TestSeekBar(): def __init__(self): root = tk.Tk() root.bind("<>", self.seek_new_position) frame = tk.Frame(root) frame.grid(row=1, pady=10, padx=10) c = Seekbar( frame, background="blue", width=360, height=10) c.grid(row=2, columnspan=10, sticky='ew', padx=5) root.mainloop() def seek_new_position(self, event): print("Dragged to x:", event.x) if __name__ == '__main__': TestSeekBar() ================================================ FILE: Chapter 05/5.06/view.py ================================================ """ Code illustration: 5.06 New modules imported here: from helpers import * Methods added here: manage_one_time_track_updates_on_play_start(self): update_now_playing_text() current_track_position = 0 display_track_duration() @Tkinter GUI Application Development Blueprints """ import tkinter as tk import tkinter.filedialog import tkinter.messagebox import tkinter.ttk import os import model import player from seekbar import * from helpers import * import itertools AUDIO_PLAYER_NAME = "Achtung Baby" SEEKBAR_WIDTH = 360 class View: loop_choices = [("No Loop", 1), ("Loop Current", 2), ("Loop All", 3)] current_track_index = 0 toggle_play_stop = itertools.cycle(["play", "stop"]) toggle_pause_unpause = itertools.cycle(["pause", "unpause"]) toggle_mute_unmute = itertools.cycle(["mute", "unmute"]) def __init__(self, root, model, player): self.root = root self.model = model self.player = player self.create_gui() def create_gui(self): self.root.title(AUDIO_PLAYER_NAME) self.create_top_display() self.create_button_frame() self.create_list_box() self.create_bottom_frame() self.create_context_menu() def create_top_display(self): frame = tk.Frame(self.root) glass_frame_image = tk.PhotoImage(file='../icons/glass_frame.gif') self.canvas = tk.Canvas(frame, width=370, height=90) self.canvas.image = glass_frame_image self.canvas.grid(row=1) self.console = self.canvas.create_image( 0, 10, anchor=tk.NW, image=glass_frame_image) self.clock = self.canvas.create_text(125, 68, anchor=tk.W, fill='#CBE4F6', text="00:00") self.track_length_text = self.canvas.create_text(167, 68, anchor=tk.W, fill='#CBE4F6', text="of 00:00") self.track_name = self.canvas.create_text(50, 35, anchor=tk.W, fill='#9CEDAC', text='\"Currently playing: none \"') self.seek_bar = Seekbar( frame, background="blue", width=SEEKBAR_WIDTH, height=10) self.seek_bar.grid(row=2, columnspan=10, sticky='ew', padx=5) frame.grid(row=1, pady=1, padx=0) def create_button_frame(self): frame = tk.Frame(self.root) previous_track_icon = tk.PhotoImage(file='../icons/previous_track.gif') previous_track_button = tk.Button( frame, image=previous_track_icon, borderwidth=0, padx=0, command=self.on_previous_track_button_clicked) previous_track_button.image = previous_track_icon previous_track_button.grid(row=3, column=1, sticky='w') rewind_icon = tk.PhotoImage(file='../icons/rewind.gif') rewind_button = tk.Button( frame, image=rewind_icon, borderwidth=0, padx=0, command=self.on_rewind_button_clicked) rewind_button.image = rewind_icon rewind_button.grid(row=3, column=2, sticky='w') self.play_icon = tk.PhotoImage(file='../icons/play.gif') self.stop_icon = tk.PhotoImage(file='../icons/stop.gif') self.play_stop_button = tk.Button( frame, image=self.play_icon, borderwidth=0, padx=0, command=self.on_play_stop_button_clicked) self.play_stop_button.image = self.play_icon self.play_stop_button.grid(row=3, column=3) pause_icon = tk.PhotoImage(file='../icons/pause.gif') pause_unpause_button = tk.Button( frame, image=pause_icon, borderwidth=0, padx=0, command=self.on_pause_unpause_button_clicked) pause_unpause_button.image = pause_icon pause_unpause_button.grid(row=3, column=4) fast_forward_icon = tk.PhotoImage(file='../icons/fast_forward.gif') fast_forward_button = tk.Button( frame, image=fast_forward_icon, borderwidth=0, padx=0, command=self.on_fast_forward_button_clicked) fast_forward_button.image = fast_forward_icon fast_forward_button.grid(row=3, column=5) next_track_icon = tk.PhotoImage(file='../icons/next_track.gif') next_track_button = tk.Button( frame, image=next_track_icon, borderwidth=0, padx=0, command=self.on_next_track_button_clicked) next_track_button.image = next_track_icon next_track_button.grid(row=3, column=6) self.mute_icon = tk.PhotoImage(file='../icons/mute.gif') self.unmute_icon = tk.PhotoImage(file='../icons/unmute.gif') self.mute_unmute_button = tk.Button( frame, image=self.unmute_icon, text='unmute', borderwidth=0, padx=0, command=self.on_mute_unmute_button_clicked) self.mute_unmute_button.image = self.unmute_icon self.mute_unmute_button.grid(row=3, column=7) self.volume_scale = tkinter.ttk.Scale( frame, from_=0.0, to=1.0, command=self.on_volume_scale_changed) self.volume_scale.set(0.6) self.volume_scale.grid(row=3, column=8, padx=5) frame.grid(row=3, columnspan=5, sticky='w', pady=4, padx=5) def create_list_box(self): frame = tk.Frame(self.root) self.list_box = tk.Listbox(frame, activestyle='none', cursor='hand2', bg='#1C3D7D', fg='#A0B9E9', selectmode=tk.EXTENDED, height=10) self.list_box.pack(side=tk.LEFT, fill=tk.BOTH, expand=1) self.list_box.bind( "", self.on_play_list_double_clicked) self.list_box.bind("", self.show_context_menu) scroll_bar = tk.Scrollbar(frame) scroll_bar.pack(side=tk.RIGHT, fill=tk.BOTH) self.list_box.config(yscrollcommand=scroll_bar.set) scroll_bar.config(command=self.list_box.yview) frame.grid(row=4, padx=5, columnspan=10, sticky='ew') def create_bottom_frame(self): frame = tk.Frame(self.root) add_file_icon = tk.PhotoImage(file='../icons/add_file.gif') add_file_button = tk.Button(frame, image=add_file_icon, borderwidth=0, padx=0, text='Add File', command=self.on_add_file_button_clicked) add_file_button.image = add_file_icon add_file_button.grid(row=5, column=1) remove_selected_icon = tk.PhotoImage( file='../icons/delete_selected.gif') remove_selected_button = tk.Button( frame, image=remove_selected_icon, borderwidth=0, padx=0, text='Delete', command=self.on_remove_selected_button_clicked) remove_selected_button.image = remove_selected_icon remove_selected_button.grid(row=5, column=2) add_directory_icon = tk.PhotoImage(file='../icons/add_directory.gif') add_directory_button = tk.Button(frame, image=add_directory_icon, borderwidth=0, padx=0, text='Add Dir', command=self.on_add_directory_button_clicked) add_directory_button.image = add_directory_icon add_directory_button.grid(row=5, column=3) empty_play_list_icon = tk.PhotoImage( file='../icons/clear_play_list.gif') empty_play_list_button = tk.Button(frame, image=empty_play_list_icon, borderwidth=0, padx=0, text='Clear All', command=self.on_clear_play_list_button_clicked) empty_play_list_button.image = empty_play_list_icon empty_play_list_button.grid(row=5, column=4) self.loop_value = tk.IntVar() self.loop_value.set(3) for txt, val in self.loop_choices: tk.Radiobutton(frame, text=txt, variable=self.loop_value, value=val).grid( row=5, column=4 + val, pady=3) frame.grid(row=5, sticky='w', padx=5) def create_context_menu(self): self.context_menu = tk.Menu(self.list_box, tearoff=0) self.context_menu.add_command( label="Delete", command=self.on_remove_selected_context_menu_clicked) def show_context_menu(self, event): self.context_menu.tk_popup(event.x_root, event.y_root) def on_add_file_button_clicked(self): self.add_audio_file() def on_remove_selected_button_clicked(self): self.remove_selected_files() def on_add_directory_button_clicked(self): self.add_all_audio_files_from_directory() def on_clear_play_list_button_clicked(self): self.clear_play_list() def on_remove_selected_context_menu_clicked(self): self.remove_selected_files() def on_play_stop_button_clicked(self): action = next(self.toggle_play_stop) if action == 'play': try: self.current_track_index = self.list_box.curselection()[0] except IndexError: self.current_track_index = 0 self.start_play() elif action == 'stop': self.stop_play() def on_pause_unpause_button_clicked(self): action = next(self.toggle_pause_unpause) if action == 'pause': self.player.pause() elif action == 'unpause': self.player.unpause() def on_mute_unmute_button_clicked(self): action = next(self.toggle_mute_unmute) if action == 'mute': self.volume_at_time_of_mute = self.player.volume self.player.mute() self.volume_scale.set(0) self.mute_unmute_button.config(image=self.mute_icon) elif action == 'unmute': self.player.unmute(self.volume_at_time_of_mute) self.volume_scale.set(self.volume_at_time_of_mute) self.mute_unmute_button.config(image=self.unmute_icon) def on_previous_track_button_clicked(self): self.play_previous_track() def on_rewind_button_clicked(self): self.player.rewind() def on_fast_forward_button_clicked(self): self.player.fast_forward() def on_next_track_button_clicked(self): self.play_next_track() def on_volume_scale_changed(self, value): self.player.volume = self.volume_scale.get() if self.volume_scale.get() == 0.0: self.mute_unmute_button.config(image=self.mute_icon) else: self.mute_unmute_button.config(image=self.unmute_icon) def on_play_list_double_clicked(self, event=None): self.current_track_index = int(self.list_box.curselection()[0]) self.start_play() def play_previous_track(self): self.current_track_index = max(0, self.current_track_index - 1) self.start_play() def play_next_track(self): self.current_track_index = min( self.list_box.size() - 1, self.current_track_index + 1) self.start_play() def start_play(self): try: audio_file = self.model.get_file_to_play(self.current_track_index) except IndexError: return self.play_stop_button.config(image=self.stop_icon) self.player.play_media(audio_file) self.current_track_position = 0 self.manage_one_time_track_updates_on_play_start() def stop_play(self): self.play_stop_button.config(image=self.play_icon) self.player.stop() def add_audio_file(self): audio_file = tkinter.filedialog.askopenfilename(filetypes=[( 'All supported', '.mp3 .wav'), ('.mp3 files', '.mp3'), ('.wav files', '.wav')]) if audio_file: self.model.add_to_play_list(audio_file) file_path, file_name = os.path.split(audio_file) self.list_box.insert(tk.END, file_name) def remove_selected_files(self): try: selected_indexes = self.list_box.curselection() for index in reversed(selected_indexes): self.list_box.delete(index) self.model.remove_item_from_play_list_at_index(index) except IndexError: pass def add_all_audio_files_from_directory(self): directory_path = tkinter.filedialog.askdirectory() if not directory_path: return audio_files_in_directory = self.get_all_audio_file_from_directory( directory_path) for audio_file in audio_files_in_directory: self.model.add_to_play_list(audio_file) file_path, file_name = os.path.split(audio_file) self.list_box.insert(tk.END, file_name) def get_all_audio_file_from_directory(self, directory_path): audio_files_in_directory = [] for (dirpath, dirnames, filenames) in os.walk(directory_path): for audio_file in filenames: if audio_file.endswith(".mp3") or audio_file.endswith(".wav"): audio_files_in_directory.append(dirpath + "/" + audio_file) return audio_files_in_directory def clear_play_list(self): self.model.clear_play_list() self.list_box.delete(0, tk.END) def manage_one_time_track_updates_on_play_start(self): self.update_now_playing_text() self.display_track_duration() def update_now_playing_text(self): current_track = self.model.play_list[self.current_track_index] file_path, file_name = os.path.split(current_track) truncated_track_name = truncate_text(file_name, 40) self.canvas.itemconfig(self.track_name, text=truncated_track_name) def display_track_duration(self): self.track_length = self.player.track_length minutes, seconds = get_time_in_minute_seconds(self.track_length) track_length_string = 'of {0:02d}:{1:02d}'.format(minutes, seconds) self.canvas.itemconfig( self.track_length_text, text=track_length_string) if __name__ == '__main__': root = tk.Tk() root.resizable(width=False, height=False) model = model.Model() player = player.Player() app = View(root, model, player) root.mainloop() ================================================ FILE: Chapter 05/5.07/helpers.py ================================================ """ Code illustration: 5.07 @Tkinter GUI Application Development Blueprints """ def get_time_in_minute_seconds(time_in_seconds): minutes = int(time_in_seconds / 60) seconds = int(time_in_seconds % 60) return (minutes, seconds) def truncate_text(text, truncate_length): truncate_length_plus_two = truncate_length + 2 # account for double dots return (text[:truncate_length_plus_two] + '..') if len(text) > truncate_length else text ================================================ FILE: Chapter 05/5.07/model.py ================================================ """ Code illustration: 5.07 @Tkinter GUI Application Development Blueprints """ class Model: def __init__(self): self.__play_list = [] @property def play_list(self): return self.__play_list def get_file_to_play(self, file_index): return self.__play_list[file_index] def clear_play_list(self): self.__play_list.clear() def add_to_play_list(self, file_name): self.__play_list.append(file_name) def remove_item_from_play_list_at_index(self, index): del self.__play_list[index] ================================================ FILE: Chapter 05/5.07/player.py ================================================ """ Code illustration: 5.07 @Tkinter GUI Application Development Blueprints """ import pyglet FORWARD_REWIND_JUMP_TIME = 20 class Player: def __init__(self): self.player = pyglet.media.Player() self.player.volume = 0.6 def play_media(self, audio_file): self.reset_player() self.player = pyglet.media.Player() self.source = pyglet.media.load(audio_file) self.player.queue(self.source) self.player.play() def reset_player(self): self.player.pause() self.player.delete() def is_playing(self): try: elapsed_time = int(self.player.time) is_playing = elapsed_time < int(self.track_length) except: is_playing = False return is_playing def seek(self, time): try: self.player.seek(time) except AttributeError: pass @property def track_length(self): try: return self.source.duration except AttributeError: return 0 @property def volume(self): return self.player.volume @property def elapsed_play_duration(self): return self.player.time @volume.setter def volume(self, volume): self.player.volume = volume def unpause(self): self.player.play() def pause(self): self.player.pause() def stop(self): self.reset_player() def mute(self): self.player.volume = 0.0 def unmute(self, newvolume_level): self.player.volume = newvolume_level def fast_forward(self): time = self.player.time + FORWARD_REWIND_JUMP_TIME try: if self.source.duration > time: self.seek(time) else: self.seek(self.source.duration) except AttributeError: pass def rewind(self): time = self.player.time - FORWARD_REWIND_JUMP_TIME try: self.seek(time) except: self.seek(0) ================================================ FILE: Chapter 05/5.07/seekbar.py ================================================ """ Code illustration: 5.07 Method modified here: on_seekbar_knob_clicked() - added a call to seek_new_position() from View class @Tkinter GUI Application Development Blueprints """ import tkinter as tk class Seekbar(tk.Canvas): def __init__(self, parent, **options): tk.Canvas.__init__(self, parent, options) self.parent = parent self.width = options['width'] self.red_rectangle = self.create_rectangle(0, 0, 0, 0, fill="red") self.seekbar_knob_image = tk.PhotoImage(file="../icons/seekbar_knob.gif") self.seekbar_knob = self.create_image( 0, 0, image=self.seekbar_knob_image) self.bind_mouse_button() def bind_mouse_button(self): self.bind('', self.on_seekbar_clicked) self.bind('', self.on_seekbar_clicked) self.tag_bind( self.red_rectangle, '', self.on_seekbar_clicked) self.tag_bind( self.seekbar_knob, '', self.on_seekbar_clicked) def on_seekbar_clicked(self, event=None): self.slide_to_position(event.x) def slide_to_position(self, new_position): if 0 <= new_position <= self.width: self.coords(self.red_rectangle, 0, 0, new_position, new_position) self.coords(self.seekbar_knob, new_position, 0) self.event_generate("<>", x=new_position) class TestSeekBar(): def __init__(self): root = tk.Tk() root.bind("<>", self.seek_new_position) frame = tk.Frame(root) frame.grid(row=1, pady=10, padx=10) c = Seekbar( frame, background="blue", width=360, height=10) c.grid(row=2, columnspan=10, sticky='ew', padx=5) root.mainloop() def seek_new_position(self, event): print("Dragged to x:", event.x) if __name__ == '__main__': TestSeekBar() ================================================ FILE: Chapter 05/5.07/view.py ================================================ """ Code illustration: 5.07 New methods added here: manage_periodic_updates_during_play() update_clock() update_seek_bar() seek_new_position() Methods modified here: start_play() - added a call to manage_periodic_updates_during_play @Tkinter GUI Application Development Blueprints """ import tkinter as tk import tkinter.filedialog import tkinter.messagebox import tkinter.ttk import os import model import player from seekbar import * from helpers import * import itertools AUDIO_PLAYER_NAME = "Achtung Baby" SEEKBAR_WIDTH = 360 class View: loop_choices = [("No Loop", 1), ("Loop Current", 2), ("Loop All", 3)] current_track_index = 0 toggle_play_stop = itertools.cycle(["play", "stop"]) toggle_pause_unpause = itertools.cycle(["pause", "unpause"]) toggle_mute_unmute = itertools.cycle(["mute", "unmute"]) def __init__(self, root, model, player): self.root = root self.model = model self.player = player self.create_gui() self.root.bind("<>", self.seek_new_position) def create_gui(self): self.root.title(AUDIO_PLAYER_NAME) self.create_top_display() self.create_button_frame() self.create_list_box() self.create_bottom_frame() self.create_context_menu() def create_top_display(self): frame = tk.Frame(self.root) glass_frame_image = tk.PhotoImage(file='../icons/glass_frame.gif') self.canvas = tk.Canvas(frame, width=370, height=90) self.canvas.image = glass_frame_image self.canvas.grid(row=1) self.console = self.canvas.create_image( 0, 10, anchor=tk.NW, image=glass_frame_image) self.clock = self.canvas.create_text(125, 68, anchor=tk.W, fill='#CBE4F6', text="00:00") self.track_length_text = self.canvas.create_text(167, 68, anchor=tk.W, fill='#CBE4F6', text="of 00:00") self.track_name = self.canvas.create_text(50, 35, anchor=tk.W, fill='#9CEDAC', text='\"Currently playing: none \"') self.seek_bar = Seekbar( frame, background="blue", width=SEEKBAR_WIDTH, height=10) self.seek_bar.grid(row=2, columnspan=10, sticky='ew', padx=5) frame.grid(row=1, pady=1, padx=0) def create_button_frame(self): frame = tk.Frame(self.root) previous_track_icon = tk.PhotoImage(file='../icons/previous_track.gif') previous_track_button = tk.Button( frame, image=previous_track_icon, borderwidth=0, padx=0, command=self.on_previous_track_button_clicked) previous_track_button.image = previous_track_icon previous_track_button.grid(row=3, column=1, sticky='w') rewind_icon = tk.PhotoImage(file='../icons/rewind.gif') rewind_button = tk.Button( frame, image=rewind_icon, borderwidth=0, padx=0, command=self.on_rewind_button_clicked) rewind_button.image = rewind_icon rewind_button.grid(row=3, column=2, sticky='w') self.play_icon = tk.PhotoImage(file='../icons/play.gif') self.stop_icon = tk.PhotoImage(file='../icons/stop.gif') self.play_stop_button = tk.Button( frame, image=self.play_icon, borderwidth=0, padx=0, command=self.on_play_stop_button_clicked) self.play_stop_button.image = self.play_icon self.play_stop_button.grid(row=3, column=3) pause_icon = tk.PhotoImage(file='../icons/pause.gif') pause_unpause_button = tk.Button( frame, image=pause_icon, borderwidth=0, padx=0, command=self.on_pause_unpause_button_clicked) pause_unpause_button.image = pause_icon pause_unpause_button.grid(row=3, column=4) fast_forward_icon = tk.PhotoImage(file='../icons/fast_forward.gif') fast_forward_button = tk.Button( frame, image=fast_forward_icon, borderwidth=0, padx=0, command=self.on_fast_forward_button_clicked) fast_forward_button.image = fast_forward_icon fast_forward_button.grid(row=3, column=5) next_track_icon = tk.PhotoImage(file='../icons/next_track.gif') next_track_button = tk.Button( frame, image=next_track_icon, borderwidth=0, padx=0, command=self.on_next_track_button_clicked) next_track_button.image = next_track_icon next_track_button.grid(row=3, column=6) self.mute_icon = tk.PhotoImage(file='../icons/mute.gif') self.unmute_icon = tk.PhotoImage(file='../icons/unmute.gif') self.mute_unmute_button = tk.Button( frame, image=self.unmute_icon, text='unmute', borderwidth=0, padx=0, command=self.on_mute_unmute_button_clicked) self.mute_unmute_button.image = self.unmute_icon self.mute_unmute_button.grid(row=3, column=7) self.volume_scale = tkinter.ttk.Scale( frame, from_=0.0, to=1.0, command=self.on_volume_scale_changed) self.volume_scale.set(0.6) self.volume_scale.grid(row=3, column=8, padx=5) frame.grid(row=3, columnspan=5, sticky='w', pady=4, padx=5) def create_list_box(self): frame = tk.Frame(self.root) self.list_box = tk.Listbox(frame, activestyle='none', cursor='hand2', bg='#1C3D7D', fg='#A0B9E9', selectmode=tk.EXTENDED, height=10) self.list_box.pack(side=tk.LEFT, fill=tk.BOTH, expand=1) self.list_box.bind( "", self.on_play_list_double_clicked) self.list_box.bind("", self.show_context_menu) scroll_bar = tk.Scrollbar(frame) scroll_bar.pack(side=tk.RIGHT, fill=tk.BOTH) self.list_box.config(yscrollcommand=scroll_bar.set) scroll_bar.config(command=self.list_box.yview) frame.grid(row=4, padx=5, columnspan=10, sticky='ew') def create_bottom_frame(self): frame = tk.Frame(self.root) add_file_icon = tk.PhotoImage(file='../icons/add_file.gif') add_file_button = tk.Button(frame, image=add_file_icon, borderwidth=0, padx=0, text='Add File', command=self.on_add_file_button_clicked) add_file_button.image = add_file_icon add_file_button.grid(row=5, column=1) remove_selected_icon = tk.PhotoImage( file='../icons/delete_selected.gif') remove_selected_button = tk.Button( frame, image=remove_selected_icon, borderwidth=0, padx=0, text='Delete', command=self.on_remove_selected_button_clicked) remove_selected_button.image = remove_selected_icon remove_selected_button.grid(row=5, column=2) add_directory_icon = tk.PhotoImage(file='../icons/add_directory.gif') add_directory_button = tk.Button(frame, image=add_directory_icon, borderwidth=0, padx=0, text='Add Dir', command=self.on_add_directory_button_clicked) add_directory_button.image = add_directory_icon add_directory_button.grid(row=5, column=3) empty_play_list_icon = tk.PhotoImage( file='../icons/clear_play_list.gif') empty_play_list_button = tk.Button(frame, image=empty_play_list_icon, borderwidth=0, padx=0, text='Clear All', command=self.on_clear_play_list_button_clicked) empty_play_list_button.image = empty_play_list_icon empty_play_list_button.grid(row=5, column=4) self.loop_value = tk.IntVar() self.loop_value.set(3) for txt, val in self.loop_choices: tk.Radiobutton(frame, text=txt, variable=self.loop_value, value=val).grid( row=5, column=4 + val, pady=3) frame.grid(row=5, sticky='w', padx=5) def create_context_menu(self): self.context_menu = tk.Menu(self.list_box, tearoff=0) self.context_menu.add_command( label="Delete", command=self.on_remove_selected_context_menu_clicked) def show_context_menu(self, event): self.context_menu.tk_popup(event.x_root, event.y_root) def on_add_file_button_clicked(self): self.add_audio_file() def on_remove_selected_button_clicked(self): self.remove_selected_files() def on_add_directory_button_clicked(self): self.add_all_audio_files_from_directory() def on_clear_play_list_button_clicked(self): self.clear_play_list() def on_remove_selected_context_menu_clicked(self): self.remove_selected_files() def on_play_stop_button_clicked(self): action = next(self.toggle_play_stop) if action == 'play': try: self.current_track_index = self.list_box.curselection()[0] except IndexError: self.current_track_index = 0 self.start_play() elif action == 'stop': self.stop_play() def on_pause_unpause_button_clicked(self): action = next(self.toggle_pause_unpause) if action == 'pause': self.player.pause() elif action == 'unpause': self.player.unpause() def on_mute_unmute_button_clicked(self): action = next(self.toggle_mute_unmute) if action == 'mute': self.volume_at_time_of_mute = self.player.volume self.player.mute() self.volume_scale.set(0) self.mute_unmute_button.config(image=self.mute_icon) elif action == 'unmute': self.player.unmute(self.volume_at_time_of_mute) self.volume_scale.set(self.volume_at_time_of_mute) self.mute_unmute_button.config(image=self.unmute_icon) def on_previous_track_button_clicked(self): self.play_previous_track() def on_rewind_button_clicked(self): self.player.rewind() def on_fast_forward_button_clicked(self): self.player.fast_forward() def on_next_track_button_clicked(self): self.play_next_track() def on_volume_scale_changed(self, value): self.player.volume = self.volume_scale.get() if self.volume_scale.get() == 0.0: self.mute_unmute_button.config(image=self.mute_icon) else: self.mute_unmute_button.config(image=self.unmute_icon) def on_play_list_double_clicked(self, event=None): self.current_track_index = int(self.list_box.curselection()[0]) self.start_play() def play_previous_track(self): self.current_track_index = max(0, self.current_track_index - 1) self.start_play() def play_next_track(self): self.current_track_index = min( self.list_box.size() - 1, self.current_track_index + 1) self.start_play() def start_play(self): try: audio_file = self.model.get_file_to_play(self.current_track_index) except IndexError: return self.play_stop_button.config(image=self.stop_icon) self.player.play_media(audio_file) self.current_track_position = 0 self.manage_one_time_track_updates_on_play_start() self.manage_periodic_updates_during_play() def stop_play(self): self.play_stop_button.config(image=self.play_icon) self.player.stop() def add_audio_file(self): audio_file = tkinter.filedialog.askopenfilename(filetypes=[( 'All supported', '.mp3 .wav'), ('.mp3 files', '.mp3'), ('.wav files', '.wav')]) if audio_file: self.model.add_to_play_list(audio_file) file_path, file_name = os.path.split(audio_file) self.list_box.insert(tk.END, file_name) def remove_selected_files(self): try: selected_indexes = self.list_box.curselection() for index in reversed(selected_indexes): self.list_box.delete(index) self.model.remove_item_from_play_list_at_index(index) except IndexError: pass def add_all_audio_files_from_directory(self): directory_path = tkinter.filedialog.askdirectory() if not directory_path: return audio_files_in_directory = self.get_all_audio_file_from_directory( directory_path) for audio_file in audio_files_in_directory: self.model.add_to_play_list(audio_file) file_path, file_name = os.path.split(audio_file) self.list_box.insert(tk.END, file_name) def get_all_audio_file_from_directory(self, directory_path): audio_files_in_directory = [] for (dirpath, dirnames, filenames) in os.walk(directory_path): for audio_file in filenames: if audio_file.endswith(".mp3") or audio_file.endswith(".wav"): audio_files_in_directory.append(dirpath + "/" + audio_file) return audio_files_in_directory def clear_play_list(self): self.model.clear_play_list() self.list_box.delete(0, tk.END) def manage_one_time_track_updates_on_play_start(self): self.update_now_playing_text() self.display_track_duration() def update_now_playing_text(self): current_track = self.model.play_list[self.current_track_index] file_path, file_name = os.path.split(current_track) truncated_track_name = truncate_text(file_name, 40) self.canvas.itemconfig(self.track_name, text=truncated_track_name) def display_track_duration(self): self.track_length = self.player.track_length minutes, seconds = get_time_in_minute_seconds(self.track_length) track_length_string = 'of {0:02d}:{1:02d}'.format(minutes, seconds) self.canvas.itemconfig( self.track_length_text, text=track_length_string) def manage_periodic_updates_during_play(self): self.update_clock() self.update_seek_bar() self.root.after(1000, self.manage_periodic_updates_during_play) def update_clock(self): self.elapsed_play_duration = self.player.elapsed_play_duration minutes, seconds = get_time_in_minute_seconds( self.elapsed_play_duration) current_time_string = '{0:02d}:{1:02d}'.format(minutes, seconds) self.canvas.itemconfig(self.clock, text=current_time_string) def update_seek_bar(self): seek_bar_position = SEEKBAR_WIDTH * \ self.player.elapsed_play_duration / self.track_length self.seek_bar.slide_to_position(seek_bar_position) def seek_new_position(self, event=None): time = self.player.track_length * event.x / SEEKBAR_WIDTH self.player.seek(time) if __name__ == '__main__': root = tk.Tk() root.resizable(width=False, height=False) model = model.Model() player = player.Player() app = View(root, model, player) root.mainloop() ================================================ FILE: Chapter 05/5.08/helpers.py ================================================ """ Code illustration: 5.08 @Tkinter GUI Application Development Blueprints """ def get_time_in_minute_seconds(time_in_seconds): minutes = int(time_in_seconds / 60) seconds = int(time_in_seconds % 60) return (minutes, seconds) def truncate_text(text, truncate_length): truncate_length_plus_two = truncate_length + 2 # account for double dots return (text[:truncate_length_plus_two] + '..') if len(text) > truncate_length else text ================================================ FILE: Chapter 05/5.08/model.py ================================================ """ Code illustration: 5.08 @Tkinter GUI Application Development Blueprints """ class Model: def __init__(self): self.__play_list = [] @property def play_list(self): return self.__play_list def get_file_to_play(self, file_index): return self.__play_list[file_index] def clear_play_list(self): self.__play_list.clear() def add_to_play_list(self, file_name): self.__play_list.append(file_name) def remove_item_from_play_list_at_index(self, index): del self.__play_list[index] ================================================ FILE: Chapter 05/5.08/player.py ================================================ """ Code illustration: 5.08 @Tkinter GUI Application Development Blueprints """ import pyglet FORWARD_REWIND_JUMP_TIME = 20 class Player: def __init__(self): self.player = pyglet.media.Player() self.player.volume = 0.6 def play_media(self, audio_file): self.reset_player() self.player = pyglet.media.Player() self.source = pyglet.media.load(audio_file) self.player.queue(self.source) self.player.play() def reset_player(self): self.player.pause() self.player.delete() def is_playing(self): try: elapsed_time = int(self.player.time) is_playing = elapsed_time < int(self.track_length) except: is_playing = False return is_playing def seek(self, time): try: self.player.seek(time) except AttributeError: pass @property def track_length(self): try: return self.source.duration except AttributeError: return 0 @property def volume(self): return self.player.volume @property def elapsed_play_duration(self): return self.player.time @volume.setter def volume(self, volume): self.player.volume = volume def unpause(self): self.player.play() def pause(self): self.player.pause() def stop(self): self.reset_player() def mute(self): self.player.volume = 0.0 def unmute(self, newvolume_level): self.player.volume = newvolume_level def fast_forward(self): time = self.player.time + FORWARD_REWIND_JUMP_TIME try: if self.source.duration > time: self.seek(time) else: self.seek(self.source.duration) except AttributeError: pass def rewind(self): time = self.player.time - FORWARD_REWIND_JUMP_TIME try: self.seek(time) except: self.seek(0) ================================================ FILE: Chapter 05/5.08/seekbar.py ================================================ """ Code illustration: 5.08 @Tkinter GUI Application Development Blueprints """ import tkinter as tk class Seekbar(tk.Canvas): def __init__(self, parent, **options): tk.Canvas.__init__(self, parent, options) self.parent = parent self.width = options['width'] self.red_rectangle = self.create_rectangle(0, 0, 0, 0, fill="red") self.seekbar_knob_image = tk.PhotoImage(file="../icons/seekbar_knob.gif") self.seekbar_knob = self.create_image( 0, 0, image=self.seekbar_knob_image) self.bind_mouse_button() def bind_mouse_button(self): self.bind('', self.on_seekbar_clicked) self.bind('', self.on_seekbar_clicked) self.tag_bind( self.red_rectangle, '', self.on_seekbar_clicked) self.tag_bind( self.seekbar_knob, '', self.on_seekbar_clicked) def on_seekbar_clicked(self, event=None): if event.x > 0 and event.x < self.width: self.slide_to_position(event.x) def slide_to_position(self, new_position): self.coords(self.red_rectangle, 0, 0, new_position, new_position) self.coords(self.seekbar_knob, new_position, 0) self.event_generate("<>", x=new_position) class TestSeekBar(): def __init__(self): root = tk.Tk() root.bind("<>", self.seek_new_position) frame = tk.Frame(root) frame.grid(row=1, pady=10, padx=10) c = Seekbar( frame, background="blue", width=360, height=10) c.grid(row=2, columnspan=10, sticky='ew', padx=5) root.mainloop() def seek_new_position(self, event): print("Dragged to x:", event.x) if __name__ == '__main__': TestSeekBar() ================================================ FILE: Chapter 05/5.08/view.py ================================================ """ Code illustration: 5.08 methods modified manage_periodic_updates_during_play() __init__ method - override of close new methods added here: not_to_loop() close_player() @Tkinter GUI Application Development Blueprints """ import tkinter as tk import tkinter.filedialog import tkinter.messagebox import tkinter.ttk import os import model import player from seekbar import * from helpers import * import itertools AUDIO_PLAYER_NAME = "Achtung Baby" SEEKBAR_WIDTH = 360 class View: loop_choices = [("No Loop", 1), ("Loop Current", 2), ("Loop All", 3)] current_track_index = 0 toggle_play_stop = itertools.cycle(["play", "stop"]) toggle_pause_unpause = itertools.cycle(["pause", "unpause"]) toggle_mute_unmute = itertools.cycle(["mute", "unmute"]) def __init__(self, root, model, player): self.root = root self.model = model self.player = player self.create_gui() self.root.protocol('WM_DELETE_WINDOW', self.close_player) self.root.bind("<>", self.seek_new_position) def create_gui(self): self.root.title(AUDIO_PLAYER_NAME) self.create_top_display() self.create_button_frame() self.create_list_box() self.create_bottom_frame() self.create_context_menu() def create_top_display(self): frame = tk.Frame(self.root) glass_frame_image = tk.PhotoImage(file='../icons/glass_frame.gif') self.canvas = tk.Canvas(frame, width=370, height=90) self.canvas.image = glass_frame_image self.canvas.grid(row=1) self.console = self.canvas.create_image( 0, 10, anchor=tk.NW, image=glass_frame_image) self.clock = self.canvas.create_text(125, 68, anchor=tk.W, fill='#CBE4F6', text="00:00") self.track_length_text = self.canvas.create_text(167, 68, anchor=tk.W, fill='#CBE4F6', text="of 00:00") self.track_name = self.canvas.create_text(50, 35, anchor=tk.W, fill='#9CEDAC', text='\"Currently playing: none \"') self.seek_bar = Seekbar( frame, background="blue", width=SEEKBAR_WIDTH, height=10) self.seek_bar.grid(row=2, columnspan=10, sticky='ew', padx=5) frame.grid(row=1, pady=1, padx=0) def create_button_frame(self): frame = tk.Frame(self.root) previous_track_icon = tk.PhotoImage(file='../icons/previous_track.gif') previous_track_button = tk.Button( frame, image=previous_track_icon, borderwidth=0, padx=0, command=self.on_previous_track_button_clicked) previous_track_button.image = previous_track_icon previous_track_button.grid(row=3, column=1, sticky='w') rewind_icon = tk.PhotoImage(file='../icons/rewind.gif') rewind_button = tk.Button( frame, image=rewind_icon, borderwidth=0, padx=0, command=self.on_rewind_button_clicked) rewind_button.image = rewind_icon rewind_button.grid(row=3, column=2, sticky='w') self.play_icon = tk.PhotoImage(file='../icons/play.gif') self.stop_icon = tk.PhotoImage(file='../icons/stop.gif') self.play_stop_button = tk.Button( frame, image=self.play_icon, borderwidth=0, padx=0, command=self.on_play_stop_button_clicked) self.play_stop_button.image = self.play_icon self.play_stop_button.grid(row=3, column=3) pause_icon = tk.PhotoImage(file='../icons/pause.gif') pause_unpause_button = tk.Button( frame, image=pause_icon, borderwidth=0, padx=0, command=self.on_pause_unpause_button_clicked) pause_unpause_button.image = pause_icon pause_unpause_button.grid(row=3, column=4) fast_forward_icon = tk.PhotoImage(file='../icons/fast_forward.gif') fast_forward_button = tk.Button( frame, image=fast_forward_icon, borderwidth=0, padx=0, command=self.on_fast_forward_button_clicked) fast_forward_button.image = fast_forward_icon fast_forward_button.grid(row=3, column=5) next_track_icon = tk.PhotoImage(file='../icons/next_track.gif') next_track_button = tk.Button( frame, image=next_track_icon, borderwidth=0, padx=0, command=self.on_next_track_button_clicked) next_track_button.image = next_track_icon next_track_button.grid(row=3, column=6) self.mute_icon = tk.PhotoImage(file='../icons/mute.gif') self.unmute_icon = tk.PhotoImage(file='../icons/unmute.gif') self.mute_unmute_button = tk.Button( frame, image=self.unmute_icon, text='unmute', borderwidth=0, padx=0, command=self.on_mute_unmute_button_clicked) self.mute_unmute_button.image = self.unmute_icon self.mute_unmute_button.grid(row=3, column=7) self.volume_scale = tkinter.ttk.Scale( frame, from_=0.0, to=1.0, command=self.on_volume_scale_changed) self.volume_scale.set(0.6) self.volume_scale.grid(row=3, column=8, padx=5) frame.grid(row=3, columnspan=5, sticky='w', pady=4, padx=5) def create_list_box(self): frame = tk.Frame(self.root) self.list_box = tk.Listbox(frame, activestyle='none', cursor='hand2', bg='#1C3D7D', fg='#A0B9E9', selectmode=tk.EXTENDED, height=10) self.list_box.pack(side=tk.LEFT, fill=tk.BOTH, expand=1) self.list_box.bind( "", self.on_play_list_double_clicked) self.list_box.bind("", self.show_context_menu) scroll_bar = tk.Scrollbar(frame) scroll_bar.pack(side=tk.RIGHT, fill=tk.BOTH) self.list_box.config(yscrollcommand=scroll_bar.set) scroll_bar.config(command=self.list_box.yview) frame.grid(row=4, padx=5, columnspan=10, sticky='ew') def create_bottom_frame(self): frame = tk.Frame(self.root) add_file_icon = tk.PhotoImage(file='../icons/add_file.gif') add_file_button = tk.Button(frame, image=add_file_icon, borderwidth=0, padx=0, text='Add File', command=self.on_add_file_button_clicked) add_file_button.image = add_file_icon add_file_button.grid(row=5, column=1) remove_selected_icon = tk.PhotoImage( file='../icons/delete_selected.gif') remove_selected_button = tk.Button( frame, image=remove_selected_icon, borderwidth=0, padx=0, text='Delete', command=self.on_remove_selected_button_clicked) remove_selected_button.image = remove_selected_icon remove_selected_button.grid(row=5, column=2) add_directory_icon = tk.PhotoImage(file='../icons/add_directory.gif') add_directory_button = tk.Button(frame, image=add_directory_icon, borderwidth=0, padx=0, text='Add Dir', command=self.on_add_directory_button_clicked) add_directory_button.image = add_directory_icon add_directory_button.grid(row=5, column=3) empty_play_list_icon = tk.PhotoImage( file='../icons/clear_play_list.gif') empty_play_list_button = tk.Button(frame, image=empty_play_list_icon, borderwidth=0, padx=0, text='Clear All', command=self.on_clear_play_list_button_clicked) empty_play_list_button.image = empty_play_list_icon empty_play_list_button.grid(row=5, column=4) self.loop_value = tk.IntVar() self.loop_value.set(3) for txt, val in self.loop_choices: tk.Radiobutton(frame, text=txt, variable=self.loop_value, value=val).grid( row=5, column=4 + val, pady=3) frame.grid(row=5, sticky='w', padx=5) def create_context_menu(self): self.context_menu = tk.Menu(self.list_box, tearoff=0) self.context_menu.add_command( label="Delete", command=self.on_remove_selected_context_menu_clicked) def show_context_menu(self, event): self.context_menu.tk_popup(event.x_root, event.y_root) def on_add_file_button_clicked(self): self.add_audio_file() def on_remove_selected_button_clicked(self): self.remove_selected_files() def on_add_directory_button_clicked(self): self.add_all_audio_files_from_directory() def on_clear_play_list_button_clicked(self): self.clear_play_list() def on_remove_selected_context_menu_clicked(self): self.remove_selected_files() def on_play_stop_button_clicked(self): action = next(self.toggle_play_stop) if action == 'play': try: self.current_track_index = self.list_box.curselection()[0] except IndexError: self.current_track_index = 0 self.start_play() elif action == 'stop': self.stop_play() def on_pause_unpause_button_clicked(self): action = next(self.toggle_pause_unpause) if action == 'pause': self.player.pause() elif action == 'unpause': self.player.unpause() def on_mute_unmute_button_clicked(self): action = next(self.toggle_mute_unmute) if action == 'mute': self.volume_at_time_of_mute = self.player.volume self.player.mute() self.volume_scale.set(0) self.mute_unmute_button.config(image=self.mute_icon) elif action == 'unmute': self.player.unmute(self.volume_at_time_of_mute) self.volume_scale.set(self.volume_at_time_of_mute) self.mute_unmute_button.config(image=self.unmute_icon) def on_previous_track_button_clicked(self): self.play_previous_track() def on_rewind_button_clicked(self): self.player.rewind() def on_fast_forward_button_clicked(self): self.player.fast_forward() def on_next_track_button_clicked(self): self.play_next_track() def on_volume_scale_changed(self, value): self.player.volume = self.volume_scale.get() if self.volume_scale.get() == 0.0: self.mute_unmute_button.config(image=self.mute_icon) else: self.mute_unmute_button.config(image=self.unmute_icon) def play_previous_track(self): self.current_track_index = max(0, self.current_track_index - 1) self.start_play() def play_next_track(self): self.current_track_index = min( self.list_box.size() - 1, self.current_track_index + 1) self.start_play() def start_play(self): try: audio_file = self.model.get_file_to_play(self.current_track_index) except IndexError: return self.play_stop_button.config(image=self.stop_icon) self.player.play_media(audio_file) self.current_track_position = 0 self.manage_one_time_track_updates_on_play_start() self.manage_periodic_updates_during_play() def stop_play(self): self.play_stop_button.config(image=self.play_icon) self.player.stop() def on_play_list_double_clicked(self, event=None): self.current_track_index = int(self.list_box.curselection()[0]) self.start_play() def add_audio_file(self): audio_file = tkinter.filedialog.askopenfilename(filetypes=[( 'All supported', '.mp3 .wav'), ('.mp3 files', '.mp3'), ('.wav files', '.wav')]) if audio_file: self.model.add_to_play_list(audio_file) file_path, file_name = os.path.split(audio_file) self.list_box.insert(tk.END, file_name) def remove_selected_files(self): try: selected_indexes = self.list_box.curselection() for index in reversed(selected_indexes): self.list_box.delete(index) self.model.remove_item_from_play_list_at_index(index) except IndexError: pass def add_all_audio_files_from_directory(self): directory_path = tkinter.filedialog.askdirectory() if not directory_path: return audio_files_in_directory = self.get_all_audio_file_from_directory( directory_path) for audio_file in audio_files_in_directory: self.model.add_to_play_list(audio_file) file_path, file_name = os.path.split(audio_file) self.list_box.insert(tk.END, file_name) def get_all_audio_file_from_directory(self, directory_path): audio_files_in_directory = [] for (dirpath, dirnames, filenames) in os.walk(directory_path): for audio_file in filenames: if audio_file.endswith(".mp3") or audio_file.endswith(".wav"): audio_files_in_directory.append(dirpath + "/" + audio_file) return audio_files_in_directory def clear_play_list(self): self.model.clear_play_list() self.list_box.delete(0, tk.END) def manage_one_time_track_updates_on_play_start(self): self.update_now_playing_text() self.display_track_duration() def update_now_playing_text(self): current_track = self.model.play_list[self.current_track_index] file_path, file_name = os.path.split(current_track) truncated_track_name = truncate_text(file_name, 40) self.canvas.itemconfig(self.track_name, text=truncated_track_name) def display_track_duration(self): self.track_length = self.player.track_length minutes, seconds = get_time_in_minute_seconds(self.track_length) track_length_string = 'of {0:02d}:{1:02d}'.format(minutes, seconds) self.canvas.itemconfig( self.track_length_text, text=track_length_string) def update_clock(self): self.elapsed_play_duration = self.player.elapsed_play_duration minutes, seconds = get_time_in_minute_seconds( self.elapsed_play_duration) current_time_string = '{0:02d}:{1:02d}'.format(minutes, seconds) self.canvas.itemconfig(self.clock, text=current_time_string) def update_seek_bar(self): seek_bar_position = SEEKBAR_WIDTH * \ self.player.elapsed_play_duration / self.track_length self.seek_bar.slide_to_position(seek_bar_position) def seek_new_position(self, event=None): time = self.player.track_length * event.x / SEEKBAR_WIDTH self.player.seek(time) def manage_periodic_updates_during_play(self): self.update_clock() self.update_seek_bar() if not self.player.is_playing(): if self.not_to_loop(): return self.root.after(1000, self.manage_periodic_updates_during_play) def not_to_loop(self): selected_loop_choice = self.loop_value.get() if selected_loop_choice == 1: return True elif selected_loop_choice == 2: self.start_play() return False elif selected_loop_choice == 3: self.play_next_track() return True def close_player(self): self.player.stop() self.root.destroy() if __name__ == '__main__': root = tk.Tk() root.resizable(width=False, height=False) model = model.Model() player = player.Player() app = View(root, model, player) root.mainloop() ================================================ FILE: Chapter 05/5.09/helpers.py ================================================ """ Code illustration: 5.09 @Tkinter GUI Application Development Blueprints """ def get_time_in_minute_seconds(time_in_seconds): minutes = int(time_in_seconds / 60) seconds = int(time_in_seconds % 60) return (minutes, seconds) def truncate_text(text, truncate_length): truncate_length_plus_two = truncate_length + 2 # account for double dots return (text[:truncate_length_plus_two] + '..') if len(text) > truncate_length else text ================================================ FILE: Chapter 05/5.09/model.py ================================================ """ Code illustration: 5.09 @Tkinter GUI Application Development Blueprints """ class Model: def __init__(self): self.__play_list = [] @property def play_list(self): return self.__play_list def get_file_to_play(self, file_index): return self.__play_list[file_index] def clear_play_list(self): self.__play_list.clear() def add_to_play_list(self, file_name): self.__play_list.append(file_name) def remove_item_from_play_list_at_index(self, index): del self.__play_list[index] ================================================ FILE: Chapter 05/5.09/player.py ================================================ """ Code illustration: 5.09 @Tkinter GUI Application Development Blueprints """ import pyglet FORWARD_REWIND_JUMP_TIME = 20 class Player: def __init__(self): self.player = pyglet.media.Player() self.player.volume = 0.6 def play_media(self, audio_file): self.reset_player() self.player = pyglet.media.Player() self.source = pyglet.media.load(audio_file) self.player.queue(self.source) self.player.play() def reset_player(self): self.player.pause() self.player.delete() def is_playing(self): try: elapsed_time = int(self.player.time) is_playing = elapsed_time < int(self.track_length) except: is_playing = False return is_playing def seek(self, time): try: self.player.seek(time) except AttributeError: pass @property def track_length(self): try: return self.source.duration except AttributeError: return 0 @property def volume(self): return self.player.volume @property def elapsed_play_duration(self): return self.player.time @volume.setter def volume(self, volume): self.player.volume = volume def unpause(self): self.player.play() def pause(self): self.player.pause() def stop(self): self.reset_player() def mute(self): self.player.volume = 0.0 def unmute(self, new_volume_level): self.player.volume = new_volume_level def fast_forward(self): time = self.player.time + FORWARD_REWIND_JUMP_TIME try: if self.source.duration > time: self.seek(time) else: self.seek(self.source.duration) except AttributeError: pass def rewind(self): time = self.player.time - FORWARD_REWIND_JUMP_TIME try: self.seek(time) except: self.seek(0) ================================================ FILE: Chapter 05/5.09/pygleterror.py ================================================ import pyglet audio_file = 'test.mp3' player = pyglet.media.Player() source = pyglet.media.load(audio_file) player.queue(source) player.play() ================================================ FILE: Chapter 05/5.09/seekbar.py ================================================ """ Code illustration: 5.09 @Tkinter GUI Application Development Blueprints """ import tkinter as tk class Seekbar(tk.Canvas): def __init__(self, parent, **options): tk.Canvas.__init__(self, parent, options) self.parent = parent self.width = options['width'] self.red_rectangle = self.create_rectangle(0, 0, 0, 0, fill="red") self.seekbar_knob_image = tk.PhotoImage(file="../icons/seekbar_knob.gif") self.seekbar_knob = self.create_image( 0, 0, image=self.seekbar_knob_image) self.bind_mouse_button() def bind_mouse_button(self): self.bind('', self.on_seekbar_clicked) self.bind('', self.on_seekbar_clicked) self.tag_bind( self.red_rectangle, '', self.on_seekbar_clicked) self.tag_bind( self.seekbar_knob, '', self.on_seekbar_clicked) def on_seekbar_clicked(self, event=None): if event.x > 0 and event.x < self.width: self.slide_to_position(event.x) def slide_to_position(self, new_position): self.coords(self.red_rectangle, 0, 0, new_position, new_position) self.coords(self.seekbar_knob, new_position, 0) self.event_generate("<>", x=new_position) class TestSeekBar(): def __init__(self): root = tk.Tk() root.bind("<>", self.seek_new_position) frame = tk.Frame(root) frame.grid(row=1, pady=10, padx=10) c = Seekbar( frame, background="blue", width=360, height=10) c.grid(row=2, columnspan=10, sticky='ew', padx=5) root.mainloop() def seek_new_position(self, event): print("Dragged to x:", event.x) if __name__ == '__main__': TestSeekBar() ================================================ FILE: Chapter 05/5.09/view.py ================================================ """ Code illustration: 5.09 new imports here: import Pmw methods modifed here: create_gui create_button_frame (added balloon tool tip to all buttons) create_bottom_frame (added baloon tool tip to all buttons) @Tkinter GUI Application Development Blueprints """ import tkinter as tk import tkinter.filedialog import tkinter.messagebox import tkinter.ttk import os import Pmw import model import player from seekbar import * from helpers import * import itertools AUDIO_PLAYER_NAME = "Achtung Baby" SEEKBAR_WIDTH = 360 class View: loop_choices = [("No Loop", 1), ("Loop Current", 2), ("Loop All", 3)] current_track_index = 0 toggle_play_stop = itertools.cycle(["play", "stop"]) toggle_pause_unpause = itertools.cycle(["pause", "unpause"]) toggle_mute_unmute = itertools.cycle(["mute", "unmute"]) def __init__(self, root, model, player): self.root = root self.model = model self.player = player self.create_gui() self.root.protocol('WM_DELETE_WINDOW', self.close_player) self.root.bind("<>", self.seek_new_position) def create_gui(self): self.root.title(AUDIO_PLAYER_NAME) self.balloon = Pmw.Balloon(self.root) self.create_top_display() self.create_button_frame() self.create_list_box() self.create_bottom_frame() self.create_context_menu() def create_top_display(self): frame = tk.Frame(self.root) glass_frame_image = tk.PhotoImage(file='../icons/glass_frame.gif') self.canvas = tk.Canvas(frame, width=370, height=90) self.canvas.image = glass_frame_image self.canvas.grid(row=1) self.console = self.canvas.create_image( 0, 10, anchor=tk.NW, image=glass_frame_image) self.clock = self.canvas.create_text(125, 68, anchor=tk.W, fill='#CBE4F6', text="00:00") self.track_length_text = self.canvas.create_text(167, 68, anchor=tk.W, fill='#CBE4F6', text="of 00:00") self.track_name = self.canvas.create_text(50, 35, anchor=tk.W, fill='#9CEDAC', text='\"Currently playing: none \"') self.seek_bar = Seekbar( frame, background="blue", width=SEEKBAR_WIDTH, height=10) self.seek_bar.grid(row=2, columnspan=10, sticky='ew', padx=5) frame.grid(row=1, pady=1, padx=0) def create_button_frame(self): frame = tk.Frame(self.root) previous_track_icon = tk.PhotoImage(file='../icons/previous_track.gif') previous_track_button = tk.Button( frame, image=previous_track_icon, borderwidth=0, padx=0, command=self.on_previous_track_button_clicked) previous_track_button.image = previous_track_icon previous_track_button.grid(row=3, column=1, sticky='w') self.balloon.bind(previous_track_button, 'Previous Song') rewind_icon = tk.PhotoImage(file='../icons/rewind.gif') rewind_button = tk.Button( frame, image=rewind_icon, borderwidth=0, padx=0, command=self.on_rewind_button_clicked) rewind_button.image = rewind_icon rewind_button.grid(row=3, column=2, sticky='w') self.balloon.bind(rewind_button, 'Rewind') self.play_icon = tk.PhotoImage(file='../icons/play.gif') self.stop_icon = tk.PhotoImage(file='../icons/stop.gif') self.play_stop_button = tk.Button( frame, image=self.play_icon, borderwidth=0, padx=0, command=self.on_play_stop_button_clicked) self.play_stop_button.image = self.play_icon self.play_stop_button.grid(row=3, column=3) self.balloon.bind(self.play_stop_button, 'Play/ Stop Song') pause_icon = tk.PhotoImage(file='../icons/pause.gif') pause_unpause_button = tk.Button( frame, image=pause_icon, borderwidth=0, padx=0, command=self.on_pause_unpause_button_clicked) pause_unpause_button.image = pause_icon pause_unpause_button.grid(row=3, column=4) self.balloon.bind(pause_unpause_button, 'Pause') fast_forward_icon = tk.PhotoImage(file='../icons/fast_forward.gif') fast_forward_button = tk.Button( frame, image=fast_forward_icon, borderwidth=0, padx=0, command=self.on_fast_forward_button_clicked) fast_forward_button.image = fast_forward_icon fast_forward_button.grid(row=3, column=5) self.balloon.bind(fast_forward_button, 'Fast Forward') next_track_icon = tk.PhotoImage(file='../icons/next_track.gif') next_track_button = tk.Button( frame, image=next_track_icon, borderwidth=0, padx=0, command=self.on_next_track_button_clicked) next_track_button.image = next_track_icon next_track_button.grid(row=3, column=6) self.balloon.bind(next_track_button, 'Next Track') self.mute_icon = tk.PhotoImage(file='../icons/mute.gif') self.unmute_icon = tk.PhotoImage(file='../icons/unmute.gif') self.mute_unmute_button = tk.Button( frame, image=self.unmute_icon, text='unmute', borderwidth=0, padx=0, command=self.on_mute_unmute_button_clicked) self.mute_unmute_button.image = self.unmute_icon self.mute_unmute_button.grid(row=3, column=7) self.balloon.bind(self.mute_unmute_button, 'Mute/Unmute') self.volume_scale = tkinter.ttk.Scale( frame, from_=0.0, to=1.0, command=self.on_volume_scale_changed) self.volume_scale.set(0.6) self.volume_scale.grid(row=3, column=8, padx=5) frame.grid(row=3, columnspan=5, sticky='w', pady=4, padx=5) def create_list_box(self): frame = tk.Frame(self.root) self.list_box = tk.Listbox(frame, activestyle='none', cursor='hand2', bg='#1C3D7D', fg='#A0B9E9', selectmode=tk.EXTENDED, height=10) self.list_box.pack(side=tk.LEFT, fill=tk.BOTH, expand=1) self.list_box.bind( "", self.on_play_list_double_clicked) self.list_box.bind("", self.show_context_menu) scroll_bar = tk.Scrollbar(frame) scroll_bar.pack(side=tk.RIGHT, fill=tk.BOTH) self.list_box.config(yscrollcommand=scroll_bar.set) scroll_bar.config(command=self.list_box.yview) frame.grid(row=4, padx=5, columnspan=10, sticky='ew') def create_bottom_frame(self): frame = tk.Frame(self.root) add_file_icon = tk.PhotoImage(file='../icons/add_file.gif') add_file_button = tk.Button(frame, image=add_file_icon, borderwidth=0, padx=0, text='Add File', command=self.on_add_file_button_clicked) add_file_button.image = add_file_icon add_file_button.grid(row=5, column=1) self.balloon.bind(add_file_button, 'Add New File') remove_selected_icon = tk.PhotoImage( file='../icons/delete_selected.gif') remove_selected_button = tk.Button( frame, image=remove_selected_icon, borderwidth=0, padx=0, text='Delete', command=self.on_remove_selected_button_clicked) remove_selected_button.image = remove_selected_icon remove_selected_button.grid(row=5, column=2) self.balloon.bind(remove_selected_button, 'Remove Selected') add_directory_icon = tk.PhotoImage(file='../icons/add_directory.gif') add_directory_button = tk.Button(frame, image=add_directory_icon, borderwidth=0, padx=0, text='Add Dir', command=self.on_add_directory_button_clicked) add_directory_button.image = add_directory_icon add_directory_button.grid(row=5, column=3) self.balloon.bind(add_directory_button, 'Add Directory') empty_play_list_icon = tk.PhotoImage( file='../icons/clear_play_list.gif') empty_play_list_button = tk.Button(frame, image=empty_play_list_icon, borderwidth=0, padx=0, text='Clear All', command=self.on_clear_play_list_button_clicked) empty_play_list_button.image = empty_play_list_icon empty_play_list_button.grid(row=5, column=4) self.balloon.bind(empty_play_list_button, 'Empty play list') self.loop_value = tk.IntVar() self.loop_value.set(3) for txt, val in self.loop_choices: tk.Radiobutton(frame, text=txt, variable=self.loop_value, value=val).grid( row=5, column=4 + val, pady=3) frame.grid(row=5, sticky='w', padx=5) def create_context_menu(self): self.context_menu = tk.Menu(self.list_box, tearoff=0) self.context_menu.add_command( label="Delete", command=self.on_remove_selected_context_menu_clicked) def show_context_menu(self, event): self.context_menu.tk_popup(event.x_root, event.y_root) def on_add_file_button_clicked(self): self.add_audio_file() def on_remove_selected_button_clicked(self): self.remove_selected_files() def on_add_directory_button_clicked(self): self.add_all_audio_files_from_directory() def on_clear_play_list_button_clicked(self): self.clear_play_list() def on_remove_selected_context_menu_clicked(self): self.remove_selected_files() def on_play_stop_button_clicked(self): action = next(self.toggle_play_stop) if action == 'play': try: self.current_track_index = self.list_box.curselection()[0] except IndexError: self.current_track_index = 0 self.start_play() elif action == 'stop': self.stop_play() def on_pause_unpause_button_clicked(self): action = next(self.toggle_pause_unpause) if action == 'pause': self.player.pause() elif action == 'unpause': self.player.unpause() def on_mute_unmute_button_clicked(self): action = next(self.toggle_mute_unmute) if action == 'mute': self.volume_at_time_of_mute = self.player.volume self.player.mute() self.volume_scale.set(0) self.mute_unmute_button.config(image=self.mute_icon) elif action == 'unmute': self.player.unmute(self.volume_at_time_of_mute) self.volume_scale.set(self.volume_at_time_of_mute) self.mute_unmute_button.config(image=self.unmute_icon) def on_previous_track_button_clicked(self): self.play_previous_track() def on_rewind_button_clicked(self): self.player.rewind() def on_fast_forward_button_clicked(self): self.player.fast_forward() def on_next_track_button_clicked(self): self.play_next_track() def on_volume_scale_changed(self, value): self.player.volume = self.volume_scale.get() if self.volume_scale.get() == 0.0: self.mute_unmute_button.config(image=self.mute_icon) else: self.mute_unmute_button.config(image=self.unmute_icon) def play_previous_track(self): self.current_track_index = max(0, self.current_track_index - 1) self.start_play() def play_next_track(self): self.current_track_index = min( self.list_box.size() - 1, self.current_track_index + 1) self.start_play() def start_play(self): try: audio_file = self.model.get_file_to_play(self.current_track_index) except IndexError: return self.play_stop_button.config(image=self.stop_icon) self.player.play_media(audio_file) self.current_track_position = 0 self.manage_one_time_track_updates_on_play_start() self.manage_periodic_updates_during_play() def stop_play(self): self.play_stop_button.config(image=self.play_icon) self.player.stop() def on_play_list_double_clicked(self, event=None): self.current_track_index = int(self.list_box.curselection()[0]) self.start_play() def add_audio_file(self): audio_file = tkinter.filedialog.askopenfilename(filetypes=[( 'All supported', '.mp3 .wav'), ('.mp3 files', '.mp3'), ('.wav files', '.wav')]) if audio_file: self.model.add_to_play_list(audio_file) file_path, file_name = os.path.split(audio_file) self.list_box.insert(tk.END, file_name) def remove_selected_files(self): try: selected_indexes = self.list_box.curselection() for index in reversed(selected_indexes): self.list_box.delete(index) self.model.remove_item_from_play_list_at_index(index) except IndexError: pass def add_all_audio_files_from_directory(self): directory_path = tkinter.filedialog.askdirectory() if not directory_path: return audio_files_in_directory = self.get_all_audio_file_from_directory( directory_path) for audio_file in audio_files_in_directory: self.model.add_to_play_list(audio_file) file_path, file_name = os.path.split(audio_file) self.list_box.insert(tk.END, file_name) def get_all_audio_file_from_directory(self, directory_path): audio_files_in_directory = [] for (dirpath, dirnames, filenames) in os.walk(directory_path): for audio_file in filenames: if audio_file.endswith(".mp3") or audio_file.endswith(".wav"): audio_files_in_directory.append(dirpath + "/" + audio_file) return audio_files_in_directory def clear_play_list(self): self.model.clear_play_list() self.list_box.delete(0, tk.END) def manage_one_time_track_updates_on_play_start(self): self.update_now_playing_text() self.display_track_duration() def update_now_playing_text(self): current_track = self.model.play_list[self.current_track_index] file_path, file_name = os.path.split(current_track) truncated_track_name = truncate_text(file_name, 40) self.canvas.itemconfig(self.track_name, text=truncated_track_name) def display_track_duration(self): self.track_length = self.player.track_length print(self.track_length) minutes, seconds = get_time_in_minute_seconds(self.track_length) track_length_string = 'of {0:02d}:{1:02d}'.format(minutes, seconds) self.canvas.itemconfig( self.track_length_text, text=track_length_string) def update_clock(self): self.elapsed_play_duration = self.player.elapsed_play_duration minutes, seconds = get_time_in_minute_seconds( self.elapsed_play_duration) current_time_string = '{0:02d}:{1:02d}'.format(minutes, seconds) self.canvas.itemconfig(self.clock, text=current_time_string) def update_seek_bar(self): seek_bar_position = SEEKBAR_WIDTH * \ self.player.elapsed_play_duration / self.track_length self.seek_bar.slide_to_position(seek_bar_position) def seek_new_position(self, event=None): time = self.player.track_length * event.x / SEEKBAR_WIDTH self.player.seek(time) def manage_periodic_updates_during_play(self): self.update_clock() self.update_seek_bar() if not self.player.is_playing(): if self.not_to_loop(): return self.root.after(1000, self.manage_periodic_updates_during_play) def not_to_loop(self): selected_loop_choice = self.loop_value.get() if selected_loop_choice == 1: return True elif selected_loop_choice == 2: self.start_play() return False elif selected_loop_choice == 3: self.play_next_track() return True def close_player(self): self.player.stop() self.root.destroy() if __name__ == '__main__': root = tk.Tk() root.resizable(width=False, height=False) model = model.Model() player = player.Player() app = View(root, model, player) root.mainloop() ================================================ FILE: Chapter 05/readme.txt ================================================ ==================================================================== Readme Tkinter GUI Application Development Blueprints Chapter 5: Audio Player ==================================================================== Description: 5.01: Program Structure & Broad View Skeleton 5.02: Deciding the Data Structure & Player class 5.03: Adding/ Removing items to the Playlist 5.04: Playing Audio and Adding Audio Controls 5.05: Creating the Seekbar (Extending Tkinter Widgets) 5.06: One Time Updates during audio playback 5.07: Managing Continuous Updates (Animation Logic) 5.08: Looping Over Tracks 5.09: Adding ToolTip (Exploring Tkinter extensions) Directory 'icons' contains all images used in the program @author Bhaskar Chaudhary ================================================ FILE: Chapter 06/6.01.py ================================================ """ Code illustration: 6.01 @ Tkinter GUI Application Development Blueprints """ import tkinter as tk import framework class PaintApplication(framework.Framework): def __init__(self, root): super().__init__(root) self.create_gui() def create_gui(self): self.create_menu() self.create_top_bar() self.create_tool_bar() self.create_drawing_canvas() self.bind_menu_accelrator_keys() def create_menu(self): self.menubar = tk.Menu(self.root) menu_definitions = ( 'File- &New/Ctrl+N/self.on_new_file_menu_clicked, Save/Ctrl+S/self.on_save_menu_clicked, SaveAs/ /self.on_save_as_menu_clicked, sep, Exit/Alt+F4/self.on_close_menu_clicked', 'Edit- Undo/Ctrl+Z/self.on_undo_menu_clicked, sep', 'View- Zoom in//self.on_canvas_zoom_in_menu_clicked,Zoom Out//self.on_canvas_zoom_out_menu_clicked', 'About- About/F1/self.on_about_menu_clicked' ) self.build_menu(menu_definitions) # here's our framework in action def create_top_bar(self): self.top_bar = tk.Frame(self.root, height=25, relief="raised") self.top_bar.pack(fill="x", side="top", pady=2) def create_tool_bar(self): self.tool_bar = tk.Frame(self.root, relief="raised", width=50) self.tool_bar.pack(fill="y", side="left", pady=3) def create_drawing_canvas(self): self.canvas_frame = tk.Frame(self.root, width=900, height=900) self.canvas_frame.pack(side="right", expand="yes", fill="both") self.canvas = tk.Canvas(self.canvas_frame, background="white", width=500, height=500, scrollregion=(0, 0, 800, 800)) self.create_scroll_bar() self.canvas.pack(side=tk.RIGHT, expand=tk.YES, fill=tk.BOTH) def create_scroll_bar(self): x_scroll = tk.Scrollbar(self.canvas_frame, orient="horizontal") x_scroll.pack(side="bottom", fill="x") x_scroll.config(command=self.canvas.xview) y_scroll = tk.Scrollbar(self.canvas_frame, orient="vertical") y_scroll.pack(side="right", fill="y") y_scroll.config(command=self.canvas.yview) self.canvas.config( xscrollcommand=x_scroll.set, yscrollcommand=y_scroll.set) def bind_menu_accelrator_keys(self): self.root.bind('', self.on_about_menu_clicked) self.root.bind('', self.on_new_file_menu_clicked) self.root.bind('', self.on_new_file_menu_clicked) self.root.bind('', self.on_save_menu_clicked) self.root.bind('', self.on_save_menu_clicked) self.root.bind('', self.on_undo_menu_clicked) self.root.bind('', self.on_undo_menu_clicked) def on_new_file_menu_clicked(self, event=None): pass def on_save_menu_clicked(self, event=None): pass def on_save_as_menu_clicked(self): pass def on_canvas_zoom_out_menu_clicked(self): pass def on_canvas_zoom_in_menu_clicked(self): pass def on_close_menu_clicked(self): pass def on_undo_menu_clicked(self, event=None): pass def on_about_menu_clicked(self, event=None): pass if __name__ == '__main__': root = tk.Tk() app = PaintApplication(root) root.mainloop() ================================================ FILE: Chapter 06/6.02.py ================================================ """ Code illustration: 6.02 New attributes defined here: start_x, start_y end_x, end_y Methods modified here: __init__ (added a call to bind_mouse) New methods defined here: bind_mouse() on_mouse_button_pressed() on_mouse_button_pressed_motion() on_mouse_button_released() on_mouse_unpressed_motion() @ Tkinter GUI Application Development Blueprints """ import tkinter as tk import framework class PaintApplication(framework.Framework): start_x, start_y = 0, 0 end_x, end_y = 0, 0 def bind_mouse(self): self.canvas.bind("", self.on_mouse_button_pressed) self.canvas.bind( "", self.on_mouse_button_pressed_motion) self.canvas.bind( "", self.on_mouse_button_released) self.canvas.bind("", self.on_mouse_unpressed_motion) def on_mouse_button_pressed(self, event): self.start_x = self.end_x = self.canvas.canvasx(event.x) self.start_y = self.end_y = self.canvas.canvasy(event.y) print("start_x, start_y = ", self.start_x, self.start_y) def on_mouse_button_pressed_motion(self, event): self.end_x = self.canvas.canvasx(event.x) self.end_y = self.canvas.canvasy(event.y) def on_mouse_button_released(self, event): self.end_x = self.canvas.canvasx(event.x) self.end_y = self.canvas.canvasy(event.y) print("end_x, end_y = ", self.end_x, self.end_y) def on_mouse_unpressed_motion(self, event): pass def __init__(self, root): super().__init__(root) self.create_gui() self.bind_mouse() def create_gui(self): self.create_menu() self.create_top_bar() self.create_tool_bar() self.create_drawing_canvas() self.bind_menu_accelrator_keys() def create_menu(self): self.menubar = tk.Menu(self.root) menu_definitions = ( 'File- &New/Ctrl+N/self.on_new_file_menu_clicked, Save/Ctrl+S/self.on_save_menu_clicked, SaveAs/ /self.on_save_as_menu_clicked, sep, Exit/Alt+F4/self.on_close_menu_clicked', 'Edit- Undo/Ctrl+Z/self.on_undo_menu_clicked, sep', 'View- Zoom in//self.on_canvas_zoom_in_menu_clicked,Zoom Out//self.on_canvas_zoom_out_menu_clicked', 'About- About/F1/self.on_about_menu_clicked' ) self.build_menu(menu_definitions) def create_top_bar(self): self.top_bar = tk.Frame(self.root, height=25, relief="raised") self.top_bar.pack(fill="x", side="top", pady=2) def create_tool_bar(self): self.tool_bar = tk.Frame(self.root, relief="raised", width=50) self.tool_bar.pack(fill="y", side="left", pady=3) def create_drawing_canvas(self): self.canvas_frame = tk.Frame(self.root, width=900, height=900) self.canvas_frame.pack(side="right", expand="yes", fill="both") self.canvas = tk.Canvas(self.canvas_frame, background="white", width=500, height=500, scrollregion=(0, 0, 800, 800)) self.create_scroll_bar() self.canvas.pack(side=tk.RIGHT, expand=tk.YES, fill=tk.BOTH) def create_scroll_bar(self): x_scroll = tk.Scrollbar(self.canvas_frame, orient="horizontal") x_scroll.pack(side="bottom", fill="x") x_scroll.config(command=self.canvas.xview) y_scroll = tk.Scrollbar(self.canvas_frame, orient="vertical") y_scroll.pack(side="right", fill="y") y_scroll.config(command=self.canvas.yview) self.canvas.config( xscrollcommand=x_scroll.set, yscrollcommand=y_scroll.set) def bind_menu_accelrator_keys(self): self.root.bind('', self.on_about_menu_clicked) self.root.bind('', self.on_new_file_menu_clicked) self.root.bind('', self.on_new_file_menu_clicked) self.root.bind('', self.on_save_menu_clicked) self.root.bind('', self.on_save_menu_clicked) self.root.bind('', self.on_undo_menu_clicked) self.root.bind('', self.on_undo_menu_clicked) def on_new_file_menu_clicked(self, event=None): pass def on_save_menu_clicked(self, event=None): pass def on_save_as_menu_clicked(self): pass def on_canvas_zoom_out_menu_clicked(self): pass def on_canvas_zoom_in_menu_clicked(self): pass def on_close_menu_clicked(self): pass def on_undo_menu_clicked(self, event=None): pass def on_about_menu_clicked(self, event=None): pass if __name__ == '__main__': root = tk.Tk() app = PaintApplication(root) root.mainloop() ================================================ FILE: Chapter 06/6.03.py ================================================ """ Code illustration: 6.03 New methods defined here: create_tool_bar_buttons() on_tool_bar_button_clicked(button_index) display_options_in_the_top_bar(function_name) remove_options_from_top_bar() Methods modified here: create_gui() - added a call to create_tool_bar_buttons() @ Tkinter GUI Application Development Blueprints """ import tkinter as tk import framework class PaintApplication(framework.Framework): start_x, start_y = 0, 0 end_x, end_y = 0, 0 tool_bar_functions = ( "draw_line", "draw_oval", "draw_rectangle", "draw_arc", "draw_triangle", "draw_star", "draw_irregular_line", "draw_super_shape", "draw_text", "delete_item", "fill_item", "duplicate_item", "move_to_top", "drag_item", "enlarge_item_size", "reduce_item_size" ) selected_tool_bar_function = tool_bar_functions[0] def create_tool_bar_buttons(self): for index, name in enumerate(self.tool_bar_functions): icon = tk.PhotoImage(file='icons/' + name + '.gif') self.button = tk.Button( self.tool_bar, image=icon, command=lambda index=index: self.on_tool_bar_button_clicked(index)) self.button.grid( row=index // 2, column=1 + index % 2, sticky='nsew') self.button.image = icon def on_tool_bar_button_clicked(self, button_index): self.selected_tool_bar_function = self.tool_bar_functions[button_index] self.remove_options_from_top_bar() self.display_options_in_the_top_bar() def display_options_in_the_top_bar(self): self.show_selected_tool_icon_in_top_bar( self.selected_tool_bar_function) def remove_options_from_top_bar(self): for child in self.top_bar.winfo_children(): child.destroy() def show_selected_tool_icon_in_top_bar(self, function_name): display_name = function_name.replace("_", " ").capitalize() + ":" tk.Label(self.top_bar, text=display_name).pack(side="left") photo = tk.PhotoImage( file='icons/' + function_name + '.gif') label = tk.Label(self.top_bar, image=photo) label.image = photo label.pack(side="left") def bind_mouse(self): self.canvas.bind("", self.on_mouse_button_pressed) self.canvas.bind( "", self.on_mouse_button_pressed_motion) self.canvas.bind( "", self.on_mouse_button_released) self.canvas.bind("", self.on_mouse_unpressed_motion) def on_mouse_button_pressed(self, event): self.start_x = self.end_x = self.canvas.canvasx(event.x) self.start_y = self.end_y = self.canvas.canvasy(event.y) def on_mouse_button_pressed_motion(self, event): self.end_x = self.canvas.canvasx(event.x) self.end_y = self.canvas.canvasy(event.y) def on_mouse_button_released(self, event): self.end_x = self.canvas.canvasx(event.x) self.end_y = self.canvas.canvasy(event.y) def on_mouse_unpressed_motion(self, event): pass def __init__(self, root): super().__init__(root) self.create_gui() self.bind_mouse() def create_gui(self): self.create_menu() self.create_top_bar() self.create_tool_bar() self.create_tool_bar_buttons() self.create_drawing_canvas() self.bind_menu_accelrator_keys() def create_menu(self): self.menubar = tk.Menu(self.root) menu_definitions = ( 'File- &New/Ctrl+N/self.on_new_file_menu_clicked, Save/Ctrl+S/self.on_save_menu_clicked, SaveAs/ /self.on_save_as_menu_clicked, sep, Exit/Alt+F4/self.on_close_menu_clicked', 'Edit- Undo/Ctrl+Z/self.on_undo_menu_clicked, sep', 'View- Zoom in//self.on_canvas_zoom_in_menu_clicked,Zoom Out//self.on_canvas_zoom_out_menu_clicked', 'About- About/F1/self.on_about_menu_clicked' ) self.build_menu(menu_definitions) def create_top_bar(self): self.top_bar = tk.Frame(self.root, height=25, relief="raised") self.top_bar.pack(fill="x", side="top", pady=2) def create_tool_bar(self): self.tool_bar = tk.Frame(self.root, relief="raised", width=50) self.tool_bar.pack(fill="y", side="left", pady=3) def create_drawing_canvas(self): self.canvas_frame = tk.Frame(self.root, width=900, height=900) self.canvas_frame.pack(side="right", expand="yes", fill="both") self.canvas = tk.Canvas(self.canvas_frame, background="white", width=500, height=500, scrollregion=(0, 0, 800, 800)) self.create_scroll_bar() self.canvas.pack(side="right", expand=tk.YES, fill=tk.BOTH) def create_scroll_bar(self): x_scroll = tk.Scrollbar(self.canvas_frame, orient="horizontal") x_scroll.pack(side="bottom", fill="x") x_scroll.config(command=self.canvas.xview) y_scroll = tk.Scrollbar(self.canvas_frame, orient="vertical") y_scroll.pack(side="right", fill="y") y_scroll.config(command=self.canvas.yview) self.canvas.config( xscrollcommand=x_scroll.set, yscrollcommand=y_scroll.set) def bind_menu_accelrator_keys(self): self.root.bind('', self.on_about_menu_clicked) self.root.bind('', self.on_new_file_menu_clicked) self.root.bind('', self.on_new_file_menu_clicked) self.root.bind('', self.on_save_menu_clicked) self.root.bind('', self.on_save_menu_clicked) self.root.bind('', self.on_undo_menu_clicked) self.root.bind('', self.on_undo_menu_clicked) def on_new_file_menu_clicked(self, event=None): pass def on_save_menu_clicked(self, event=None): pass def on_save_as_menu_clicked(self): pass def on_canvas_zoom_out_menu_clicked(self): pass def on_canvas_zoom_in_menu_clicked(self): pass def on_close_menu_clicked(self): pass def on_undo_menu_clicked(self, event=None): pass def on_about_menu_clicked(self, event=None): pass if __name__ == '__main__': root = tk.Tk() app = PaintApplication(root) root.mainloop() ================================================ FILE: Chapter 06/6.04.py ================================================ """ Code illustration: 6.04 New module import here: math cmath New attributes added here: current_item fill outline width arrow dash New methods defined here: execute_selected_method() draw_line() draw_oval() draw_rectangle() draw_arc() draw_triangle() draw_star() Methods modifed here: on_mouse_button_pressed() : added call to execute_selected_method() on_mouse_button_pressed_motion(): added call to execute_selected_method() and added line to delete the last drawn item. @ Tkinter GUI Application Development Blueprints """ import math import cmath import tkinter as tk import framework class PaintApplication(framework.Framework): start_x, start_y = 0, 0 end_x, end_y = 0, 0 current_item = None fill = "red" outline = "red" width = 2.0 number_of_spokes = 5 arrow = None dash = None tool_bar_functions = ( "draw_line", "draw_oval", "draw_rectangle", "draw_arc", "draw_triangle", "draw_star", "draw_irregular_line", "draw_super_shape", "draw_text", "delete_item", "fill_item", "duplicate_item", "move_to_top", "drag_item", "enlarge_item_size", "reduce_item_size" ) selected_tool_bar_function = tool_bar_functions[0] def function_not_defined(self): pass def execute_selected_method(self): self.current_item = None func = getattr( self, self.selected_tool_bar_function, self.function_not_defined) func() def draw_line(self): self.current_item = self.canvas.create_line( self.start_x, self.start_y, self.end_x, self.end_y, fill=self.fill, width=self.width, arrow=self.arrow, dash=self.dash) def draw_oval(self): self.current_item = self.canvas.create_oval( self.start_x, self.start_y, self.end_x, self.end_y, outline=self.outline, fill=self.fill, width=self.width) def draw_rectangle(self): self.current_item = self.canvas.create_rectangle( self.start_x, self.start_y, self.end_x, self.end_y, outline=self.outline, fill=self.fill, width=self.width) def draw_arc(self): self.current_item = self.canvas.create_arc( self.start_x, self.start_y, self.end_x, self.end_y, outline=self.outline, fill=self.fill, width=self.width) def draw_triangle(self): dx = self.end_x - self.start_x dy = self.end_y - self.start_y z = complex(dx, dy) radius, angle0 = cmath.polar(z) edges = 3 # nb of edges in circle points = list() for edge in range(edges): angle = angle0 + edge * (2 * math.pi) / edges points.append(self.start_x + radius * math.cos(angle)) # x coordinate points.append(self.start_y + radius * math.sin(angle)) # y coordinate self.current_item = self.canvas.create_polygon( points, outline=self.outline, fill=self.fill, width=self.width) def draw_star(self): dx = self.end_x - self.start_x dy = self.end_y - self.start_y z = complex(dx, dy) radius_out, angle0 = cmath.polar(z) radius_in = radius_out / 2 # this ratio is called the spoke ratio and can also be configured by user points = list() for edge in range(self.number_of_spokes): # outer circle angle angle = angle0 + edge * (2 * math.pi) / self.number_of_spokes # x coordinate (outer circle) points.append(self.start_x + radius_out * math.cos(angle)) # y coordinate (outer circle) points.append(self.start_y + radius_out * math.sin(angle)) # inner circle angle angle += math.pi / self.number_of_spokes # x coordinate (inner circle) points.append(self.start_x + radius_in * math.cos(angle)) # y coordinate (inner circle) points.append(self.start_y + radius_in * math.sin(angle)) self.current_item = self.canvas.create_polygon( points, outline=self.outline, fill=self.fill, width=self.width) def create_tool_bar_buttons(self): for index, name in enumerate(self.tool_bar_functions): icon = tk.PhotoImage(file='icons/' + name + '.gif') self.button = tk.Button( self.tool_bar, image=icon, command=lambda index=index: self.on_tool_bar_button_clicked(index)) self.button.grid( row=index // 2, column=1 + index % 2, sticky='nsew') self.button.image = icon def on_tool_bar_button_clicked(self, button_index): self.selected_tool_bar_function = self.tool_bar_functions[button_index] self.remove_options_from_top_bar() self.display_options_in_the_top_bar() def display_options_in_the_top_bar(self): self.show_selected_tool_icon_in_top_bar( self.selected_tool_bar_function) def remove_options_from_top_bar(self): for child in self.top_bar.winfo_children(): child.destroy() def show_selected_tool_icon_in_top_bar(self, function_name): display_name = function_name.replace("_", " ").capitalize() + ":" tk.Label(self.top_bar, text=display_name).pack(side="left") photo = tk.PhotoImage( file='icons/' + function_name + '.gif') label = tk.Label(self.top_bar, image=photo) label.image = photo label.pack(side="left") def bind_mouse(self): self.canvas.bind("", self.on_mouse_button_pressed) self.canvas.bind( "", self.on_mouse_button_pressed_motion) self.canvas.bind( "", self.on_mouse_button_released) self.canvas.bind("", self.on_mouse_unpressed_motion) def on_mouse_button_pressed(self, event): self.start_x = self.end_x = self.canvas.canvasx(event.x) self.start_y = self.end_y = self.canvas.canvasy(event.y) self.execute_selected_method() def on_mouse_button_pressed_motion(self, event): self.end_x = self.canvas.canvasx(event.x) self.end_y = self.canvas.canvasy(event.y) self.canvas.delete(self.current_item) self.execute_selected_method() def on_mouse_button_released(self, event): self.end_x = self.canvas.canvasx(event.x) self.end_y = self.canvas.canvasy(event.y) def on_mouse_unpressed_motion(self, event): pass def __init__(self, root): super().__init__(root) self.create_gui() self.bind_mouse() def create_gui(self): self.create_menu() self.create_top_bar() self.create_tool_bar() self.create_tool_bar_buttons() self.create_drawing_canvas() self.bind_menu_accelrator_keys() def create_menu(self): self.menubar = tk.Menu(self.root) menu_definitions = ( 'File- &New/Ctrl+N/self.on_new_file_menu_clicked, Save/Ctrl+S/self.on_save_menu_clicked, SaveAs/ /self.on_save_as_menu_clicked, sep, Exit/Alt+F4/self.on_close_menu_clicked', 'Edit- Undo/Ctrl+Z/self.on_undo_menu_clicked, sep', 'View- Zoom in//self.on_canvas_zoom_in_menu_clicked,Zoom Out//self.on_canvas_zoom_out_menu_clicked', 'About- About/F1/self.on_about_menu_clicked' ) self.build_menu(menu_definitions) def create_top_bar(self): self.top_bar = tk.Frame(self.root, height=25, relief="raised") self.top_bar.pack(fill="x", side="top", pady=2) def create_tool_bar(self): self.tool_bar = tk.Frame(self.root, relief="raised", width=50) self.tool_bar.pack(fill="y", side="left", pady=3) def create_drawing_canvas(self): self.canvas_frame = tk.Frame(self.root, width=900, height=900) self.canvas_frame.pack(side="right", expand="yes", fill="both") self.canvas = tk.Canvas(self.canvas_frame, background="white", width=500, height=500, scrollregion=(0, 0, 800, 800)) self.create_scroll_bar() self.canvas.pack(side=tk.RIGHT, expand=tk.YES, fill=tk.BOTH) def create_scroll_bar(self): x_scroll = tk.Scrollbar(self.canvas_frame, orient="horizontal") x_scroll.pack(side="bottom", fill="x") x_scroll.config(command=self.canvas.xview) y_scroll = tk.Scrollbar(self.canvas_frame, orient="vertical") y_scroll.pack(side="right", fill="y") y_scroll.config(command=self.canvas.yview) self.canvas.config( xscrollcommand=x_scroll.set, yscrollcommand=y_scroll.set) def bind_menu_accelrator_keys(self): self.root.bind('', self.on_about_menu_clicked) self.root.bind('', self.on_new_file_menu_clicked) self.root.bind('', self.on_new_file_menu_clicked) self.root.bind('', self.on_save_menu_clicked) self.root.bind('', self.on_save_menu_clicked) self.root.bind('', self.on_undo_menu_clicked) self.root.bind('', self.on_undo_menu_clicked) def on_new_file_menu_clicked(self, event=None): pass def on_save_menu_clicked(self, event=None): pass def on_save_as_menu_clicked(self): pass def on_canvas_zoom_out_menu_clicked(self): pass def on_canvas_zoom_in_menu_clicked(self): pass def on_close_menu_clicked(self): pass def on_undo_menu_clicked(self, event=None): pass def on_about_menu_clicked(self, event=None): pass if __name__ == '__main__': root = tk.Tk() app = PaintApplication(root) root.mainloop() ================================================ FILE: Chapter 06/6.05.py ================================================ """ Code illustration: 6.05 New imports here: from tkinter import colorchooser New attributes added here: background foreground New methods added here: create_color_palette() bind_color_palette() set_foreground_color() set_background_color() create_current_coordinate_label() show_current_coordinates() Methods modified here: create_gui() - added call to create_color_palette() & create_current_coordinate_label() on_mouse_unpressed_motion() - added call to show_current_coordinates() @ Tkinter GUI Application Development Blueprints """ import math import cmath import tkinter as tk from tkinter import colorchooser import framework class PaintApplication(framework.Framework): start_x, start_y = 0, 0 end_x, end_y = 0, 0 current_item = None fill = "red" outline = "red" width = 2.0 number_of_spokes = 5 arrow = None dash = None background = 'white' foreground = 'red' tool_bar_functions = ( "draw_line", "draw_oval", "draw_rectangle", "draw_arc", "draw_triangle", "draw_star", "draw_irregular_line", "draw_super_shape", "draw_text", "delete_item", "fill_item", "duplicate_item", "move_to_top", "drag_item", "enlarge_item_size", "reduce_item_size" ) selected_tool_bar_function = tool_bar_functions[0] def create_color_palette(self): self.color_palette = tk.Canvas(self.tool_bar, height=55, width=55) self.color_palette.grid(row=10, column=1, columnspan=2, pady=5, padx=3) self.background_palette = self.color_palette.create_rectangle( 15, 15, 48, 48, outline=self.background, fill=self.background) self.foreground_palette = self.color_palette.create_rectangle( 1, 1, 33, 33, outline=self.foreground, fill=self.foreground) self.bind_color_palette() def bind_color_palette(self): self.color_palette.tag_bind( self.background_palette, "", self.set_background_color) self.color_palette.tag_bind( self.foreground_palette, "", self.set_foreground_color) def set_foreground_color(self, event=None): self.foreground = colorchooser.askcolor( title="select foreground color")[-1] self.color_palette.itemconfig( self.foreground_palette, outline=self.foreground, fill=self.foreground) def set_background_color(self, event=None): x = colorchooser.askcolor(title="select background color") print(x) self.background = x[-1] self.color_palette.itemconfig( self.background_palette, outline=self.background, fill=self.background) def create_current_coordinate_label(self): self.current_coordinate_label = tk.Label( self.tool_bar, text='x:0\ny: 0 ') self.current_coordinate_label.grid( row=13, column=1, columnspan=2, pady=5, padx=1, sticky='w') def show_current_coordinates(self, event=None): x_coordinate = event.x y_coordinate = event.y coordinate_string = "x:{0}\ny:{1}".format(x_coordinate, y_coordinate) self.current_coordinate_label.config(text=coordinate_string) def function_not_defined(self): pass def execute_selected_method(self): self.current_item = None func = getattr( self, self.selected_tool_bar_function, self.function_not_defined) func() def draw_line(self): self.current_item = self.canvas.create_line( self.start_x, self.start_y, self.end_x, self.end_y, fill=self.fill, width=self.width, arrow=self.arrow, dash=self.dash) def draw_oval(self): self.current_item = self.canvas.create_oval( self.start_x, self.start_y, self.end_x, self.end_y, outline=self.outline, fill=self.fill, width=self.width) def draw_rectangle(self): self.current_item = self.canvas.create_rectangle( self.start_x, self.start_y, self.end_x, self.end_y, outline=self.outline, fill=self.fill, width=self.width) def draw_arc(self): self.current_item = self.canvas.create_arc( self.start_x, self.start_y, self.end_x, self.end_y, outline=self.outline, fill=self.fill, width=self.width) def draw_triangle(self): dx = self.end_x - self.start_x dy = self.end_y - self.start_y z = complex(dx, dy) radius, angle0 = cmath.polar(z) edges = 3 points = list() for edge in range(edges): angle = angle0 + edge * (2 * math.pi) / edges points.append(self.start_x + radius * math.cos(angle)) points.append(self.start_y + radius * math.sin(angle)) self.current_item = self.canvas.create_polygon( points, outline=self.outline, fill=self.fill, width=self.width) def draw_star(self): dx = self.end_x - self.start_x dy = self.end_y - self.start_y z = complex(dx, dy) radius_out, angle0 = cmath.polar(z) radius_in = radius_out / 2 points = list() for edge in range(self.number_of_spokes): angle = angle0 + edge * (2 * math.pi) / self.number_of_spokes points.append(self.start_x + radius_out * math.cos(angle)) points.append(self.start_y + radius_out * math.sin(angle)) angle += math.pi / self.number_of_spokes points.append(self.start_x + radius_in * math.cos(angle)) points.append(self.start_y + radius_in * math.sin(angle)) self.current_item = self.canvas.create_polygon( points, outline=self.outline, fill=self.fill, width=self.width) def create_tool_bar_buttons(self): for index, name in enumerate(self.tool_bar_functions): icon = tk.PhotoImage(file='icons/' + name + '.gif') self.button = tk.Button( self.tool_bar, image=icon, command=lambda index=index: self.on_tool_bar_button_clicked(index)) self.button.grid( row=index // 2, column=1 + index % 2, sticky='nsew') self.button.image = icon def on_tool_bar_button_clicked(self, button_index): self.selected_tool_bar_function = self.tool_bar_functions[button_index] self.remove_options_from_top_bar() self.display_options_in_the_top_bar() def display_options_in_the_top_bar(self): self.show_selected_tool_icon_in_top_bar( self.selected_tool_bar_function) def remove_options_from_top_bar(self): for child in self.top_bar.winfo_children(): child.destroy() def show_selected_tool_icon_in_top_bar(self, function_name): display_name = function_name.replace("_", " ").capitalize() + ":" tk.Label(self.top_bar, text=display_name).pack(side="left") photo = tk.PhotoImage( file='icons/' + function_name + '.gif') label = tk.Label(self.top_bar, image=photo) label.image = photo label.pack(side="left") def bind_mouse(self): self.canvas.bind("", self.on_mouse_button_pressed) self.canvas.bind( "", self.on_mouse_button_pressed_motion) self.canvas.bind( "", self.on_mouse_button_released) self.canvas.bind("", self.on_mouse_unpressed_motion) def on_mouse_button_pressed(self, event): self.start_x = self.end_x = self.canvas.canvasx(event.x) self.start_y = self.end_y = self.canvas.canvasy(event.y) self.execute_selected_method() def on_mouse_button_pressed_motion(self, event): self.end_x = self.canvas.canvasx(event.x) self.end_y = self.canvas.canvasy(event.y) self.canvas.delete(self.current_item) self.execute_selected_method() def on_mouse_button_released(self, event): self.end_x = self.canvas.canvasx(event.x) self.end_y = self.canvas.canvasy(event.y) def on_mouse_unpressed_motion(self, event): self.show_current_coordinates(event) def __init__(self, root): super().__init__(root) self.create_gui() self.bind_mouse() def create_gui(self): self.create_menu() self.create_top_bar() self.create_tool_bar() self.create_tool_bar_buttons() self.create_drawing_canvas() self.create_color_palette() self.create_current_coordinate_label() self.bind_menu_accelrator_keys() def create_menu(self): self.menubar = tk.Menu(self.root) menu_definitions = ( 'File- &New/Ctrl+N/self.on_new_file_menu_clicked, Save/Ctrl+S/self.on_save_menu_clicked, SaveAs/ /self.on_save_as_menu_clicked, sep, Exit/Alt+F4/self.on_close_menu_clicked', 'Edit- Undo/Ctrl+Z/self.on_undo_menu_clicked, sep', 'View- Zoom in//self.on_canvas_zoom_in_menu_clicked,Zoom Out//self.on_canvas_zoom_out_menu_clicked', 'About- About/F1/self.on_about_menu_clicked' ) self.build_menu(menu_definitions) def create_top_bar(self): self.top_bar = tk.Frame(self.root, height=25, relief="raised") self.top_bar.pack(fill="x", side="top", pady=2) def create_tool_bar(self): self.tool_bar = tk.Frame(self.root, relief="raised", width=50) self.tool_bar.pack(fill="y", side="left", pady=3) def create_drawing_canvas(self): self.canvas_frame = tk.Frame(self.root, width=900, height=900) self.canvas_frame.pack(side="right", expand="yes", fill="both") self.canvas = tk.Canvas(self.canvas_frame, background="white", width=500, height=500, scrollregion=(0, 0, 800, 800)) self.create_scroll_bar() self.canvas.pack(side=tk.RIGHT, expand=tk.YES, fill=tk.BOTH) def create_scroll_bar(self): x_scroll = tk.Scrollbar(self.canvas_frame, orient="horizontal") x_scroll.pack(side="bottom", fill="x") x_scroll.config(command=self.canvas.xview) y_scroll = tk.Scrollbar(self.canvas_frame, orient="vertical") y_scroll.pack(side="right", fill="y") y_scroll.config(command=self.canvas.yview) self.canvas.config( xscrollcommand=x_scroll.set, yscrollcommand=y_scroll.set) def bind_menu_accelrator_keys(self): self.root.bind('', self.on_about_menu_clicked) self.root.bind('', self.on_new_file_menu_clicked) self.root.bind('', self.on_new_file_menu_clicked) self.root.bind('', self.on_save_menu_clicked) self.root.bind('', self.on_save_menu_clicked) self.root.bind('', self.on_undo_menu_clicked) self.root.bind('', self.on_undo_menu_clicked) def on_new_file_menu_clicked(self, event=None): pass def on_save_menu_clicked(self, event=None): pass def on_save_as_menu_clicked(self): pass def on_canvas_zoom_out_menu_clicked(self): pass def on_canvas_zoom_in_menu_clicked(self): pass def on_close_menu_clicked(self): pass def on_undo_menu_clicked(self, event=None): pass def on_about_menu_clicked(self, event=None): pass if __name__ == '__main__': root = tk.Tk() app = PaintApplication(root) root.mainloop() ================================================ FILE: Chapter 06/6.06.py ================================================ """ Code illustration: 6.06 New imports here: from tkinter import ttk New methods added here: create_fill_options_combobox() create_outline_options_combobox() create_width_options_combobox() create_dash_options_combobox() create_arrow_options_combobox() set_fill() set_outline() set_width() set_dash() set_arrow() draw_line_options() draw_oval_options() draw_rectangle_options() draw_arc_options() draw_triangle_options() draw_star_options() try_to_set_fill_after_palette_change() try_to_set_outline_after_palette_change() Methods modified here: display_options_in_the_top_bar (added a dynamic function call) set_background_color() set_foreground_color() create_gui() @ Tkinter GUI Application Development Blueprints """ import math import cmath import tkinter as tk from tkinter import colorchooser from tkinter import ttk import framework class PaintApplication(framework.Framework): start_x, start_y = 0, 0 end_x, end_y = 0, 0 current_item = None fill = "red" outline = "red" width = 2.0 number_of_spokes = 5 arrow = None dash = None background = 'white' foreground = 'red' tool_bar_functions = ( "draw_line", "draw_oval", "draw_rectangle", "draw_arc", "draw_triangle", "draw_star", "draw_irregular_line", "draw_super_shape", "draw_text", "delete_item", "fill_item", "duplicate_item", "move_to_top", "drag_item", "enlarge_item_size", "reduce_item_size" ) selected_tool_bar_function = tool_bar_functions[0] def set_foreground_color(self, event=None): self.foreground = self.get_color_from_chooser( self.foreground, "foreground") self.color_palette.itemconfig( self.foreground_palette, width=0, fill=self.foreground) def set_background_color(self, event=None): self.background = self.get_color_from_chooser( self.background, "background") self.color_palette.itemconfig( self.background_palette, width=0, fill=self.background) def get_color_from_chooser(self, initial_color, color_type="a"): color = colorchooser.askcolor( color=initial_color, title="select {} color".format(color_type) )[-1] if color: return color # dialog has been cancelled else: return initial_color def try_to_set_fill_after_palette_change(self): try: self.set_fill() except: pass def try_to_set_outline_after_palette_change(self): try: self.set_outline() except: pass def display_options_in_the_top_bar(self): self.show_selected_tool_icon_in_top_bar( self.selected_tool_bar_function) options_function_name = "{}_options".format(self.selected_tool_bar_function) func = getattr(self, options_function_name, self.function_not_defined) func() def draw_line_options(self): self.create_fill_options_combobox() self.create_width_options_combobox() self.create_arrow_options_combobox() self.create_dash_options_combobox() def draw_oval_options(self): self.create_fill_options_combobox() self.create_outline_options_combobox() self.create_width_options_combobox() def draw_rectangle_options(self): self.create_fill_options_combobox() self.create_outline_options_combobox() self.create_width_options_combobox() def draw_arc_options(self): self.create_fill_options_combobox() self.create_outline_options_combobox() self.create_width_options_combobox() def draw_triangle_options(self): self.create_fill_options_combobox() self.create_outline_options_combobox() self.create_width_options_combobox() def draw_star_options(self): self.create_number_of_spokes_options_combobox() self.create_fill_options_combobox() self.create_outline_options_combobox() self.create_width_options_combobox() def create_fill_options_combobox(self): tk.Label(self.top_bar, text='Fill:').pack(side="left") self.fill_combobox = ttk.Combobox( self.top_bar, state='readonly', width=5) self.fill_combobox.pack(side="left") self.fill_combobox['values'] = ('none', 'fg', 'bg', 'black', 'white') self.fill_combobox.bind('<>', self.set_fill) self.fill_combobox.set(self.fill) def create_number_of_spokes_options_combobox(self): tk.Label(self.top_bar, text='Number of Edges:').pack(side="left") self.number_of_spokes_combobox = ttk.Combobox( self.top_bar, state='readonly', width=3) self.number_of_spokes_combobox.pack(side="left") self.number_of_spokes_combobox[ 'values'] = tuple(i for i in range(5, 50)) self.number_of_spokes_combobox.bind( '<>', self.set_number_of_spokes) self.number_of_spokes_combobox.set(self.number_of_spokes) def create_outline_options_combobox(self): tk.Label(self.top_bar, text='Outline:').pack(side="left") self.outline_combobox = ttk.Combobox( self.top_bar, state='readonly', width=5) self.outline_combobox.pack(side="left") self.outline_combobox['values'] = ( 'none', 'fg', 'bg', 'black', 'white') self.outline_combobox.bind('<>', self.set_outline) self.outline_combobox.set(self.outline) def create_width_options_combobox(self): tk.Label(self.top_bar, text='Width:').pack(side="left") self.width_combobox = ttk.Combobox( self.top_bar, state='readonly', width=3) self.width_combobox.pack(side="left") self.width_combobox['values'] = ( 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0) self.width_combobox.bind('<>', self.set_width) self.width_combobox.set(self.width) def create_dash_options_combobox(self): tk.Label(self.top_bar, text='Dash:').pack(side="left") self.dash_combobox = ttk.Combobox( self.top_bar, state='readonly', width=5) self.dash_combobox.pack(side="left") self.dash_combobox['values'] = ('none', 'small', 'medium', 'large') self.dash_combobox.bind('<>', self.set_dash) self.dash_combobox.current(0) def create_arrow_options_combobox(self): tk.Label(self.top_bar, text='Arrow:').pack(side="left") self.arrow_combobox = ttk.Combobox( self.top_bar, state='readonly', width=5) self.arrow_combobox.pack(side="left") self.arrow_combobox['values'] = ('none', 'first', 'last', 'both') self.arrow_combobox.bind('<>', self.set_arrow) self.arrow_combobox.current(0) def set_fill(self, event=None): fill_color = self.fill_combobox.get() if fill_color == 'none': self.fill = '' # transparent elif fill_color == 'fg': self.fill = self.foreground elif fill_color == 'bg': self.fill = self.background else: self.fill = fill_color def set_outline(self, event=None): outline_color = self.outline_combobox.get() if outline_color == 'none': self.outline = '' # transparent elif outline_color == 'fg': self.outline = self.foreground elif outline_color == 'bg': self.outline = self.background else: self.outline = outline_color def set_width(self, event): self.width = float(self.width_combobox.get()) def set_number_of_spokes(self, event): self.number_of_spokes = int(self.number_of_spokes_combobox.get()) def set_arrow(self, event): self.arrow = self.arrow_combobox.get() def set_dash(self, event): '''Dash takes value from 1 to 255''' dash_size = self.dash_combobox.get() if dash_size == 'none': self.dash = None elif dash_size == 'small': self.dash = 1 elif dash_size == 'medium': self.dash = 15 elif dash_size == 'large': self.dash = 100 def create_color_palette(self): self.color_palette = tk.Canvas(self.tool_bar, height=55, width=55) self.color_palette.grid(row=10, column=1, columnspan=2, pady=5, padx=3) self.background_palette = self.color_palette.create_rectangle( 15, 15, 48, 48, outline=self.background, fill=self.background) self.foreground_palette = self.color_palette.create_rectangle( 1, 1, 33, 33, outline=self.foreground, fill=self.foreground) self.bind_color_palette() def bind_color_palette(self): self.color_palette.tag_bind( self.background_palette, "", self.set_background_color) self.color_palette.tag_bind( self.foreground_palette, "", self.set_foreground_color) def create_current_coordinate_label(self): self.current_coordinate_label = tk.Label( self.tool_bar, text='x:0\ny: 0 ') self.current_coordinate_label.grid( row=13, column=1, columnspan=2, pady=5, padx=1, sticky='w') def show_current_coordinates(self, event=None): x_coordinate = event.x y_coordinate = event.y coordinate_string = "x:{0}\ny:{1}".format(x_coordinate, y_coordinate) self.current_coordinate_label.config(text=coordinate_string) def function_not_defined(self): pass def execute_selected_method(self): self.current_item = None func = getattr( self, self.selected_tool_bar_function, self.function_not_defined) func() def draw_line(self): self.current_item = self.canvas.create_line( self.start_x, self.start_y, self.end_x, self.end_y, fill=self.fill, width=self.width, arrow=self.arrow, dash=self.dash) def draw_oval(self): self.current_item = self.canvas.create_oval( self.start_x, self.start_y, self.end_x, self.end_y, outline=self.outline, fill=self.fill, width=self.width) def draw_rectangle(self): self.current_item = self.canvas.create_rectangle( self.start_x, self.start_y, self.end_x, self.end_y, outline=self.outline, fill=self.fill, width=self.width) def draw_arc(self): self.current_item = self.canvas.create_arc( self.start_x, self.start_y, self.end_x, self.end_y, outline=self.outline, fill=self.fill, width=self.width) def draw_triangle(self): dx = self.end_x - self.start_x dy = self.end_y - self.start_y z = complex(dx, dy) radius, angle0 = cmath.polar(z) edges = 3 points = list() for edge in range(edges): angle = angle0 + edge * (2 * math.pi) / edges points.append(self.start_x + radius * math.cos(angle)) points.append(self.start_y + radius * math.sin(angle)) self.current_item = self.canvas.create_polygon( points, outline=self.outline, fill=self.fill, width=self.width) def draw_star(self): dx = self.end_x - self.start_x dy = self.end_y - self.start_y z = complex(dx, dy) radius_out, angle0 = cmath.polar(z) radius_in = radius_out / 2 points = list() for edge in range(self.number_of_spokes): angle = angle0 + edge * (2 * math.pi) / self.number_of_spokes points.append(self.start_x + radius_out * math.cos(angle)) points.append(self.start_y + radius_out * math.sin(angle)) angle += math.pi / self.number_of_spokes points.append(self.start_x + radius_in * math.cos(angle)) points.append(self.start_y + radius_in * math.sin(angle)) self.current_item = self.canvas.create_polygon( points, outline=self.outline, fill=self.fill, width=self.width) def create_tool_bar_buttons(self): for index, name in enumerate(self.tool_bar_functions): icon = tk.PhotoImage(file='icons/' + name + '.gif') self.button = tk.Button( self.tool_bar, image=icon, command=lambda index=index: self.on_tool_bar_button_clicked(index)) self.button.grid( row=index // 2, column=1 + index % 2, sticky='nsew') self.button.image = icon def on_tool_bar_button_clicked(self, button_index): self.selected_tool_bar_function = self.tool_bar_functions[button_index] self.remove_options_from_top_bar() self.display_options_in_the_top_bar() def remove_options_from_top_bar(self): for child in self.top_bar.winfo_children(): child.destroy() def show_selected_tool_icon_in_top_bar(self, function_name): display_name = function_name.replace("_", " ").capitalize() + ":" tk.Label(self.top_bar, text=display_name).pack(side="left") photo = tk.PhotoImage( file='icons/' + function_name + '.gif') label = tk.Label(self.top_bar, image=photo) label.image = photo label.pack(side="left") def bind_mouse(self): self.canvas.bind("", self.on_mouse_button_pressed) self.canvas.bind( "", self.on_mouse_button_pressed_motion) self.canvas.bind( "", self.on_mouse_button_released) self.canvas.bind("", self.on_mouse_unpressed_motion) def on_mouse_button_pressed(self, event): self.start_x = self.end_x = self.canvas.canvasx(event.x) self.start_y = self.end_y = self.canvas.canvasy(event.y) self.execute_selected_method() def on_mouse_button_pressed_motion(self, event): self.end_x = self.canvas.canvasx(event.x) self.end_y = self.canvas.canvasy(event.y) self.canvas.delete(self.current_item) self.execute_selected_method() def on_mouse_button_released(self, event): self.end_x = self.canvas.canvasx(event.x) self.end_y = self.canvas.canvasy(event.y) def on_mouse_unpressed_motion(self, event): self.show_current_coordinates(event) def __init__(self, root): super().__init__(root) self.create_gui() self.bind_mouse() def create_gui(self): self.create_menu() self.create_top_bar() self.create_tool_bar() self.create_tool_bar_buttons() self.create_drawing_canvas() self.create_color_palette() self.create_current_coordinate_label() self.bind_menu_accelrator_keys() self.show_selected_tool_icon_in_top_bar("draw_line") self.draw_line_options() def create_menu(self): self.menubar = tk.Menu(self.root) menu_definitions = ( 'File- &New/Ctrl+N/self.on_new_file_menu_clicked, Save/Ctrl+S/self.on_save_menu_clicked, SaveAs/ /self.on_save_as_menu_clicked, sep, Exit/Alt+F4/self.on_close_menu_clicked', 'Edit- Undo/Ctrl+Z/self.on_undo_menu_clicked, sep', 'View- Zoom in//self.on_canvas_zoom_in_menu_clicked,Zoom Out//self.on_canvas_zoom_out_menu_clicked', 'About- About/F1/self.on_about_menu_clicked' ) self.build_menu(menu_definitions) def create_top_bar(self): self.top_bar = tk.Frame(self.root, height=25, relief="raised") self.top_bar.pack(fill="x", side="top", pady=2) def create_tool_bar(self): self.tool_bar = tk.Frame(self.root, relief="raised", width=50) self.tool_bar.pack(fill="y", side="left", pady=3) def create_drawing_canvas(self): self.canvas_frame = tk.Frame(self.root, width=900, height=900) self.canvas_frame.pack(side="right", expand="yes", fill="both") self.canvas = tk.Canvas(self.canvas_frame, background="white", width=500, height=500, scrollregion=(0, 0, 800, 800)) self.create_scroll_bar() self.canvas.pack(side=tk.RIGHT, expand=tk.YES, fill=tk.BOTH) def create_scroll_bar(self): x_scroll = tk.Scrollbar(self.canvas_frame, orient="horizontal") x_scroll.pack(side="bottom", fill="x") x_scroll.config(command=self.canvas.xview) y_scroll = tk.Scrollbar(self.canvas_frame, orient="vertical") y_scroll.pack(side="right", fill="y") y_scroll.config(command=self.canvas.yview) self.canvas.config( xscrollcommand=x_scroll.set, yscrollcommand=y_scroll.set) def bind_menu_accelrator_keys(self): self.root.bind('', self.on_about_menu_clicked) self.root.bind('', self.on_new_file_menu_clicked) self.root.bind('', self.on_new_file_menu_clicked) self.root.bind('', self.on_save_menu_clicked) self.root.bind('', self.on_save_menu_clicked) self.root.bind('', self.on_undo_menu_clicked) self.root.bind('', self.on_undo_menu_clicked) def on_new_file_menu_clicked(self, event=None): pass def on_save_menu_clicked(self, event=None): pass def on_save_as_menu_clicked(self): pass def on_canvas_zoom_out_menu_clicked(self): pass def on_canvas_zoom_in_menu_clicked(self): pass def on_close_menu_clicked(self): pass def on_undo_menu_clicked(self, event=None): pass def on_about_menu_clicked(self, event=None): pass if __name__ == '__main__': root = tk.Tk() app = PaintApplication(root) root.mainloop() ================================================ FILE: Chapter 06/6.07.py ================================================ """ Code illustration: 6.07 New imports here: from supershapes import * New attributes added here: selected_super_shape = "shape 1" New methods added here: draw_irregular_line() draw_irregular_line_update_x_y() draw_irregular_line_options() draw_super_shape() draw_super_shape_options() create_super_shapes_options_combobox() set_selected_super_shape() get_super_shape_points() float_range() Methods modified here: on_tool_bar_button_clicked() - added a call to bind_mouse() @ Tkinter GUI Application Development Blueprints """ import math import cmath import tkinter as tk from tkinter import colorchooser from tkinter import ttk import framework from supershapes import * class PaintApplication(framework.Framework): start_x, start_y = 0, 0 end_x, end_y = 0, 0 current_item = None fill = "red" outline = "red" width = 2.0 number_of_spokes = 5 arrow = None dash = None background = 'white' foreground = 'red' selected_super_shape = "shape A" tool_bar_functions = ( "draw_line", "draw_oval", "draw_rectangle", "draw_arc", "draw_triangle", "draw_star", "draw_irregular_line", "draw_super_shape", "draw_text", "delete_item", "fill_item", "duplicate_item", "move_to_top", "drag_item", "enlarge_item_size", "reduce_item_size" ) selected_tool_bar_function = tool_bar_functions[0] def draw_irregular_line(self): self.current_item = self.canvas.create_line( self.start_x, self.start_y, self.end_x, self.end_y, fill=self.fill, width=self.width) self.canvas.bind("", self.draw_irregular_line_update_x_y) def draw_irregular_line_update_x_y(self, event=None): self.start_x, self.start_y = self.end_x, self.end_y self.end_x, self.end_y = event.x, event.y self.draw_irregular_line() def draw_irregular_line_options(self): self.create_fill_options_combobox() self.create_width_options_combobox() def on_tool_bar_button_clicked(self, button_index): self.selected_tool_bar_function = self.tool_bar_functions[button_index] self.remove_options_from_top_bar() self.display_options_in_the_top_bar() self.bind_mouse() def draw_super_shape(self): points = self.get_super_shape_points( *super_shapes[self.selected_super_shape]) self.current_item = self.canvas.create_polygon(points, outline=self.outline, fill=self.fill, width=self.width) def draw_super_shape_options(self): self.create_super_shapes_options_combobox() self.create_fill_options_combobox() self.create_outline_options_combobox() self.create_width_options_combobox() def create_super_shapes_options_combobox(self): tk.Label(self.top_bar, text='Select shape:').pack(side="left") self.super_shape_combobox = ttk.Combobox( self.top_bar, state='readonly', width=8) self.super_shape_combobox.pack(side="left") self.super_shape_combobox['values'] = sorted(tuple( shape for shape in super_shapes.keys())) self.super_shape_combobox.bind( '<>', self.set_selected_super_shape) self.super_shape_combobox.set(self.selected_super_shape) def set_selected_super_shape(self, event=None): self.selected_super_shape = self.super_shape_combobox.get() def get_super_shape_points(self, a, b, m, n1, n2, n3): # https://en.wikipedia.org/wiki/Superformula points = [] for i in self.float_range(0, 2 * math.pi, 0.01): raux = (abs(1 / a * abs(math.cos(m * i / 4))) ** n2 + \ abs(1 / b * abs(math.sin(m * i / 4))) ** n3) r = abs(raux) ** (-1 / n1) x = self.end_x + r * math.cos(i) y = self.end_y + r * math.sin(i) points.extend((x, y)) return points def float_range(self, x, y, step): while x < y: yield x x += step def set_foreground_color(self, event=None): self.foreground = self.get_color_from_chooser( self.foreground, "foreground") self.color_palette.itemconfig( self.foreground_palette, width=0, fill=self.foreground) def set_background_color(self, event=None): self.background = self.get_color_from_chooser( self.background, "background") self.color_palette.itemconfig( self.background_palette, width=0, fill=self.background) def get_color_from_chooser(self, initial_color, color_type="a"): color = colorchooser.askcolor( color=initial_color, title="select {} color".format(color_type) )[-1] if color: return color # dialog has been cancelled else: return initial_color def try_to_set_fill_after_palette_change(self): try: self.set_fill() except: pass def try_to_set_outline_after_palette_change(self): try: self.set_outline() except: pass def display_options_in_the_top_bar(self): self.show_selected_tool_icon_in_top_bar( self.selected_tool_bar_function) options_function_name = "{}_options".format(self.selected_tool_bar_function) func = getattr(self, options_function_name, self.function_not_defined) func() def draw_line_options(self): self.create_fill_options_combobox() self.create_width_options_combobox() self.create_arrow_options_combobox() self.create_dash_options_combobox() def draw_oval_options(self): self.create_fill_options_combobox() self.create_outline_options_combobox() self.create_width_options_combobox() def draw_rectangle_options(self): self.create_fill_options_combobox() self.create_outline_options_combobox() self.create_width_options_combobox() def draw_arc_options(self): self.create_fill_options_combobox() self.create_outline_options_combobox() self.create_width_options_combobox() def draw_triangle_options(self): self.create_fill_options_combobox() self.create_outline_options_combobox() self.create_width_options_combobox() def draw_star_options(self): self.create_number_of_spokes_options_combobox() self.create_fill_options_combobox() self.create_outline_options_combobox() self.create_width_options_combobox() def create_fill_options_combobox(self): tk.Label(self.top_bar, text='Fill:').pack(side="left") self.fill_combobox = ttk.Combobox( self.top_bar, state='readonly', width=5) self.fill_combobox.pack(side="left") self.fill_combobox['values'] = ('none', 'fg', 'bg', 'black', 'white') self.fill_combobox.bind('<>', self.set_fill) self.fill_combobox.set(self.fill) def create_number_of_spokes_options_combobox(self): tk.Label(self.top_bar, text='Number of Edges:').pack(side="left") self.number_of_spokes_combobox = ttk.Combobox( self.top_bar, state='readonly', width=3) self.number_of_spokes_combobox.pack(side="left") self.number_of_spokes_combobox[ 'values'] = tuple(i for i in range(5, 50)) self.number_of_spokes_combobox.bind( '<>', self.set_number_of_spokes) self.number_of_spokes_combobox.set(self.number_of_spokes) def create_outline_options_combobox(self): tk.Label(self.top_bar, text='Outline:').pack(side="left") self.outline_combobox = ttk.Combobox( self.top_bar, state='readonly', width=5) self.outline_combobox.pack(side="left") self.outline_combobox['values'] = ( 'none', 'fg', 'bg', 'black', 'white') self.outline_combobox.bind('<>', self.set_outline) self.outline_combobox.set(self.outline) def create_width_options_combobox(self): tk.Label(self.top_bar, text='Width:').pack(side="left") self.width_combobox = ttk.Combobox( self.top_bar, state='readonly', width=3) self.width_combobox.pack(side="left") self.width_combobox['values'] = ( 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0) self.width_combobox.bind('<>', self.set_width) self.width_combobox.set(self.width) def create_dash_options_combobox(self): tk.Label(self.top_bar, text='Dash:').pack(side="left") self.dash_combobox = ttk.Combobox( self.top_bar, state='readonly', width=5) self.dash_combobox.pack(side="left") self.dash_combobox['values'] = ('none', 'small', 'medium', 'large') self.dash_combobox.bind('<>', self.set_dash) self.dash_combobox.current(0) def create_arrow_options_combobox(self): tk.Label(self.top_bar, text='Arrow:').pack(side="left") self.arrow_combobox = ttk.Combobox( self.top_bar, state='readonly', width=5) self.arrow_combobox.pack(side="left") self.arrow_combobox['values'] = ('none', 'first', 'last', 'both') self.arrow_combobox.bind('<>', self.set_arrow) self.arrow_combobox.current(0) def set_fill(self, event=None): fill_color = self.fill_combobox.get() if fill_color == 'none': self.fill = '' # transparent elif fill_color == 'fg': self.fill = self.foreground elif fill_color == 'bg': self.fill = self.background else: self.fill = fill_color def set_outline(self, event=None): outline_color = self.outline_combobox.get() if outline_color == 'none': self.outline = '' # transparent elif outline_color == 'fg': self.outline = self.foreground elif outline_color == 'bg': self.outline = self.background else: self.outline = outline_color def set_width(self, event): self.width = float(self.width_combobox.get()) def set_number_of_spokes(self, event): self.number_of_spokes = int(self.number_of_spokes_combobox.get()) def set_arrow(self, event): self.arrow = self.arrow_combobox.get() def set_dash(self, event): '''Dash takes value from 1 to 255''' dash_size = self.dash_combobox.get() if dash_size == 'none': self.dash = None elif dash_size == 'small': self.dash = 1 elif dash_size == 'medium': self.dash = 15 elif dash_size == 'large': self.dash = 100 def create_color_palette(self): self.color_palette = tk.Canvas(self.tool_bar, height=55, width=55) self.color_palette.grid(row=10, column=1, columnspan=2, pady=5, padx=3) self.background_palette = self.color_palette.create_rectangle( 15, 15, 48, 48, outline=self.background, fill=self.background) self.foreground_palette = self.color_palette.create_rectangle( 1, 1, 33, 33, outline=self.foreground, fill=self.foreground) self.bind_color_palette() def bind_color_palette(self): self.color_palette.tag_bind( self.background_palette, "", self.set_background_color) self.color_palette.tag_bind( self.foreground_palette, "", self.set_foreground_color) def create_current_coordinate_label(self): self.current_coordinate_label = tk.Label( self.tool_bar, text='x:0\ny: 0 ') self.current_coordinate_label.grid( row=13, column=1, columnspan=2, pady=5, padx=1, sticky='w') def show_current_coordinates(self, event=None): x_coordinate = event.x y_coordinate = event.y coordinate_string = "x:{0}\ny:{1}".format(x_coordinate, y_coordinate) self.current_coordinate_label.config(text=coordinate_string) def function_not_defined(self): pass def execute_selected_method(self): self.current_item = None func = getattr( self, self.selected_tool_bar_function, self.function_not_defined) func() def draw_line(self): self.current_item = self.canvas.create_line( self.start_x, self.start_y, self.end_x, self.end_y, fill=self.fill, width=self.width, arrow=self.arrow, dash=self.dash) def draw_oval(self): self.current_item = self.canvas.create_oval( self.start_x, self.start_y, self.end_x, self.end_y, outline=self.outline, fill=self.fill, width=self.width) def draw_rectangle(self): self.current_item = self.canvas.create_rectangle( self.start_x, self.start_y, self.end_x, self.end_y, outline=self.outline, fill=self.fill, width=self.width) def draw_arc(self): self.current_item = self.canvas.create_arc( self.start_x, self.start_y, self.end_x, self.end_y, outline=self.outline, fill=self.fill, width=self.width) def draw_triangle(self): dx = self.end_x - self.start_x dy = self.end_y - self.start_y z = complex(dx, dy) radius, angle0 = cmath.polar(z) edges = 3 points = list() for edge in range(edges): angle = angle0 + edge * (2 * math.pi) / edges points.append(self.start_x + radius * math.cos(angle)) points.append(self.start_y + radius * math.sin(angle)) self.current_item = self.canvas.create_polygon( points, outline=self.outline, fill=self.fill, width=self.width) def draw_star(self): dx = self.end_x - self.start_x dy = self.end_y - self.start_y z = complex(dx, dy) radius_out, angle0 = cmath.polar(z) radius_in = radius_out / 2 points = list() for edge in range(self.number_of_spokes): angle = angle0 + edge * (2 * math.pi) / self.number_of_spokes points.append(self.start_x + radius_out * math.cos(angle)) points.append(self.start_y + radius_out * math.sin(angle)) angle += math.pi / self.number_of_spokes points.append(self.start_x + radius_in * math.cos(angle)) points.append(self.start_y + radius_in * math.sin(angle)) self.current_item = self.canvas.create_polygon( points, outline=self.outline, fill=self.fill, width=self.width) def create_tool_bar_buttons(self): for index, name in enumerate(self.tool_bar_functions): icon = tk.PhotoImage(file='icons/' + name + '.gif') self.button = tk.Button( self.tool_bar, image=icon, command=lambda index=index: self.on_tool_bar_button_clicked(index)) self.button.grid( row=index // 2, column=1 + index % 2, sticky='nsew') self.button.image = icon def remove_options_from_top_bar(self): for child in self.top_bar.winfo_children(): child.destroy() def show_selected_tool_icon_in_top_bar(self, function_name): display_name = function_name.replace("_", " ").capitalize() + ":" tk.Label(self.top_bar, text=display_name).pack(side="left") photo = tk.PhotoImage( file='icons/' + function_name + '.gif') label = tk.Label(self.top_bar, image=photo) label.image = photo label.pack(side="left") def bind_mouse(self): self.canvas.bind("", self.on_mouse_button_pressed) self.canvas.bind( "", self.on_mouse_button_pressed_motion) self.canvas.bind( "", self.on_mouse_button_released) self.canvas.bind("", self.on_mouse_unpressed_motion) def on_mouse_button_pressed(self, event): self.start_x = self.end_x = self.canvas.canvasx(event.x) self.start_y = self.end_y = self.canvas.canvasy(event.y) self.execute_selected_method() def on_mouse_button_pressed_motion(self, event): self.end_x = self.canvas.canvasx(event.x) self.end_y = self.canvas.canvasy(event.y) self.canvas.delete(self.current_item) self.execute_selected_method() def on_mouse_button_released(self, event): self.end_x = self.canvas.canvasx(event.x) self.end_y = self.canvas.canvasy(event.y) def on_mouse_unpressed_motion(self, event): self.show_current_coordinates(event) def __init__(self, root): super().__init__(root) self.create_gui() self.bind_mouse() def create_gui(self): self.create_menu() self.create_top_bar() self.create_tool_bar() self.create_tool_bar_buttons() self.create_drawing_canvas() self.create_color_palette() self.create_current_coordinate_label() self.bind_menu_accelrator_keys() self.show_selected_tool_icon_in_top_bar("draw_line") self.draw_line_options() def create_menu(self): self.menubar = tk.Menu(self.root) menu_definitions = ( 'File- &New/Ctrl+N/self.on_new_file_menu_clicked, Save/Ctrl+S/self.on_save_menu_clicked, SaveAs/ /self.on_save_as_menu_clicked, sep, Exit/Alt+F4/self.on_close_menu_clicked', 'Edit- Undo/Ctrl+Z/self.on_undo_menu_clicked, sep', 'View- Zoom in//self.on_canvas_zoom_in_menu_clicked,Zoom Out//self.on_canvas_zoom_out_menu_clicked', 'About- About/F1/self.on_about_menu_clicked' ) self.build_menu(menu_definitions) def create_top_bar(self): self.top_bar = tk.Frame(self.root, height=25, relief="raised") self.top_bar.pack(fill="x", side="top", pady=2) def create_tool_bar(self): self.tool_bar = tk.Frame(self.root, relief="raised", width=50) self.tool_bar.pack(fill="y", side="left", pady=3) def create_drawing_canvas(self): self.canvas_frame = tk.Frame(self.root, width=900, height=900) self.canvas_frame.pack(side="right", expand="yes", fill="both") self.canvas = tk.Canvas(self.canvas_frame, background="white", width=500, height=500, scrollregion=(0, 0, 800, 800)) self.create_scroll_bar() self.canvas.pack(side=tk.RIGHT, expand=tk.YES, fill=tk.BOTH) def create_scroll_bar(self): x_scroll = tk.Scrollbar(self.canvas_frame, orient="horizontal") x_scroll.pack(side="bottom", fill="x") x_scroll.config(command=self.canvas.xview) y_scroll = tk.Scrollbar(self.canvas_frame, orient="vertical") y_scroll.pack(side="right", fill="y") y_scroll.config(command=self.canvas.yview) self.canvas.config( xscrollcommand=x_scroll.set, yscrollcommand=y_scroll.set) def bind_menu_accelrator_keys(self): self.root.bind('', self.on_about_menu_clicked) self.root.bind('', self.on_new_file_menu_clicked) self.root.bind('', self.on_new_file_menu_clicked) self.root.bind('', self.on_save_menu_clicked) self.root.bind('', self.on_save_menu_clicked) self.root.bind('', self.on_undo_menu_clicked) self.root.bind('', self.on_undo_menu_clicked) def on_new_file_menu_clicked(self, event=None): pass def on_save_menu_clicked(self, event=None): pass def on_save_as_menu_clicked(self): pass def on_canvas_zoom_out_menu_clicked(self): pass def on_canvas_zoom_in_menu_clicked(self): pass def on_close_menu_clicked(self): pass def on_undo_menu_clicked(self, event=None): pass def on_about_menu_clicked(self, event=None): pass if __name__ == '__main__': root = tk.Tk() app = PaintApplication(root) root.mainloop() ================================================ FILE: Chapter 06/6.08.py ================================================ """ Code illustration: 6.08 New methods added here: draw_text() draw_text_options() on_create_text_button_clicked() delete_item() fill_item() fill_item_options() duplicate_item() get_all_configurations_for_item() canvas_function_wrapper() move_to_top() drag_item() drag_item_update_x_y(self, event): enlarge_item_size() reduce_item_size() @ Tkinter GUI Application Development Blueprints """ import math import cmath import tkinter as tk from tkinter import colorchooser from tkinter import ttk import framework from supershapes import * class PaintApplication(framework.Framework): start_x, start_y = 0, 0 end_x, end_y = 0, 0 current_item = None fill = "red" outline = "red" width = 2.0 number_of_spokes = 5 arrow = None dash = None background = 'white' foreground = 'red' selected_super_shape = "shape A" tool_bar_functions = ( "draw_line", "draw_oval", "draw_rectangle", "draw_arc", "draw_triangle", "draw_star", "draw_irregular_line", "draw_super_shape", "draw_text", "delete_item", "fill_item", "duplicate_item", "move_to_top", "drag_item", "enlarge_item_size", "reduce_item_size" ) selected_tool_bar_function = tool_bar_functions[0] def draw_text(self): pass def draw_text_options(self): tk.Label(self.top_bar, text='Text:').pack(side="left") self.text_entry_widget = tk.Entry(self.top_bar, width=20) self.text_entry_widget.pack(side="left") tk.Label(self.top_bar, text='Font size:').pack(side="left") self.font_size_spinbox = tk.Spinbox( self.top_bar, from_=14, to=100, width=3) self.font_size_spinbox.pack(side="left") self.create_fill_options_combobox() self.create_text_button = tk.Button( self.top_bar, text="Go", command=self.on_create_text_button_clicked) self.create_text_button.pack(side="left", padx=5) def on_create_text_button_clicked(self): entered_text = self.text_entry_widget.get() center_x = self.canvas.winfo_width() / 2 center_y = self.canvas.winfo_height() / 2 font_size = self.font_size_spinbox.get() self.canvas.create_text( center_x, center_y, font=("", font_size), text=entered_text, fill=self.fill) def delete_item(self): self.current_item = None self.canvas.delete("current") def fill_item(self): try: self.canvas.itemconfig( "current", fill=self.fill, outline=self.outline) except TclError: self.canvas.itemconfig("current", fill=self.fill) def fill_item_options(self): self.create_fill_options_combobox() self.create_outline_options_combobox() def duplicate_item(self): try: function_name = "create_" + self.canvas.type("current") except TypeError: return coordinates = tuple( map(lambda i: i + 10, self.canvas.coords("current"))) configurations = self.get_all_configurations_for_item() self.canvas_function_wrapper( function_name, coordinates, configurations) def get_all_configurations_for_item(self): configuration_dict = {} for key, value in self.canvas.itemconfig("current").items(): if value[-1] and value[-1] not in ["0", "0.0", "0,0", "current"]: configuration_dict[key] = value[-1] return configuration_dict def canvas_function_wrapper(self, function_name, *arg, **kwargs): func = getattr(self.canvas, function_name) func(*arg, **kwargs) def move_to_top(self): self.current_item = None self.canvas.tag_raise("current") def drag_item(self): self.canvas.move( "current", self.end_x - self.start_x, self.end_y - self.start_y) self.canvas.bind("", self.drag_item_update_x_y) def drag_item_update_x_y(self, event): self.start_x, self.start_y = self.end_x, self.end_y self.end_x, self.end_y = event.x, event.y self.drag_item() def enlarge_item_size(self): self.current_item = None if self.canvas.find_withtag("current"): self.canvas.scale("current", self.end_x, self.end_y, 1.2, 1.2) self.canvas.config(scrollregion=self.canvas.bbox(tk.ALL)) def reduce_item_size(self): self.current_item = None if self.canvas.find_withtag("current"): self.canvas.scale("current", self.end_x, self.end_y, .8, .8) self.canvas.config(scrollregion=self.canvas.bbox(tk.ALL)) def draw_irregular_line(self): self.current_item = self.canvas.create_line( self.start_x, self.start_y, self.end_x, self.end_y, fill=self.fill, width=self.width) self.canvas.bind("", self.draw_irregular_line_update_x_y) def draw_irregular_line_update_x_y(self, event=None): self.start_x, self.start_y = self.end_x, self.end_y self.end_x, self.end_y = event.x, event.y self.draw_irregular_line() def draw_irregular_line_options(self): self.create_fill_options_combobox() self.create_width_options_combobox() def on_tool_bar_button_clicked(self, button_index): self.selected_tool_bar_function = self.tool_bar_functions[button_index] self.remove_options_from_top_bar() self.display_options_in_the_top_bar() self.bind_mouse() def draw_super_shape(self): points = self.get_super_shape_points( *super_shapes[self.selected_super_shape]) self.current_item = self.canvas.create_polygon(points, outline=self.outline, fill=self.fill, width=self.width) def draw_super_shape_options(self): self.create_super_shapes_options_combobox() self.create_fill_options_combobox() self.create_outline_options_combobox() self.create_width_options_combobox() def create_super_shapes_options_combobox(self): tk.Label(self.top_bar, text='Select shape:').pack(side="left") self.super_shape_combobox = ttk.Combobox( self.top_bar, state='readonly', width=8) self.super_shape_combobox.pack(side="left") self.super_shape_combobox['values'] = tuple( shape for shape in super_shapes.keys()) self.super_shape_combobox.bind( '<>', self.set_selected_super_shape) self.super_shape_combobox.set(self.selected_super_shape) def set_selected_super_shape(self, event=None): self.selected_super_shape = self.super_shape_combobox.get() def get_super_shape_points(self, a, b, m, n1, n2, n3): # https://en.wikipedia.org/wiki/Superformula points = [] for i in self.float_range(0, 2 * math.pi, 0.01): raux = (abs(1 / a * abs(math.cos(m * i / 4))) ** n2 + abs(1 / b * abs(math.sin(m * i / 4))) ** n3) r = abs(raux) ** (-1 / n1) x = self.end_x + r * math.cos(i) y = self.end_y + r * math.sin(i) points.extend((x, y)) return points def float_range(self, x, y, step): while x < y: yield x x += step def set_foreground_color(self, event=None): self.foreground = self.get_color_from_chooser( self.foreground, "foreground") self.color_palette.itemconfig( self.foreground_palette, width=0, fill=self.foreground) def set_background_color(self, event=None): self.background = self.get_color_from_chooser( self.background, "background") self.color_palette.itemconfig( self.background_palette, width=0, fill=self.background) def get_color_from_chooser(self, initial_color, color_type="a"): color = colorchooser.askcolor( color=initial_color, title="select {} color".format(color_type) )[-1] if color: return color # dialog has been cancelled else: return initial_color def try_to_set_fill_after_palette_change(self): try: self.set_fill() except: pass def try_to_set_outline_after_palette_change(self): try: self.set_outline() except: pass def display_options_in_the_top_bar(self): self.show_selected_tool_icon_in_top_bar( self.selected_tool_bar_function) options_function_name = "{}_options".format( self.selected_tool_bar_function) func = getattr(self, options_function_name, self.function_not_defined) func() def draw_line_options(self): self.create_fill_options_combobox() self.create_width_options_combobox() self.create_arrow_options_combobox() self.create_dash_options_combobox() def draw_oval_options(self): self.create_fill_options_combobox() self.create_outline_options_combobox() self.create_width_options_combobox() def draw_rectangle_options(self): self.create_fill_options_combobox() self.create_outline_options_combobox() self.create_width_options_combobox() def draw_arc_options(self): self.create_fill_options_combobox() self.create_outline_options_combobox() self.create_width_options_combobox() def draw_triangle_options(self): self.create_fill_options_combobox() self.create_outline_options_combobox() self.create_width_options_combobox() def draw_star_options(self): self.create_number_of_spokes_options_combobox() self.create_fill_options_combobox() self.create_outline_options_combobox() self.create_width_options_combobox() def create_fill_options_combobox(self): tk.Label(self.top_bar, text='Fill:').pack(side="left") self.fill_combobox = ttk.Combobox( self.top_bar, state='readonly', width=5) self.fill_combobox.pack(side="left") self.fill_combobox['values'] = ('none', 'fg', 'bg', 'black', 'white') self.fill_combobox.bind('<>', self.set_fill) self.fill_combobox.set(self.fill) def create_number_of_spokes_options_combobox(self): tk.Label(self.top_bar, text='Number of Edges:').pack(side="left") self.number_of_spokes_combobox = ttk.Combobox( self.top_bar, state='readonly', width=3) self.number_of_spokes_combobox.pack(side="left") self.number_of_spokes_combobox[ 'values'] = tuple(i for i in range(5, 50)) self.number_of_spokes_combobox.bind( '<>', self.set_number_of_spokes) self.number_of_spokes_combobox.set(self.number_of_spokes) def create_outline_options_combobox(self): tk.Label(self.top_bar, text='Outline:').pack(side="left") self.outline_combobox = ttk.Combobox( self.top_bar, state='readonly', width=5) self.outline_combobox.pack(side="left") self.outline_combobox['values'] = ( 'none', 'fg', 'bg', 'black', 'white') self.outline_combobox.bind('<>', self.set_outline) self.outline_combobox.set(self.outline) def create_width_options_combobox(self): tk.Label(self.top_bar, text='Width:').pack(side="left") self.width_combobox = ttk.Combobox( self.top_bar, state='readonly', width=3) self.width_combobox.pack(side="left") self.width_combobox['values'] = ( 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0) self.width_combobox.bind('<>', self.set_width) self.width_combobox.set(self.width) def create_dash_options_combobox(self): tk.Label(self.top_bar, text='Dash:').pack(side="left") self.dash_combobox = ttk.Combobox( self.top_bar, state='readonly', width=5) self.dash_combobox.pack(side="left") self.dash_combobox['values'] = ('none', 'small', 'medium', 'large') self.dash_combobox.bind('<>', self.set_dash) self.dash_combobox.current(0) def create_arrow_options_combobox(self): tk.Label(self.top_bar, text='Arrow:').pack(side="left") self.arrow_combobox = ttk.Combobox( self.top_bar, state='readonly', width=5) self.arrow_combobox.pack(side="left") self.arrow_combobox['values'] = ('none', 'first', 'last', 'both') self.arrow_combobox.bind('<>', self.set_arrow) self.arrow_combobox.current(0) def set_fill(self, event=None): fill_color = self.fill_combobox.get() if fill_color == 'none': self.fill = '' # transparent elif fill_color == 'fg': self.fill = self.foreground elif fill_color == 'bg': self.fill = self.background else: self.fill = fill_color def set_outline(self, event=None): outline_color = self.outline_combobox.get() if outline_color == 'none': self.outline = '' # transparent elif outline_color == 'fg': self.outline = self.foreground elif outline_color == 'bg': self.outline = self.background else: self.outline = outline_color def set_width(self, event): self.width = float(self.width_combobox.get()) def set_number_of_spokes(self, event): self.number_of_spokes = int(self.number_of_spokes_combobox.get()) def set_arrow(self, event): self.arrow = self.arrow_combobox.get() def set_dash(self, event): '''Dash takes value from 1 to 255''' dash_size = self.dash_combobox.get() if dash_size == 'none': self.dash = None elif dash_size == 'small': self.dash = 1 elif dash_size == 'medium': self.dash = 15 elif dash_size == 'large': self.dash = 100 def create_color_palette(self): self.color_palette = tk.Canvas(self.tool_bar, height=55, width=55) self.color_palette.grid(row=10, column=1, columnspan=2, pady=5, padx=3) self.background_palette = self.color_palette.create_rectangle( 15, 15, 48, 48, outline=self.background, fill=self.background) self.foreground_palette = self.color_palette.create_rectangle( 1, 1, 33, 33, outline=self.foreground, fill=self.foreground) self.bind_color_palette() def bind_color_palette(self): self.color_palette.tag_bind( self.background_palette, "", self.set_background_color) self.color_palette.tag_bind( self.foreground_palette, "", self.set_foreground_color) def create_current_coordinate_label(self): self.current_coordinate_label = tk.Label( self.tool_bar, text='x:0\ny: 0 ') self.current_coordinate_label.grid( row=13, column=1, columnspan=2, pady=5, padx=1, sticky='w') def show_current_coordinates(self, event=None): x_coordinate = event.x y_coordinate = event.y coordinate_string = "x:{0}\ny:{1}".format(x_coordinate, y_coordinate) self.current_coordinate_label.config(text=coordinate_string) def function_not_defined(self): pass def execute_selected_method(self): self.current_item = None func = getattr( self, self.selected_tool_bar_function, self.function_not_defined) func() def draw_line(self): self.current_item = self.canvas.create_line( self.start_x, self.start_y, self.end_x, self.end_y, fill=self.fill, width=self.width, arrow=self.arrow, dash=self.dash) def draw_oval(self): self.current_item = self.canvas.create_oval( self.start_x, self.start_y, self.end_x, self.end_y, outline=self.outline, fill=self.fill, width=self.width) def draw_rectangle(self): self.current_item = self.canvas.create_rectangle( self.start_x, self.start_y, self.end_x, self.end_y, outline=self.outline, fill=self.fill, width=self.width) def draw_arc(self): self.current_item = self.canvas.create_arc( self.start_x, self.start_y, self.end_x, self.end_y, outline=self.outline, fill=self.fill, width=self.width) def draw_triangle(self): dx = self.end_x - self.start_x dy = self.end_y - self.start_y z = complex(dx, dy) radius, angle0 = cmath.polar(z) edges = 3 points = list() for edge in range(edges): angle = angle0 + edge * (2 * math.pi) / edges points.append(self.start_x + radius * math.cos(angle)) points.append(self.start_y + radius * math.sin(angle)) self.current_item = self.canvas.create_polygon( points, outline=self.outline, fill=self.fill, width=self.width) def draw_star(self): dx = self.end_x - self.start_x dy = self.end_y - self.start_y z = complex(dx, dy) radius_out, angle0 = cmath.polar(z) radius_in = radius_out / 2 points = list() for edge in range(self.number_of_spokes): angle = angle0 + edge * (2 * math.pi) / self.number_of_spokes points.append(self.start_x + radius_out * math.cos(angle)) points.append(self.start_y + radius_out * math.sin(angle)) angle += math.pi / self.number_of_spokes points.append(self.start_x + radius_in * math.cos(angle)) points.append(self.start_y + radius_in * math.sin(angle)) self.current_item = self.canvas.create_polygon( points, outline=self.outline, fill=self.fill, width=self.width) def create_tool_bar_buttons(self): for index, name in enumerate(self.tool_bar_functions): icon = tk.PhotoImage(file='icons/' + name + '.gif') self.button = tk.Button( self.tool_bar, image=icon, command=lambda index=index: self.on_tool_bar_button_clicked(index)) self.button.grid( row=index // 2, column=1 + index % 2, sticky='nsew') self.button.image = icon def remove_options_from_top_bar(self): for child in self.top_bar.winfo_children(): child.destroy() def show_selected_tool_icon_in_top_bar(self, function_name): display_name = function_name.replace("_", " ").capitalize() + ":" tk.Label(self.top_bar, text=display_name).pack(side="left") photo = tk.PhotoImage( file='icons/' + function_name + '.gif') label = tk.Label(self.top_bar, image=photo) label.image = photo label.pack(side="left") def bind_mouse(self): self.canvas.bind("", self.on_mouse_button_pressed) self.canvas.bind( "", self.on_mouse_button_pressed_motion) self.canvas.bind( "", self.on_mouse_button_released) self.canvas.bind("", self.on_mouse_unpressed_motion) def on_mouse_button_pressed(self, event): self.start_x = self.end_x = self.canvas.canvasx(event.x) self.start_y = self.end_y = self.canvas.canvasy(event.y) self.execute_selected_method() def on_mouse_button_pressed_motion(self, event): self.end_x = self.canvas.canvasx(event.x) self.end_y = self.canvas.canvasy(event.y) self.canvas.delete(self.current_item) self.execute_selected_method() def on_mouse_button_released(self, event): self.end_x = self.canvas.canvasx(event.x) self.end_y = self.canvas.canvasy(event.y) def on_mouse_unpressed_motion(self, event): self.show_current_coordinates(event) def __init__(self, root): super().__init__(root) self.create_gui() self.bind_mouse() def create_gui(self): self.create_menu() self.create_top_bar() self.create_tool_bar() self.create_tool_bar_buttons() self.create_drawing_canvas() self.create_color_palette() self.create_current_coordinate_label() self.bind_menu_accelrator_keys() self.show_selected_tool_icon_in_top_bar("draw_line") self.draw_line_options() def create_menu(self): self.menubar = tk.Menu(self.root) menu_definitions = ( 'File- &New/Ctrl+N/self.on_new_file_menu_clicked, Save/Ctrl+S/self.on_save_menu_clicked, SaveAs/ /self.on_save_as_menu_clicked, sep, Exit/Alt+F4/self.on_close_menu_clicked', 'Edit- Undo/Ctrl+Z/self.on_undo_menu_clicked, sep', 'View- Zoom in//self.on_canvas_zoom_in_menu_clicked,Zoom Out//self.on_canvas_zoom_out_menu_clicked', 'About- About/F1/self.on_about_menu_clicked' ) self.build_menu(menu_definitions) def create_top_bar(self): self.top_bar = tk.Frame(self.root, height=25, relief="raised") self.top_bar.pack(fill="x", side="top", pady=2) def create_tool_bar(self): self.tool_bar = tk.Frame(self.root, relief="raised", width=50) self.tool_bar.pack(fill="y", side="left", pady=3) def create_drawing_canvas(self): self.canvas_frame = tk.Frame(self.root, width=900, height=900) self.canvas_frame.pack(side="right", expand="yes", fill="both") self.canvas = tk.Canvas(self.canvas_frame, background="white", width=500, height=500, scrollregion=(0, 0, 800, 800)) self.create_scroll_bar() self.canvas.pack(side=tk.RIGHT, expand=tk.YES, fill=tk.BOTH) def create_scroll_bar(self): x_scroll = tk.Scrollbar(self.canvas_frame, orient="horizontal") x_scroll.pack(side="bottom", fill="x") x_scroll.config(command=self.canvas.xview) y_scroll = tk.Scrollbar(self.canvas_frame, orient="vertical") y_scroll.pack(side="right", fill="y") y_scroll.config(command=self.canvas.yview) self.canvas.config( xscrollcommand=x_scroll.set, yscrollcommand=y_scroll.set) def bind_menu_accelrator_keys(self): self.root.bind('', self.on_about_menu_clicked) self.root.bind('', self.on_new_file_menu_clicked) self.root.bind('', self.on_new_file_menu_clicked) self.root.bind('', self.on_save_menu_clicked) self.root.bind('', self.on_save_menu_clicked) self.root.bind('', self.on_undo_menu_clicked) self.root.bind('', self.on_undo_menu_clicked) def on_new_file_menu_clicked(self, event=None): pass def on_save_menu_clicked(self, event=None): pass def on_save_as_menu_clicked(self): pass def on_canvas_zoom_out_menu_clicked(self): pass def on_canvas_zoom_in_menu_clicked(self): pass def on_close_menu_clicked(self): pass def on_undo_menu_clicked(self, event=None): pass def on_about_menu_clicked(self, event=None): pass if __name__ == '__main__': root = tk.Tk() app = PaintApplication(root) root.mainloop() ================================================ FILE: Chapter 06/6.09.py ================================================ """ Code illustration: 6.09 Modules imported here: from tkinter import messagebox from tkinter import filedialog Attributes added here: file_name = "untitled" Methods modified here: on_new_file_menu_clicked() on_save_menu_clicked() on_save_as_menu_clicked() on_close_menu_clicked() on_undo_menu_clicked() on_canvas_zoom_in_menu_clicked() on_canvas_zoom_out_menu_clicked() on_about_menu_clicked() Methods added here start_new_project() actual_save() close_window() undo() canvas_zoom_in() canvas_zoom_out() @ Tkinter GUI Application Development Blueprints """ import math import tkinter as tk from tkinter import colorchooser from tkinter import ttk from tkinter import messagebox from tkinter import filedialog import cmath import framework from supershapes import * class PaintApplication(framework.Framework): start_x, start_y = 0, 0 end_x, end_y = 0, 0 current_item = None fill = "red" outline = "red" width = 2.0 number_of_spokes = 5 arrow = None dash = None background = 'white' foreground = 'red' selected_super_shape = "shape A" file_name = "untitled" tool_bar_functions = ( "draw_line", "draw_oval", "draw_rectangle", "draw_arc", "draw_triangle", "draw_star", "draw_irregular_line", "draw_super_shape", "draw_text", "delete_item", "fill_item", "duplicate_item", "move_to_top", "drag_item", "enlarge_item_size", "reduce_item_size" ) selected_tool_bar_function = tool_bar_functions[0] def on_new_file_menu_clicked(self, event=None): self.start_new_project() def start_new_project(self): self.canvas.delete(ALL) self.canvas.config(bg="#ffffff") self.root.title('untitled') def on_save_menu_clicked(self, event=None): if self.file_name == 'untitled': self.on_save_as_menu_clicked() else: self.actual_save() def on_save_as_menu_clicked(self): file_name = filedialog.asksaveasfilename( master=self.root, filetypes=[('All Files', ('*.ps', '*.ps'))], title="Save...") if not file_name: return self.file_name = file_name self.actual_save() def actual_save(self): self.canvas.postscript(file=self.file_name, colormode='color') self.root.title(self.file_name) def on_close_menu_clicked(self): self.close_window() def close_window(self): if messagebox.askokcancel("Quit", "Do you really want to quit?"): self.root.destroy() def on_undo_menu_clicked(self, event=None): self.undo() def undo(self): items_stack = list(self.canvas.find("all")) try: last_item_id = items_stack.pop() except IndexError: return self.canvas.delete(last_item_id) def on_canvas_zoom_in_menu_clicked(self): self.canvas_zoom_in() def on_canvas_zoom_out_menu_clicked(self): self.canvas_zoom_out() def canvas_zoom_in(self): self.canvas.scale("all", 0, 0, 1.2, 1.2) self.canvas.config(scrollregion=self.canvas.bbox(tk.ALL)) def canvas_zoom_out(self): self.canvas.scale("all", 0, 0, .8, .8) self.canvas.config(scrollregion=self.canvas.bbox(tk.ALL)) def on_about_menu_clicked(self, event=None): messagebox.showinfo( "About", "Tkinter GUI Application\n Development Blueprints") def draw_text_options(self): tk.Label(self.top_bar, text='Text:').pack(side="left") self.text_entry_widget = tk.Entry(self.top_bar, width=20) self.text_entry_widget.pack(side="left") tk.Label(self.top_bar, text='Font size:').pack(side="left") self.font_size_spinbox = tk.Spinbox( self.top_bar, from_=14, to=100, width=3) self.font_size_spinbox.pack(side="left") self.create_fill_options_combobox() self.create_text_button = tk.Button( self.top_bar, text="Go", command=self.on_create_text_button_clicked) self.create_text_button.pack(side="left", padx=5) def on_create_text_button_clicked(self): entered_text = self.text_entry_widget.get() center_x = self.canvas.winfo_width() / 2 center_y = self.canvas.winfo_height() / 2 font_size = self.font_size_spinbox.get() self.canvas.create_text( center_x, center_y, font=("", font_size), text=entered_text, fill=self.fill) def delete_item(self): self.current_item = None self.canvas.delete("current") def fill_item(self): try: self.canvas.itemconfig("current", fill=self.fill, outline=self.outline) except TclError: self.canvas.itemconfig("current", fill=self.fill) def fill_item_options(self): self.create_fill_options_combobox() self.create_outline_options_combobox() def duplicate_item(self): try: function_name = "create_" + self.canvas.type("current") except TypeError: return coordinates = tuple(map(lambda i: i+10, self.canvas.coords("current"))) configurations = self.get_all_configurations_for_item() self.canvas_function_wrapper( function_name, coordinates, configurations) def get_all_configurations_for_item(self): configuration_dict = {} for key, value in self.canvas.itemconfig("current").items(): if value[-1] and value[-1] not in ["0", "0.0", "0,0", "current"]: configuration_dict[key] = value[-1] return configuration_dict def canvas_function_wrapper(self, function_name, *arg, **kwargs): func = getattr(self.canvas, function_name) func(*arg, **kwargs) def move_to_top(self): self.current_item = None self.canvas.tag_raise("current") def drag_item(self): self.canvas.move( "current", self.end_x - self.start_x, self.end_y - self.start_y) self.canvas.bind("", self.drag_item_update_x_y) def drag_item_update_x_y(self, event): self.start_x, self.start_y = self.end_x, self.end_y self.end_x, self.end_y = event.x, event.y self.drag_item() def enlarge_item_size(self): self.current_item = None if self.canvas.find_withtag("current"): self.canvas.scale("current", self.end_x, self.end_y, 1.2, 1.2) self.canvas.config(scrollregion=self.canvas.bbox(tk.ALL)) def reduce_item_size(self): self.current_item = None if self.canvas.find_withtag("current"): self.canvas.scale("current", self.end_x, self.end_y, .8, .8) self.canvas.config(scrollregion=self.canvas.bbox(tk.ALL)) def draw_irregular_line(self): self.current_item = self.canvas.create_line( self.start_x, self.start_y, self.end_x, self.end_y, fill=self.fill, width=self.width) self.canvas.bind("", self.draw_irregular_line_update_x_y) def draw_irregular_line_update_x_y(self, event=None): self.start_x, self.start_y = self.end_x, self.end_y self.end_x, self.end_y = event.x, event.y self.draw_irregular_line() def draw_irregular_line_options(self): self.create_fill_options_combobox() self.create_width_options_combobox() def on_tool_bar_button_clicked(self, button_index): self.selected_tool_bar_function = self.tool_bar_functions[button_index] self.remove_options_from_top_bar() self.display_options_in_the_top_bar() self.bind_mouse() def draw_super_shape(self): points = self.get_super_shape_points( *super_shapes[self.selected_super_shape]) self.current_item = self.canvas.create_polygon(points, outline=self.outline, fill=self.fill, width=self.width) def draw_super_shape_options(self): self.create_super_shapes_options_combobox() self.create_fill_options_combobox() self.create_outline_options_combobox() self.create_width_options_combobox() def create_super_shapes_options_combobox(self): tk.Label(self.top_bar, text='Select shape:').pack(side="left") self.super_shape_combobox = ttk.Combobox( self.top_bar, state='readonly', width=8) self.super_shape_combobox.pack(side="left") self.super_shape_combobox['values'] = tuple( shape for shape in super_shapes.keys()) self.super_shape_combobox.bind( '<>', self.set_selected_super_shape) self.super_shape_combobox.set(self.selected_super_shape) def set_selected_super_shape(self, event=None): self.selected_super_shape = self.super_shape_combobox.get() def get_super_shape_points(self, a, b, m, n1, n2, n3): # https://en.wikipedia.org/wiki/Superformula points = [] for i in self.float_range(0, 2 * math.pi, 0.01): raux = (abs(1 / a * abs(math.cos(m * i / 4))) ** n2 + \ abs(1 / b * abs(math.sin(m * i / 4))) ** n3) r = abs(raux) ** (-1 / n1) x = self.end_x + r * math.cos(i) y = self.end_y + r * math.sin(i) points.extend((x, y)) return points def float_range(self, x, y, step): while x < y: yield x x += step def set_foreground_color(self, event=None): self.foreground = self.get_color_from_chooser( self.foreground, "foreground") self.color_palette.itemconfig( self.foreground_palette, width=0, fill=self.foreground) def set_background_color(self, event=None): self.background = self.get_color_from_chooser( self.background, "background") self.color_palette.itemconfig( self.background_palette, width=0, fill=self.background) def get_color_from_chooser(self, initial_color, color_type="a"): color = colorchooser.askcolor( color=initial_color, title="select {} color".format(color_type) )[-1] if color: return color # dialog has been cancelled else: return initial_color def try_to_set_fill_after_palette_change(self): try: self.set_fill() except: pass def try_to_set_outline_after_palette_change(self): try: self.set_outline() except: pass def display_options_in_the_top_bar(self): self.show_selected_tool_icon_in_top_bar( self.selected_tool_bar_function) options_function_name = "{}_options".format(self.selected_tool_bar_function) func = getattr(self, options_function_name, self.function_not_defined) func() def draw_line_options(self): self.create_fill_options_combobox() self.create_width_options_combobox() self.create_arrow_options_combobox() self.create_dash_options_combobox() def draw_oval_options(self): self.create_fill_options_combobox() self.create_outline_options_combobox() self.create_width_options_combobox() def draw_rectangle_options(self): self.create_fill_options_combobox() self.create_outline_options_combobox() self.create_width_options_combobox() def draw_arc_options(self): self.create_fill_options_combobox() self.create_outline_options_combobox() self.create_width_options_combobox() def draw_triangle_options(self): self.create_fill_options_combobox() self.create_outline_options_combobox() self.create_width_options_combobox() def draw_star_options(self): self.create_number_of_spokes_options_combobox() self.create_fill_options_combobox() self.create_outline_options_combobox() self.create_width_options_combobox() def create_fill_options_combobox(self): tk.Label(self.top_bar, text='Fill:').pack(side="left") self.fill_combobox = ttk.Combobox( self.top_bar, state='readonly', width=5) self.fill_combobox.pack(side="left") self.fill_combobox['values'] = ('none', 'fg', 'bg', 'black', 'white') self.fill_combobox.bind('<>', self.set_fill) self.fill_combobox.set(self.fill) def create_outline_options_combobox(self): tk.Label(self.top_bar, text='Outline:').pack(side="left") self.outline_combobox = ttk.Combobox( self.top_bar, state='readonly', width=5) self.outline_combobox.pack(side="left") self.outline_combobox['values'] = ( 'none', 'fg', 'bg', 'black', 'white') self.outline_combobox.bind('<>', self.set_outline) self.outline_combobox.set(self.outline) def create_number_of_spokes_options_combobox(self): tk.Label(self.top_bar, text='Number of Edges:').pack(side="left") self.number_of_spokes_combobox = ttk.Combobox( self.top_bar, state='readonly', width=3) self.number_of_spokes_combobox.pack(side="left") self.number_of_spokes_combobox[ 'values'] = tuple(i for i in range(5, 50)) self.number_of_spokes_combobox.bind( '<>', self.set_number_of_spokes) self.number_of_spokes_combobox.set(self.number_of_spokes) def create_width_options_combobox(self): tk.Label(self.top_bar, text='Width:').pack(side="left") self.width_combobox = ttk.Combobox( self.top_bar, state='readonly', width=3) self.width_combobox.pack(side="left") self.width_combobox['values'] = ( 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0) self.width_combobox.bind('<>', self.set_width) self.width_combobox.set(self.width) def create_dash_options_combobox(self): tk.Label(self.top_bar, text='Dash:').pack(side="left") self.dash_combobox = ttk.Combobox( self.top_bar, state='readonly', width=5) self.dash_combobox.pack(side="left") self.dash_combobox['values'] = ('none', 'small', 'medium', 'large') self.dash_combobox.bind('<>', self.set_dash) self.dash_combobox.current(0) def create_arrow_options_combobox(self): tk.Label(self.top_bar, text='Arrow:').pack(side="left") self.arrow_combobox = ttk.Combobox( self.top_bar, state='readonly', width=5) self.arrow_combobox.pack(side="left") self.arrow_combobox['values'] = ('none', 'first', 'last', 'both') self.arrow_combobox.bind('<>', self.set_arrow) self.arrow_combobox.current(0) def set_fill(self, event=None): fill_color = self.fill_combobox.get() if fill_color == 'none': self.fill = '' # transparent elif fill_color == 'fg': self.fill = self.foreground elif fill_color == 'bg': self.fill = self.background else: self.fill = fill_color def set_outline(self, event=None): outline_color = self.outline_combobox.get() if outline_color == 'none': self.outline = '' # transparent elif outline_color == 'fg': self.outline = self.foreground elif outline_color == 'bg': self.outline = self.background else: self.outline = outline_color def set_width(self, event): self.width = float(self.width_combobox.get()) def set_number_of_spokes(self, event): self.number_of_spokes = int(self.number_of_spokes_combobox.get()) def set_arrow(self, event): self.arrow = self.arrow_combobox.get() def set_dash(self, event): '''Dash takes value from 1 to 255''' dash_size = self.dash_combobox.get() if dash_size == 'none': self.dash = None elif dash_size == 'small': self.dash = 1 elif dash_size == 'medium': self.dash = 15 elif dash_size == 'large': self.dash = 100 def create_color_palette(self): self.color_palette = tk.Canvas(self.tool_bar, height=55, width=55) self.color_palette.grid(row=10, column=1, columnspan=2, pady=5, padx=3) self.background_palette = self.color_palette.create_rectangle( 15, 15, 48, 48, outline=self.background, fill=self.background) self.foreground_palette = self.color_palette.create_rectangle( 1, 1, 33, 33, outline=self.foreground, fill=self.foreground) self.bind_color_palette() def bind_color_palette(self): self.color_palette.tag_bind( self.background_palette, "", self.set_background_color) self.color_palette.tag_bind( self.foreground_palette, "", self.set_foreground_color) def create_current_coordinate_label(self): self.current_coordinate_label = tk.Label( self.tool_bar, text='x:0\ny: 0 ') self.current_coordinate_label.grid( row=13, column=1, columnspan=2, pady=5, padx=1, sticky='w') def show_current_coordinates(self, event=None): x_coordinate = event.x y_coordinate = event.y coordinate_string = "x:{0}\ny:{1}".format(x_coordinate, y_coordinate) self.current_coordinate_label.config(text=coordinate_string) def function_not_defined(self): pass def execute_selected_method(self): self.current_item = None func = getattr( self, self.selected_tool_bar_function, self.function_not_defined) func() def draw_line(self): self.current_item = self.canvas.create_line( self.start_x, self.start_y, self.end_x, self.end_y, fill=self.fill, width=self.width, arrow=self.arrow, dash=self.dash) def draw_oval(self): self.current_item = self.canvas.create_oval( self.start_x, self.start_y, self.end_x, self.end_y, outline=self.outline, fill=self.fill, width=self.width) def draw_rectangle(self): self.current_item = self.canvas.create_rectangle( self.start_x, self.start_y, self.end_x, self.end_y, outline=self.outline, fill=self.fill, width=self.width) def draw_arc(self): self.current_item = self.canvas.create_arc( self.start_x, self.start_y, self.end_x, self.end_y, outline=self.outline, fill=self.fill, width=self.width) def draw_triangle(self): dx = self.end_x - self.start_x dy = self.end_y - self.start_y z = complex(dx, dy) radius, angle0 = cmath.polar(z) edges = 3 points = list() for edge in range(edges): angle = angle0 + edge * (2 * math.pi) / edges points.append(self.start_x + radius * math.cos(angle)) points.append(self.start_y + radius * math.sin(angle)) self.current_item = self.canvas.create_polygon( points, outline=self.outline, fill=self.fill, width=self.width) def draw_star(self): dx = self.end_x - self.start_x dy = self.end_y - self.start_y z = complex(dx, dy) radius_out, angle0 = cmath.polar(z) radius_in = radius_out / 2 points = list() for edge in range(self.number_of_spokes): angle = angle0 + edge * (2 * math.pi) / self.number_of_spokes points.append(self.start_x + radius_out * math.cos(angle)) points.append(self.start_y + radius_out * math.sin(angle)) angle += math.pi / self.number_of_spokes points.append(self.start_x + radius_in * math.cos(angle)) points.append(self.start_y + radius_in * math.sin(angle)) self.current_item = self.canvas.create_polygon( points, outline=self.outline, fill=self.fill, width=self.width) def create_tool_bar_buttons(self): for index, name in enumerate(self.tool_bar_functions): icon = tk.PhotoImage(file='icons/' + name + '.gif') self.button = tk.Button( self.tool_bar, image=icon, command=lambda index=index: self.on_tool_bar_button_clicked(index)) self.button.grid( row=index // 2, column=1 + index % 2, sticky='nsew') self.button.image = icon def remove_options_from_top_bar(self): for child in self.top_bar.winfo_children(): child.destroy() def show_selected_tool_icon_in_top_bar(self, function_name): display_name = function_name.replace("_", " ").capitalize() + ":" tk.Label(self.top_bar, text=display_name).pack(side="left") photo = tk.PhotoImage( file='icons/' + function_name + '.gif') label = tk.Label(self.top_bar, image=photo) label.image = photo label.pack(side="left") def bind_mouse(self): self.canvas.bind("", self.on_mouse_button_pressed) self.canvas.bind( "", self.on_mouse_button_pressed_motion) self.canvas.bind( "", self.on_mouse_button_released) self.canvas.bind("", self.on_mouse_unpressed_motion) def on_mouse_button_pressed(self, event): self.start_x = self.end_x = self.canvas.canvasx(event.x) self.start_y = self.end_y = self.canvas.canvasy(event.y) self.execute_selected_method() def on_mouse_button_pressed_motion(self, event): self.end_x = self.canvas.canvasx(event.x) self.end_y = self.canvas.canvasy(event.y) self.canvas.delete(self.current_item) self.execute_selected_method() def on_mouse_button_released(self, event): self.end_x = self.canvas.canvasx(event.x) self.end_y = self.canvas.canvasy(event.y) def on_mouse_unpressed_motion(self, event): self.show_current_coordinates(event) def __init__(self, root): super().__init__(root) self.create_gui() self.bind_mouse() def create_gui(self): self.create_menu() self.create_top_bar() self.create_tool_bar() self.create_tool_bar_buttons() self.create_drawing_canvas() self.create_color_palette() self.create_current_coordinate_label() self.bind_menu_accelrator_keys() self.show_selected_tool_icon_in_top_bar("draw_line") self.draw_line_options() def create_menu(self): self.menubar = tk.Menu(self.root) menu_definitions = ( 'File- &New/Ctrl+N/self.on_new_file_menu_clicked, Save/Ctrl+S/self.on_save_menu_clicked, SaveAs/ /self.on_save_as_menu_clicked, sep, Exit/Alt+F4/self.on_close_menu_clicked', 'Edit- Undo/Ctrl+Z/self.on_undo_menu_clicked, sep', 'View- Zoom in//self.on_canvas_zoom_in_menu_clicked,Zoom Out//self.on_canvas_zoom_out_menu_clicked', 'About- About/F1/self.on_about_menu_clicked' ) self.build_menu(menu_definitions) def create_top_bar(self): self.top_bar = tk.Frame(self.root, height=25, relief="raised") self.top_bar.pack(fill="x", side="top", pady=2) def create_tool_bar(self): self.tool_bar = tk.Frame(self.root, relief="raised", width=50) self.tool_bar.pack(fill="y", side="left", pady=3) def create_drawing_canvas(self): self.canvas_frame = tk.Frame(self.root, width=900, height=900) self.canvas_frame.pack(side="right", expand="yes", fill="both") self.canvas = tk.Canvas(self.canvas_frame, background="white", width=500, height=500, scrollregion=(0, 0, 800, 800)) self.create_scroll_bar() self.canvas.pack(side=tk.RIGHT, expand=tk.YES, fill=tk.BOTH) def create_scroll_bar(self): x_scroll = tk.Scrollbar(self.canvas_frame, orient="horizontal") x_scroll.pack(side="bottom", fill="x") x_scroll.config(command=self.canvas.xview) y_scroll = tk.Scrollbar(self.canvas_frame, orient="vertical") y_scroll.pack(side="right", fill="y") y_scroll.config(command=self.canvas.yview) self.canvas.config( xscrollcommand=x_scroll.set, yscrollcommand=y_scroll.set) def bind_menu_accelrator_keys(self): self.root.bind('', self.on_about_menu_clicked) self.root.bind('', self.on_new_file_menu_clicked) self.root.bind('', self.on_new_file_menu_clicked) self.root.bind('', self.on_save_menu_clicked) self.root.bind('', self.on_save_menu_clicked) self.root.bind('', self.on_undo_menu_clicked) self.root.bind('', self.on_undo_menu_clicked) if __name__ == '__main__': root = tk.Tk() app = PaintApplication(root) root.mainloop() ================================================ FILE: Chapter 06/framework.py ================================================ """ Chapter 6: Paint Application Developing a Tiny Framework Tkinter GUI Application Development Blueprints """ import tkinter as tk class Framework(): """ GUIFramework is a class that provides a higher level of abstraction for the development of Tkinter graphic user interfaces (GUIs). Every class that uses this GUI framework must inherit from this class and should pass the root window as an argument to this class by calling the super method as follows: super().__init__(root) Building Menus: To build a menu, call build_menu() method with one argument for menu_definition, where menu_definition is a tuple where each item is a string of the format: 'Top Level Menu Name - MenuItemName/Accelrator/Commandcallback/Underlinenumber'. MenuSeparator is denoted by a string 'sep'. For instance, passing this tuple as an argument to this method menu_definition = ( 'File - &New/Ctrl+N/new_file, &Open/Ctrl+O/openfile, &Save/Ctrl+S/save, Save&As//saveas, sep, Exit/Alt+F4/close', 'Edit - Cut/Ctrl+X/cut, Copy/Ctrl+C/copy, Paste/Ctrl+V/paste, Sep', ) will generate a File and Edit Menu Buttons with listed menu items for each of the buttons. """ menu_items = None def __init__(self, root): self.root = root def build_menu(self, menu_definitions): menu_bar = tk.Menu(self.root) for definition in menu_definitions: menu = tk.Menu(menu_bar, tearoff=0) top_level_menu, pull_down_menus = definition.split('-') menu_items = map(str.strip, pull_down_menus.split(',')) for item in menu_items: self._add_menu_command(menu, item) menu_bar.add_cascade(label=top_level_menu, menu=menu) self.root.config(menu=menu_bar) def _add_menu_command(self, menu, item): if item == 'sep': menu.add_separator() else: menu_label, accelrator_key, command_callback = item.split('/') try: underline = menu_label.index('&') menu_label = menu_label.replace('&', '', 1) except ValueError: underline = None menu.add_command(label=menu_label, underline=underline, accelerator=accelrator_key, command=eval(command_callback)) class TestThisFramework(Framework): def new_file(self): print('new tested OK') def open_file(self): print ('open tested OK') def undo(self): print ('undo tested OK') def options(self): print ('options tested OK') def about(self): print ('about tested OK') if __name__ == '__main__': root = tk.Tk() menu_items = ( 'File- &New/Ctrl+N/self.new_file, &Open/Ctrl+O/self.open_file', 'Edit- Undo/Ctrl+Z/self.undo, sep, Options/Ctrl+T/self.options', 'About- About//self.about' ) app = TestThisFramework(root) app.build_menu(menu_items) root.mainloop() ================================================ FILE: Chapter 06/supershapes.py ================================================ super_shapes = { "shape A": (1.5, 1.5, 5, 2, 7, 7), "shape B": (1.5, 1.5, 3, 5, 18, 18), "shape C": (1.4, 1.4, 4, 2, 4, 13), "shape D": (1.6, 1.6, 7, 3, 4, 17), "shape E": (1.9, 1.9, 7, 3, 6, 6), "shape F": (4, 4, 19, 9, 14, 11), "shape G": (12, 12, 1, 15, 20, 3), "shape H": (1.5, 1.5, 8, 1, 1, 8), "shape I": (1.2, 1.2, 8, 1, 5, 8), "shape J": (8, 8, 3, 6, 6, 6), "shape K": (8, 8, 2, 1, 1, 1), "shape L": (1.1, 1.1, 16, 0.5, 0.5, 16) } ================================================ FILE: Chapter 07/7.01/constants.py ================================================ WINDOW_WIDTH = 560 MODE_SELECTOR_HEIGHT = 50 CONTROLS_FRAME_HEIGHT = 80 KEYBOARD_HEIGHT = 160 SCORE_DISPLAY_HEIGHT = 110 WINDOW_HEIGHT = KEYBOARD_HEIGHT + CONTROLS_FRAME_HEIGHT + MODE_SELECTOR_HEIGHT + SCORE_DISPLAY_HEIGHT CHOICES = ['Scales','Chords','Chord Progressions'] ================================================ FILE: Chapter 07/7.01/view.py ================================================ ''' Chapter 7: Piano Tutor Tkinter GUI Application Development Blueprints ''' from tkinter import Tk, Frame, Button, BOTH, Label, PhotoImage, \ StringVar, OptionMenu, ttk from constants import * class PianoTutor: def __init__(self, root): self.root = root self.root.resizable(False, False) self.root.title('Piano Tutor') self.build_mode_selector_frame() self.build_score_sheet_frame() self.build_controls_frame() self.build_keyboard_frame() self.build_chords_frame() self.build_progressions_frame() self.build_scales_frame() def build_mode_selector_frame(self): self.mode_selector_frame = Frame(self.root, width=WINDOW_WIDTH, height=MODE_SELECTOR_HEIGHT, background='mint cream') self.mode_selector_frame.grid_propagate(False) self.mode_selector = ttk.Combobox(self.mode_selector_frame, values=CHOICES) self.mode_selector.bind('<>', self.on_mode_changed) self.mode_selector.current(0) self.mode_selector.grid( row=0, column=1, columnspan=3, padx=10, pady=10, sticky='nsew', ) self.mode_selector_frame.grid(row=0, column=0) def build_score_sheet_frame(self): self.score_sheet_frame = Frame(self.root, width=WINDOW_WIDTH, height=SCORE_DISPLAY_HEIGHT, background='SteelBlue1') self.score_sheet_frame.grid_propagate(False) Label(self.score_sheet_frame, text='placeholder for score sheet' , background='SteelBlue1').grid(row=1, column=1) self.score_sheet_frame.grid(row=1, column=0) def build_controls_frame(self): self.controls_frame = Frame(self.root, width=WINDOW_WIDTH, height=CONTROLS_FRAME_HEIGHT, background='cornsilk3') self.controls_frame.grid_propagate(False) self.controls_frame.grid(row=2, column=0) def build_keyboard_frame(self): self.keyboard_frame = Frame(self.root, width=WINDOW_WIDTH, height=KEYBOARD_HEIGHT, background='LavenderBlush2') self.keyboard_frame.grid_propagate(False) Label(self.keyboard_frame, text='placeholder for keyboard', background='LavenderBlush2').grid() self.keyboard_frame.grid(row=4, column=0, sticky='nsew') def build_scales_frame(self): self.scales_frame = Frame(self.controls_frame, width=WINDOW_WIDTH, height=CONTROLS_FRAME_HEIGHT, bg='SlateBlue3') Label(self.scales_frame, text='placeholder for scales frame' ).grid(row=1, column=1) self.scales_frame.grid(row=1, column=0, sticky='nsew') def build_chords_frame(self): self.chords_frame = Frame(self.controls_frame, width=WINDOW_WIDTH, height=CONTROLS_FRAME_HEIGHT, bg='cornsilk4') self.chords_frame.grid_propagate(False) Label(self.chords_frame, text='placeholder for chords frame' ).grid(row=1, column=1) self.chords_frame.grid(row=1, column=0, sticky='nsew') def build_progressions_frame(self): self.progressions_frame = Frame(self.controls_frame, width=WINDOW_WIDTH, height=CONTROLS_FRAME_HEIGHT, bg='plum2') self.progressions_frame.grid_propagate(False) Label(self.progressions_frame, text='placeholder for progression frame').grid(row=1, column=1) self.progressions_frame.grid(row=1, column=0, sticky='nsew') def on_mode_changed(self, event): selected_mode = self.mode_selector.get() if selected_mode == 'Scales': self.show_scales_frame() elif selected_mode == 'Chords': self.show_chords_frame() elif selected_mode == 'Chord Progressions': self.show_progressions_frame() def show_scales_frame(self): self.chords_frame.grid_remove() self.progressions_frame.grid_remove() self.scales_frame.grid() def show_chords_frame(self): self.chords_frame.grid() self.progressions_frame.grid_remove() self.scales_frame.grid_remove() def show_progressions_frame(self): self.chords_frame.grid_remove() self.progressions_frame.grid() self.scales_frame.grid_remove() def run(): root = Tk() SCREEN_WIDTH = root.winfo_screenwidth() SCREEN_HEIGHT = root.winfo_screenheight() SCREEN_X_CENTER = (SCREEN_WIDTH - WINDOW_WIDTH) / 2 SCREEN_Y_CENTER = (SCREEN_HEIGHT - WINDOW_HEIGHT) / 2 root.geometry('%dx%d+%d+%d' % (WINDOW_WIDTH, WINDOW_HEIGHT, SCREEN_X_CENTER, SCREEN_Y_CENTER)) root.resizable(False, False) PianoTutor(root) root.mainloop() if __name__ == '__main__': run() ================================================ FILE: Chapter 07/7.02/constants.py ================================================ WINDOW_WIDTH = 560 MODE_SELECTOR_HEIGHT = 50 CONTROLS_FRAME_HEIGHT = 80 KEYBOARD_HEIGHT = 160 SCORE_DISPLAY_HEIGHT = 110 WINDOW_HEIGHT = KEYBOARD_HEIGHT + CONTROLS_FRAME_HEIGHT + \ MODE_SELECTOR_HEIGHT + SCORE_DISPLAY_HEIGHT CHOICES = ['Scales','Chords','Chord Progressions'] WHITE_KEY_IMAGE = '../pictures/white_key.gif' WHITE_KEY_PRESSED_IMAGE = '../pictures/white_key_pressed.gif' BLACK_KEY_IMAGE = '../pictures/black_key.gif' BLACK_KEY_PRESSED_IMAGE = '../pictures/black_key_pressed.gif' WHITE_KEY_NAMES = ['C1','D1', 'E1', 'F1', 'G1','A1', 'B1', \ 'C2','D2', 'E2', 'F2', 'G2','A2', 'B2'] BLACK_KEY_NAMES = ['C#1', 'D#1', 'F#1', 'G#1', 'A#1', 'C#2', 'D#2', \ 'F#2', 'G#2', 'A#2'] WHITE_KEY_X_COORDINATES = [0,40, 80,120, 160, 200, 240,280, 320, 360, \ 400, 440, 480,520] BLACK_KEY_X_COORDINATES = [30,70,150,190, 230, 310, 350, 430,470, 510] ================================================ FILE: Chapter 07/7.02/view.py ================================================ ''' Code 7.02.py Adding the piano keyboard attribute added here: self.keys = [] new methods added here create_key on_key_pressesed on_key_released method modified here build_keyboard_frame ''' from tkinter import Tk, Frame, Button, BOTH, Label, PhotoImage, \ StringVar, OptionMenu, ttk from constants import * class PianoTutor: def __init__(self, root): self.root = root self.root.resizable(False, False) self.root.title('Piano Tutor') self.keys = [] self.build_mode_selector_frame() self.build_score_sheet_frame() self.build_controls_frame() self.build_keyboard_frame() self.build_chords_frame() self.build_progressions_frame() self.build_scales_frame() def build_mode_selector_frame(self): self.mode_selector_frame = Frame(self.root, width=WINDOW_WIDTH, height=MODE_SELECTOR_HEIGHT, background='mint cream') self.mode_selector_frame.grid_propagate(False) self.mode_selector = ttk.Combobox(self.mode_selector_frame, values=CHOICES) self.mode_selector.bind('<>', self.on_mode_changed) self.mode_selector.current(0) self.mode_selector.grid( row=0, column=1, columnspan=3, padx=10, pady=10, sticky='nsew', ) self.mode_selector_frame.grid(row=0, column=0) def build_score_sheet_frame(self): self.score_sheet_frame = Frame(self.root, width=WINDOW_WIDTH, height=SCORE_DISPLAY_HEIGHT, background='SteelBlue1') self.score_sheet_frame.grid_propagate(False) Label(self.score_sheet_frame, text='placeholder for score sheet' , background='SteelBlue1').grid(row=1, column=1) self.score_sheet_frame.grid(row=1, column=0) def build_controls_frame(self): self.controls_frame = Frame(self.root, width=WINDOW_WIDTH, height=CONTROLS_FRAME_HEIGHT, background='cornsilk3') self.controls_frame.grid_propagate(False) self.controls_frame.grid(row=2, column=0) def build_keyboard_frame(self): self.keyboard_frame = Frame(self.root, width=WINDOW_WIDTH, height=KEYBOARD_HEIGHT, background='LavenderBlush2') self.keyboard_frame.grid_propagate(False) self.keyboard_frame.grid(row=4, column=0, sticky='nsew') for (index, key) in enumerate(WHITE_KEY_NAMES): x = WHITE_KEY_X_COORDINATES[index] self.create_key(WHITE_KEY_IMAGE, key, x) for (index, key) in enumerate(BLACK_KEY_NAMES): x = BLACK_KEY_X_COORDINATES[index] self.create_key(BLACK_KEY_IMAGE, key, x) def build_scales_frame(self): self.scales_frame = Frame(self.controls_frame, width=WINDOW_WIDTH, height=CONTROLS_FRAME_HEIGHT, bg='SlateBlue3') Label(self.scales_frame, text='placeholder for scales frame' ).grid(row=1, column=1) self.scales_frame.grid(row=1, column=0, sticky='nsew') def build_chords_frame(self): self.chords_frame = Frame(self.controls_frame, width=WINDOW_WIDTH, height=CONTROLS_FRAME_HEIGHT, bg='cornsilk4') self.chords_frame.grid_propagate(False) Label(self.chords_frame, text='placeholder for chords frame' ).grid(row=1, column=1) self.chords_frame.grid(row=1, column=0, sticky='nsew') def build_progressions_frame(self): self.progressions_frame = Frame(self.controls_frame, width=WINDOW_WIDTH, height=CONTROLS_FRAME_HEIGHT, bg='plum2') self.progressions_frame.grid_propagate(False) Label(self.progressions_frame, text='placeholder for progression frame').grid(row=1, column=1) self.progressions_frame.grid(row=1, column=0, sticky='nsew') def on_mode_changed(self, event): selected_mode = self.mode_selector.get() if selected_mode == 'Scales': self.show_scales_frame() elif selected_mode == 'Chords': self.show_chords_frame() elif selected_mode == 'Chord Progressions': self.show_progressions_frame() def show_scales_frame(self): self.chords_frame.grid_remove() self.progressions_frame.grid_remove() self.scales_frame.grid() def show_chords_frame(self): self.chords_frame.grid() self.progressions_frame.grid_remove() self.scales_frame.grid_remove() def show_progressions_frame(self): self.chords_frame.grid_remove() self.progressions_frame.grid() self.scales_frame.grid_remove() def create_key(self, img, key_name, x_coordinate): key_image = PhotoImage(file=img) label = Label(self.keyboard_frame, image=key_image, border=0) label.image = key_image label.place(x=x_coordinate, y=0) label.name = key_name label.bind('', self.on_key_pressed) label.bind('', self.on_key_released) self.keys.append(label) return label def change_image_to_pressed(self, event): if len(event.widget.name) == 2: img = WHITE_KEY_PRESSED_IMAGE elif len(event.widget.name) == 3: img = BLACK_KEY_PRESSED_IMAGE key_img = PhotoImage(file=img) event.widget.configure(image=key_img) event.widget.image = key_img def change_image_to_unpressed(self, event): if len(event.widget.name) == 2: img = WHITE_KEY_IMAGE elif len(event.widget.name) == 3: img = BLACK_KEY_IMAGE key_img = PhotoImage(file=img) event.widget.configure(image=key_img) event.widget.image = key_img def on_key_pressed(self, event): print (event.widget.name + ' pressed') self.change_image_to_pressed(event) def on_key_released(self, event): print (event.widget.name + ' released') self.change_image_to_unpressed(event) def run(): root = Tk() SCREEN_WIDTH = root.winfo_screenwidth() SCREEN_HEIGHT = root.winfo_screenheight() SCREEN_X_CENTER = (SCREEN_WIDTH - WINDOW_WIDTH) / 2 SCREEN_Y_CENTER = (SCREEN_HEIGHT - WINDOW_HEIGHT) / 2 root.geometry('%dx%d+%d+%d' % (WINDOW_WIDTH, WINDOW_HEIGHT, SCREEN_X_CENTER, SCREEN_Y_CENTER)) root.resizable(False, False) PianoTutor(root) root.mainloop() if __name__ == '__main__': run() ================================================ FILE: Chapter 07/7.03/audio.py ================================================ import simpleaudio as sa from _thread import start_new_thread import time def play_note(note_name): wave_obj = sa.WaveObject.from_wave_file('../sounds/' + note_name + '.wav') wave_obj.play() def play_scale(scale): for note in scale: play_note(note) time.sleep(0.5) def play_scale_in_new_thread(scale): start_new_thread(play_scale,(scale,)) def play_chord(scale): for note in scale: play_note(note) def play_chord_in_new_thread(chord): start_new_thread(play_chord,(chord,)) ================================================ FILE: Chapter 07/7.03/constants.py ================================================ WINDOW_WIDTH = 560 MODE_SELECTOR_HEIGHT = 50 CONTROLS_FRAME_HEIGHT = 80 KEYBOARD_HEIGHT = 160 SCORE_DISPLAY_HEIGHT = 110 WINDOW_HEIGHT = KEYBOARD_HEIGHT + CONTROLS_FRAME_HEIGHT + \ MODE_SELECTOR_HEIGHT + SCORE_DISPLAY_HEIGHT CHOICES = ['Scales','Chords','Chord Progressions'] WHITE_KEY_IMAGE = '../pictures/white_key.gif' WHITE_KEY_PRESSED_IMAGE = '../pictures/white_key_pressed.gif' BLACK_KEY_IMAGE = '../pictures/black_key.gif' BLACK_KEY_PRESSED_IMAGE = '../pictures/black_key_pressed.gif' WHITE_KEY_NAMES = ['C1','D1', 'E1', 'F1', 'G1','A1', 'B1', \ 'C2','D2', 'E2', 'F2', 'G2','A2', 'B2'] BLACK_KEY_NAMES = ['C#1', 'D#1', 'F#1', 'G#1', 'A#1', 'C#2', 'D#2', \ 'F#2', 'G#2', 'A#2'] WHITE_KEY_X_COORDINATES = [0,40, 80,120, 160, 200, 240,280, 320, 360, \ 400, 440, 480,520] BLACK_KEY_X_COORDINATES = [30,70,150,190, 230, 310, 350, 430,470, 510] ================================================ FILE: Chapter 07/7.03/view.py ================================================ ''' Code 7.03.py Playing Audio new import here: from audio import play_note method modified here on_key_pressed ''' from tkinter import Tk, Frame, Button, BOTH, Label, PhotoImage, \ StringVar, OptionMenu, ttk from audio import play_note from constants import * class PianoTutor: def __init__(self, root): self.root = root self.root.resizable(False, False) self.root.title('Piano Tutor') self.keys = [] self.build_mode_selector_frame() self.build_score_sheet_frame() self.build_controls_frame() self.build_keyboard_frame() self.build_chords_frame() self.build_progressions_frame() self.build_scales_frame() def build_mode_selector_frame(self): self.mode_selector_frame = Frame(self.root, width=WINDOW_WIDTH, height=MODE_SELECTOR_HEIGHT, background='mint cream') self.mode_selector_frame.grid_propagate(False) self.mode_selector = ttk.Combobox(self.mode_selector_frame, values=CHOICES) self.mode_selector.bind('<>', self.on_mode_changed) self.mode_selector.current(0) self.mode_selector.grid( row=0, column=1, columnspan=3, padx=10, pady=10, sticky='nsew', ) self.mode_selector_frame.grid(row=0, column=0) def build_score_sheet_frame(self): self.score_sheet_frame = Frame(self.root, width=WINDOW_WIDTH, height=SCORE_DISPLAY_HEIGHT, background='SteelBlue1') self.score_sheet_frame.grid_propagate(False) Label(self.score_sheet_frame, text='placeholder for score sheet' , background='SteelBlue1').grid(row=1, column=1) self.score_sheet_frame.grid(row=1, column=0) def build_controls_frame(self): self.controls_frame = Frame(self.root, width=WINDOW_WIDTH, height=CONTROLS_FRAME_HEIGHT, background='cornsilk3') self.controls_frame.grid_propagate(False) self.controls_frame.grid(row=2, column=0) def build_keyboard_frame(self): self.keyboard_frame = Frame(self.root, width=WINDOW_WIDTH, height=KEYBOARD_HEIGHT, background='LavenderBlush2') self.keyboard_frame.grid_propagate(False) self.keyboard_frame.grid(row=4, column=0, sticky='nsew') for (index, key) in enumerate(WHITE_KEY_NAMES): x = WHITE_KEY_X_COORDINATES[index] self.create_key(WHITE_KEY_IMAGE, key, x) for (index, key) in enumerate(BLACK_KEY_NAMES): x = BLACK_KEY_X_COORDINATES[index] self.create_key(BLACK_KEY_IMAGE, key, x) def build_scales_frame(self): self.scales_frame = Frame(self.controls_frame, width=WINDOW_WIDTH, height=CONTROLS_FRAME_HEIGHT, bg='SlateBlue3') Label(self.scales_frame, text='placeholder for scales frame' ).grid(row=1, column=1) self.scales_frame.grid(row=1, column=0, sticky='nsew') def build_chords_frame(self): self.chords_frame = Frame(self.controls_frame, width=WINDOW_WIDTH, height=CONTROLS_FRAME_HEIGHT, bg='cornsilk4') self.chords_frame.grid_propagate(False) Label(self.chords_frame, text='placeholder for chords frame' ).grid(row=1, column=1) self.chords_frame.grid(row=1, column=0, sticky='nsew') def build_progressions_frame(self): self.progressions_frame = Frame(self.controls_frame, width=WINDOW_WIDTH, height=CONTROLS_FRAME_HEIGHT, bg='plum2') self.progressions_frame.grid_propagate(False) Label(self.progressions_frame, text='placeholder for progression frame').grid(row=1, column=1) self.progressions_frame.grid(row=1, column=0, sticky='nsew') def on_mode_changed(self, event): selected_mode = self.mode_selector.get() if selected_mode == 'Scales': self.show_scales_frame() elif selected_mode == 'Chords': self.show_chords_frame() elif selected_mode == 'Chord Progressions': self.show_progressions_frame() def show_scales_frame(self): self.chords_frame.grid_remove() self.progressions_frame.grid_remove() self.scales_frame.grid() def show_chords_frame(self): self.chords_frame.grid() self.progressions_frame.grid_remove() self.scales_frame.grid_remove() def show_progressions_frame(self): self.chords_frame.grid_remove() self.progressions_frame.grid() self.scales_frame.grid_remove() def create_key(self, img, key_name, x_coordinate): key_image = PhotoImage(file=img) label = Label(self.keyboard_frame, image=key_image, border=0) label.image = key_image label.place(x=x_coordinate, y=0) label.name = key_name label.bind('', self.on_key_pressed) label.bind('', self.on_key_released) self.keys.append(label) return label def change_image_to_pressed(self, event): if len(event.widget.name) == 2: img = WHITE_KEY_PRESSED_IMAGE elif len(event.widget.name) == 3: img = BLACK_KEY_PRESSED_IMAGE key_img = PhotoImage(file=img) event.widget.configure(image=key_img) event.widget.image = key_img def change_image_to_unpressed(self, event): if len(event.widget.name) == 2: img = WHITE_KEY_IMAGE elif len(event.widget.name) == 3: img = BLACK_KEY_IMAGE key_img = PhotoImage(file=img) event.widget.configure(image=key_img) event.widget.image = key_img def on_key_pressed(self, event): play_note(event.widget.name) self.change_image_to_pressed(event) def on_key_released(self, event): print (event.widget.name + ' released') self.change_image_to_unpressed(event) def run(): root = Tk() SCREEN_WIDTH = root.winfo_screenwidth() SCREEN_HEIGHT = root.winfo_screenheight() SCREEN_X_CENTER = (SCREEN_WIDTH - WINDOW_WIDTH) / 2 SCREEN_Y_CENTER = (SCREEN_HEIGHT - WINDOW_HEIGHT) / 2 root.geometry('%dx%d+%d+%d' % (WINDOW_WIDTH, WINDOW_HEIGHT, SCREEN_X_CENTER, SCREEN_Y_CENTER)) root.resizable(False, False) PianoTutor(root) root.mainloop() if __name__ == '__main__': run() ================================================ FILE: Chapter 07/7.04/audio.py ================================================ import simpleaudio as sa from _thread import start_new_thread import time def play_note(note_name): wave_obj = sa.WaveObject.from_wave_file('../sounds/' + note_name + '.wav') wave_obj.play() def play_scale(scale): for note in scale: play_note(note) time.sleep(0.5) def play_scale_in_new_thread(scale): start_new_thread(play_scale,(scale,)) def play_chord(scale): for note in scale: play_note(note) def play_chord_in_new_thread(chord): start_new_thread(play_chord,(chord,)) ================================================ FILE: Chapter 07/7.04/constants.py ================================================ WINDOW_WIDTH = 560 MODE_SELECTOR_HEIGHT = 50 CONTROLS_FRAME_HEIGHT = 80 KEYBOARD_HEIGHT = 160 SCORE_DISPLAY_HEIGHT = 110 WINDOW_HEIGHT = KEYBOARD_HEIGHT + CONTROLS_FRAME_HEIGHT + \ MODE_SELECTOR_HEIGHT + SCORE_DISPLAY_HEIGHT CHOICES = ['Scales','Chords','Chord Progressions'] WHITE_KEY_IMAGE = '../pictures/white_key.gif' WHITE_KEY_PRESSED_IMAGE = '../pictures/white_key_pressed.gif' BLACK_KEY_IMAGE = '../pictures/black_key.gif' BLACK_KEY_PRESSED_IMAGE = '../pictures/black_key_pressed.gif' ALL_KEYS = ['C1','C#1','D1','D#1','E1','F1','F#1','G1','G#1','A1', \ 'A#1','B1', 'C2','C#2','D2','D#2','E2','F2','F#2','G2',\ 'G#2','A2','A#2','B2'] KEYS = ['C','C#','D','D#','E','F','F#','G','G#','A','A#','B'] WHITE_KEY_NAMES = ['C1','D1', 'E1', 'F1', 'G1','A1', 'B1', \ 'C2','D2', 'E2', 'F2', 'G2','A2', 'B2'] BLACK_KEY_NAMES = ['C#1', 'D#1', 'F#1', 'G#1', 'A#1', 'C#2', 'D#2', \ 'F#2', 'G#2', 'A#2'] WHITE_KEY_X_COORDINATES = [0,40, 80,120, 160, 200, 240,280, 320, 360, \ 400, 440, 480,520] BLACK_KEY_X_COORDINATES = [30,70,150,190, 230, 310, 350, 430,470, 510] SCALES_JSON_FILE = '../json/scales.json' ================================================ FILE: Chapter 07/7.04/view.py ================================================ ''' 7.04 Implementing the Scales Tutor new imports here: import json from collections import OrderedDict from audio import play_scale_in_new_thread method modified here __init___ - added call to self.load_json_files() build_scales_frame - added two combobox methods defined here: on_scale_changed on_scale_key_changed find_scale highlight_list_of_keys ''' from tkinter import Tk, Frame, Button, BOTH, Label, PhotoImage, \ StringVar, OptionMenu, ttk import json from collections import OrderedDict from audio import play_note, play_scale_in_new_thread from constants import * class PianoTutor: def __init__(self, root): self.root = root self.root.resizable(False, False) self.root.title('Piano Tutor') self.keys = [] self.load_json_files() self.keys_to_highlight = [] self.build_mode_selector_frame() self.build_score_sheet_frame() self.build_controls_frame() self.build_keyboard_frame() self.build_chords_frame() self.build_progressions_frame() self.build_scales_frame() self.find_scale() def load_json_files(self): with open(SCALES_JSON_FILE, 'r') as f: self.scales = json.load(f, object_pairs_hook=OrderedDict) def on_scale_changed(self, event): self.remove_all_key_highlights() self.find_scale(event) def on_scale_key_changed(self, event): self.remove_all_key_highlights() self.find_scale(event) def find_scale(self, event=None): self.selected_scale = self.scale_selector.get() self.scale_selected_key = self.scale_key_selector.get() index_of_selected_key = KEYS.index(self.scale_selected_key) self.keys_to_highlight = [ ALL_KEYS[i+index_of_selected_key] \ for i in self.scales[self.selected_scale]] self.highlight_list_of_keys(self.keys_to_highlight) play_scale_in_new_thread(self.keys_to_highlight) def highlight_list_of_keys(self, key_names): for key in key_names: self.highlight_key(key) def highlight_key(self, key_name): if len(key_name) == 2: img = WHITE_KEY_PRESSED_IMAGE elif len(key_name) == 3: img = BLACK_KEY_PRESSED_IMAGE key_img = PhotoImage(file=img) for widget in self.keys: if widget.name == key_name: widget.configure(image=key_img) widget.image = key_img def remove_key_highlight(self, key_name): if len(key_name) == 2: img = WHITE_KEY_IMAGE elif len(key_name) == 3: img = BLACK_KEY_IMAGE key_img = PhotoImage(file=img) for widget in self.keys: if widget.name == key_name: widget.configure(image=key_img) widget.image = key_img def remove_all_key_highlights(self): for key in self.keys_to_highlight: self.remove_key_highlight(key) self.keys_to_highlight = [] def build_mode_selector_frame(self): self.mode_selector_frame = Frame(self.root, width=WINDOW_WIDTH, height=MODE_SELECTOR_HEIGHT) self.mode_selector_frame.grid_propagate(False) self.mode_selector = ttk.Combobox(self.mode_selector_frame, values=CHOICES) self.mode_selector.bind('<>', self.on_mode_changed) self.mode_selector.current(0) self.mode_selector.grid( row=0, column=1, columnspan=3, padx=10, pady=10, sticky='nsew', ) self.mode_selector_frame.grid(row=0, column=0) def build_score_sheet_frame(self): self.score_sheet_frame = Frame(self.root, width=WINDOW_WIDTH, height=SCORE_DISPLAY_HEIGHT, background='SteelBlue1') self.score_sheet_frame.grid_propagate(False) Label(self.score_sheet_frame, text='placeholder for score sheet' , background='SteelBlue1').grid(row=1, column=1) self.score_sheet_frame.grid(row=1, column=0) def build_controls_frame(self): self.controls_frame = Frame(self.root, width=WINDOW_WIDTH, height=CONTROLS_FRAME_HEIGHT) self.controls_frame.grid_propagate(False) self.controls_frame.grid(row=2, column=0) def build_keyboard_frame(self): self.keyboard_frame = Frame(self.root, width=WINDOW_WIDTH, height=KEYBOARD_HEIGHT, background='LavenderBlush2') self.keyboard_frame.grid_propagate(False) self.keyboard_frame.grid(row=4, column=0, sticky='nsew') for (index, key) in enumerate(WHITE_KEY_NAMES): x = WHITE_KEY_X_COORDINATES[index] self.create_key(WHITE_KEY_IMAGE, key, x) for (index, key) in enumerate(BLACK_KEY_NAMES): x = BLACK_KEY_X_COORDINATES[index] self.create_key(BLACK_KEY_IMAGE, key, x) def build_scales_frame(self): self.scales_frame = Frame(self.controls_frame, width=WINDOW_WIDTH, height=CONTROLS_FRAME_HEIGHT ) self.scales_frame.grid(row=1, column=0, sticky='nsew') Label(self.scales_frame, text='Select scale').grid(row=0, \ column=1, sticky='w', padx=10,pady=1) self.scale_selector = ttk.Combobox(self.scales_frame, \ values=[k for k in self.scales.keys()]) self.scale_selector.current(0) self.scale_selector.bind("<>", \ self.on_scale_changed) self.scale_selector.grid(row=1, column=1, sticky='e', padx=10,\ pady=10) Label(self.scales_frame, text='in the key of').grid(row=0, \ column=2, sticky='w', padx=10,pady=1) self.scale_key_selector = ttk.Combobox(self.scales_frame, \ values=[k for k in KEYS]) self.scale_key_selector.current(0) self.scale_key_selector.bind("<>", \ self.on_scale_key_changed) self.scale_key_selector.grid(row=1, column=2, sticky='e', \ padx=10, pady=10) def build_chords_frame(self): self.chords_frame = Frame(self.controls_frame, width=WINDOW_WIDTH, height=CONTROLS_FRAME_HEIGHT ) self.chords_frame.grid_propagate(False) Label(self.chords_frame, text='placeholder for chords frame' ).grid(row=1, column=1) self.chords_frame.grid(row=1, column=0, sticky='nsew') def build_progressions_frame(self): self.progressions_frame = Frame(self.controls_frame, width=WINDOW_WIDTH, height=CONTROLS_FRAME_HEIGHT) self.progressions_frame.grid_propagate(False) Label(self.progressions_frame, text='placeholder for progression frame').grid(row=1, column=1) self.progressions_frame.grid(row=1, column=0, sticky='nsew') def on_mode_changed(self, event): selected_mode = self.mode_selector.get() if selected_mode == 'Scales': self.show_scales_frame() elif selected_mode == 'Chords': self.show_chords_frame() elif selected_mode == 'Chord Progressions': self.show_progressions_frame() def show_scales_frame(self): self.chords_frame.grid_remove() self.progressions_frame.grid_remove() self.scales_frame.grid() def show_chords_frame(self): self.chords_frame.grid() self.progressions_frame.grid_remove() self.scales_frame.grid_remove() def show_progressions_frame(self): self.chords_frame.grid_remove() self.progressions_frame.grid() self.scales_frame.grid_remove() def create_key(self, img, key_name, x_coordinate): key_image = PhotoImage(file=img) label = Label(self.keyboard_frame, image=key_image, border=0) label.image = key_image label.place(x=x_coordinate, y=0) label.name = key_name label.bind('', self.on_key_pressed) label.bind('', self.on_key_released) self.keys.append(label) return label def change_image_to_pressed(self, event): if len(event.widget.name) == 2: img = WHITE_KEY_PRESSED_IMAGE elif len(event.widget.name) == 3: img = BLACK_KEY_PRESSED_IMAGE key_img = PhotoImage(file=img) event.widget.configure(image=key_img) event.widget.image = key_img def change_image_to_unpressed(self, event): if len(event.widget.name) == 2: img = WHITE_KEY_IMAGE elif len(event.widget.name) == 3: img = BLACK_KEY_IMAGE key_img = PhotoImage(file=img) event.widget.configure(image=key_img) event.widget.image = key_img def on_key_pressed(self, event): play_note(event.widget.name) self.change_image_to_pressed(event) def on_key_released(self, event): print (event.widget.name + ' released') self.change_image_to_unpressed(event) def run(): root = Tk() SCREEN_WIDTH = root.winfo_screenwidth() SCREEN_HEIGHT = root.winfo_screenheight() SCREEN_X_CENTER = (SCREEN_WIDTH - WINDOW_WIDTH) / 2 SCREEN_Y_CENTER = (SCREEN_HEIGHT - WINDOW_HEIGHT) / 2 root.geometry('%dx%d+%d+%d' % (WINDOW_WIDTH, WINDOW_HEIGHT, SCREEN_X_CENTER, SCREEN_Y_CENTER)) root.resizable(False, False) PianoTutor(root) root.mainloop() if __name__ == '__main__': run() ================================================ FILE: Chapter 07/7.05/audio.py ================================================ import simpleaudio as sa from _thread import start_new_thread import time def play_note(note_name): wave_obj = sa.WaveObject.from_wave_file('../sounds/' + note_name + '.wav') wave_obj.play() def play_scale(scale): for note in scale: play_note(note) time.sleep(0.5) def play_scale_in_new_thread(scale): start_new_thread(play_scale,(scale,)) def play_chord(scale): for note in scale: play_note(note) def play_chord_in_new_thread(chord): start_new_thread(play_chord,(chord,)) ================================================ FILE: Chapter 07/7.05/constants.py ================================================ WINDOW_WIDTH = 560 MODE_SELECTOR_HEIGHT = 50 CONTROLS_FRAME_HEIGHT = 80 KEYBOARD_HEIGHT = 160 SCORE_DISPLAY_HEIGHT = 110 WINDOW_HEIGHT = KEYBOARD_HEIGHT + CONTROLS_FRAME_HEIGHT + \ MODE_SELECTOR_HEIGHT + SCORE_DISPLAY_HEIGHT CHOICES = ['Scales','Chords','Chord Progressions'] WHITE_KEY_IMAGE = '../pictures/white_key.gif' WHITE_KEY_PRESSED_IMAGE = '../pictures/white_key_pressed.gif' BLACK_KEY_IMAGE = '../pictures/black_key.gif' BLACK_KEY_PRESSED_IMAGE = '../pictures/black_key_pressed.gif' ALL_KEYS = ['C1','C#1','D1','D#1','E1','F1','F#1','G1','G#1','A1', \ 'A#1','B1', 'C2','C#2','D2','D#2','E2','F2','F#2','G2',\ 'G#2','A2','A#2','B2'] KEYS = ['C','C#','D','D#','E','F','F#','G','G#','A','A#','B'] WHITE_KEY_NAMES = ['C1','D1', 'E1', 'F1', 'G1','A1', 'B1', \ 'C2','D2', 'E2', 'F2', 'G2','A2', 'B2'] BLACK_KEY_NAMES = ['C#1', 'D#1', 'F#1', 'G#1', 'A#1', 'C#2', 'D#2', \ 'F#2', 'G#2', 'A#2'] WHITE_KEY_X_COORDINATES = [0,40, 80,120, 160, 200, 240,280, 320, 360, \ 400, 440, 480,520] BLACK_KEY_X_COORDINATES = [30,70,150,190, 230, 310, 350, 430,470, 510] SCALES_JSON_FILE = '../json/scales.json' CHORDS_JSON_FILE = '../json/chords.json' ================================================ FILE: Chapter 07/7.05/view.py ================================================ ''' 7.05 Implementing the Chords TUtor new imports here: from audio import play_chord_in_new_thread method modified here load_json_files() # read the chords.json file in self.chords variable build_chords_frame() # build the GUI component - added combobox for selecting chord and its key on_mode_changed - added call to self.remove_all_key_highlights() and self.find_chord() methods defined here: on_chords_key_changed on_chord_changed find_chord ''' from tkinter import Tk, Frame, Button, BOTH, Label, PhotoImage, \ StringVar, OptionMenu, ttk import json from collections import OrderedDict from audio import play_note, play_scale_in_new_thread, \ play_chord_in_new_thread from constants import * class PianoTutor: def __init__(self, root): self.root = root self.root.resizable(False, False) self.root.title('Piano Tutor') self.keys = [] self.load_json_files() self.keys_to_highlight = [] self.build_mode_selector_frame() self.build_score_sheet_frame() self.build_controls_frame() self.build_keyboard_frame() self.build_chords_frame() self.build_progressions_frame() self.build_scales_frame() self.find_scale() def load_json_files(self): with open(SCALES_JSON_FILE, 'r') as f: self.scales = json.load(f, object_pairs_hook=OrderedDict) with open(CHORDS_JSON_FILE, 'r') as f: self.chords = json.load(f, object_pairs_hook=OrderedDict) def on_chord_changed(self, event): self.remove_all_key_highlights() self.find_chord(event) def on_chords_key_changed(self, event): self.remove_all_key_highlights() self.find_chord(event) def find_chord(self, event=None): self.selected_chord = self.chords_selector.get() self.chords_selected_key = self.chords_key_selector.get() index_of_selected_key = KEYS.index(self.chords_selected_key) self.keys_to_highlight = [ ALL_KEYS[i+index_of_selected_key] for \ i in self.chords[self.selected_chord]] self.highlight_list_of_keys(self.keys_to_highlight) play_chord_in_new_thread(self.keys_to_highlight) def on_scale_changed(self, event): self.remove_all_key_highlights() self.find_scale(event) def on_scale_key_changed(self, event): self.remove_all_key_highlights() self.find_scale(event) def find_scale(self, event=None): self.selected_scale = self.scale_selector.get() self.scale_selected_key = self.scale_key_selector.get() index_of_selected_key = KEYS.index(self.scale_selected_key) self.keys_to_highlight = [ ALL_KEYS[i+index_of_selected_key] \ for i in self.scales[self.selected_scale]] self.highlight_list_of_keys(self.keys_to_highlight) play_scale_in_new_thread(self.keys_to_highlight) def highlight_list_of_keys(self, key_names): for key in key_names: self.highlight_key(key) def highlight_key(self, key_name): if len(key_name) == 2: img = WHITE_KEY_PRESSED_IMAGE elif len(key_name) == 3: img = BLACK_KEY_PRESSED_IMAGE key_img = PhotoImage(file=img) for widget in self.keys: if widget.name == key_name: widget.configure(image=key_img) widget.image = key_img def remove_key_highlight(self, key_name): if len(key_name) == 2: img = WHITE_KEY_IMAGE elif len(key_name) == 3: img = BLACK_KEY_IMAGE key_img = PhotoImage(file=img) for widget in self.keys: if widget.name == key_name: widget.configure(image=key_img) widget.image = key_img def remove_all_key_highlights(self): for key in self.keys_to_highlight: self.remove_key_highlight(key) self.keys_to_highlight = [] def build_mode_selector_frame(self): self.mode_selector_frame = Frame(self.root, width=WINDOW_WIDTH, height=MODE_SELECTOR_HEIGHT) self.mode_selector_frame.grid_propagate(False) self.mode_selector = ttk.Combobox(self.mode_selector_frame, values=CHOICES) self.mode_selector.bind('<>', self.on_mode_changed) self.mode_selector.current(0) self.mode_selector.grid( row=0, column=1, columnspan=3, padx=10, pady=10, sticky='nsew', ) self.mode_selector_frame.grid(row=0, column=0) def build_score_sheet_frame(self): self.score_sheet_frame = Frame(self.root, width=WINDOW_WIDTH, height=SCORE_DISPLAY_HEIGHT, background='SteelBlue1') self.score_sheet_frame.grid_propagate(False) Label(self.score_sheet_frame, text='placeholder for score sheet' , background='SteelBlue1').grid(row=1, column=1) self.score_sheet_frame.grid(row=1, column=0) def build_controls_frame(self): self.controls_frame = Frame(self.root, width=WINDOW_WIDTH, height=CONTROLS_FRAME_HEIGHT) self.controls_frame.grid_propagate(False) self.controls_frame.grid(row=2, column=0) def build_keyboard_frame(self): self.keyboard_frame = Frame(self.root, width=WINDOW_WIDTH, height=KEYBOARD_HEIGHT, background='LavenderBlush2') self.keyboard_frame.grid_propagate(False) self.keyboard_frame.grid(row=4, column=0, sticky='nsew') for (index, key) in enumerate(WHITE_KEY_NAMES): x = WHITE_KEY_X_COORDINATES[index] self.create_key(WHITE_KEY_IMAGE, key, x) for (index, key) in enumerate(BLACK_KEY_NAMES): x = BLACK_KEY_X_COORDINATES[index] self.create_key(BLACK_KEY_IMAGE, key, x) def build_scales_frame(self): self.scales_frame = Frame(self.controls_frame, width=WINDOW_WIDTH, height=CONTROLS_FRAME_HEIGHT ) self.scales_frame.grid(row=1, column=0, sticky='nsew') Label(self.scales_frame, text='Select scale').grid(row=0, \ column=1, sticky='w', padx=10,pady=1) self.scale_selector = ttk.Combobox(self.scales_frame, \ values=[k for k in self.scales.keys()]) self.scale_selector.current(0) self.scale_selector.bind("<>", \ self.on_scale_changed) self.scale_selector.grid(row=1, column=1, sticky='e', padx=10,\ pady=10) Label(self.scales_frame, text='in the key of').grid(row=0, \ column=2, sticky='w', padx=10,pady=1) self.scale_key_selector = ttk.Combobox(self.scales_frame, \ values=[k for k in KEYS]) self.scale_key_selector.current(0) self.scale_key_selector.bind("<>", \ self.on_scale_key_changed) self.scale_key_selector.grid(row=1, column=2, sticky='e', \ padx=10, pady=10) def build_chords_frame(self): self.chords_frame = Frame(self.controls_frame, width=WINDOW_WIDTH, height=CONTROLS_FRAME_HEIGHT ) self.chords_frame.grid_propagate(False) self.chords_frame.grid(row=1, column=0, sticky='nsew') Label(self.chords_frame, text='Select Chord').grid(row=0, \ column=1, sticky='w', padx=10,pady=1) self.chords_selector = ttk.Combobox(self.chords_frame, \ values=[k for k in self.chords.keys()]) self.chords_selector.current(0) self.chords_selector.bind("<>", \ self.on_chord_changed) self.chords_selector.grid(row=1, column=1, sticky='e', \ padx=10, pady=10) Label(self.chords_frame, text='in the key of').grid(row=0, \ column=2, sticky='w', padx=10,pady=1) self.chords_key_selector = ttk.Combobox(self.chords_frame, \ values=[k for k in KEYS]) self.chords_key_selector.current(0) self.chords_key_selector.bind("<>", \ self.on_chords_key_changed) self.chords_key_selector.grid(row=1, column=2, sticky='e', padx=10, pady=10) def build_progressions_frame(self): self.progressions_frame = Frame(self.controls_frame, width=WINDOW_WIDTH, height=CONTROLS_FRAME_HEIGHT) self.progressions_frame.grid_propagate(False) Label(self.progressions_frame, text='placeholder for progression frame').grid(row=1, column=1) self.progressions_frame.grid(row=1, column=0, sticky='nsew') def on_mode_changed(self, event): self.remove_all_key_highlights() selected_mode = self.mode_selector.get() if selected_mode == 'Scales': self.show_scales_frame() self.find_scale() elif selected_mode == 'Chords': self.show_chords_frame() self.find_chord() elif selected_mode == 'Chord Progressions': self.show_progressions_frame() def show_scales_frame(self): self.chords_frame.grid_remove() self.progressions_frame.grid_remove() self.scales_frame.grid() def show_chords_frame(self): self.chords_frame.grid() self.progressions_frame.grid_remove() self.scales_frame.grid_remove() def show_progressions_frame(self): self.chords_frame.grid_remove() self.progressions_frame.grid() self.scales_frame.grid_remove() def create_key(self, img, key_name, x_coordinate): key_image = PhotoImage(file=img) label = Label(self.keyboard_frame, image=key_image, border=0) label.image = key_image label.place(x=x_coordinate, y=0) label.name = key_name label.bind('', self.on_key_pressed) label.bind('', self.on_key_released) self.keys.append(label) return label def change_image_to_pressed(self, event): if len(event.widget.name) == 2: img = WHITE_KEY_PRESSED_IMAGE elif len(event.widget.name) == 3: img = BLACK_KEY_PRESSED_IMAGE key_img = PhotoImage(file=img) event.widget.configure(image=key_img) event.widget.image = key_img def change_image_to_unpressed(self, event): if len(event.widget.name) == 2: img = WHITE_KEY_IMAGE elif len(event.widget.name) == 3: img = BLACK_KEY_IMAGE key_img = PhotoImage(file=img) event.widget.configure(image=key_img) event.widget.image = key_img def on_key_pressed(self, event): play_note(event.widget.name) self.change_image_to_pressed(event) def on_key_released(self, event): print (event.widget.name + ' released') self.change_image_to_unpressed(event) def run(): root = Tk() SCREEN_WIDTH = root.winfo_screenwidth() SCREEN_HEIGHT = root.winfo_screenheight() SCREEN_X_CENTER = (SCREEN_WIDTH - WINDOW_WIDTH) / 2 SCREEN_Y_CENTER = (SCREEN_HEIGHT - WINDOW_HEIGHT) / 2 root.geometry('%dx%d+%d+%d' % (WINDOW_WIDTH, WINDOW_HEIGHT, SCREEN_X_CENTER, SCREEN_Y_CENTER)) root.resizable(False, False) PianoTutor(root) root.mainloop() if __name__ == '__main__': run() ================================================ FILE: Chapter 07/7.06/audio.py ================================================ import simpleaudio as sa from _thread import start_new_thread import time def play_note(note_name): wave_obj = sa.WaveObject.from_wave_file('../sounds/' + note_name + '.wav') wave_obj.play() def play_scale(scale): for note in scale: play_note(note) time.sleep(0.5) def play_scale_in_new_thread(scale): start_new_thread(play_scale,(scale,)) def play_chord(scale): for note in scale: play_note(note) def play_chord_in_new_thread(chord): start_new_thread(play_chord,(chord,)) ================================================ FILE: Chapter 07/7.06/constants.py ================================================ WINDOW_WIDTH = 560 MODE_SELECTOR_HEIGHT = 50 CONTROLS_FRAME_HEIGHT = 100 KEYBOARD_HEIGHT = 160 SCORE_DISPLAY_HEIGHT = 110 WINDOW_HEIGHT = KEYBOARD_HEIGHT + CONTROLS_FRAME_HEIGHT + \ MODE_SELECTOR_HEIGHT + SCORE_DISPLAY_HEIGHT CHOICES = ['Scales','Chords','Chord Progressions'] WHITE_KEY_IMAGE = '../pictures/white_key.gif' WHITE_KEY_PRESSED_IMAGE = '../pictures/white_key_pressed.gif' BLACK_KEY_IMAGE = '../pictures/black_key.gif' BLACK_KEY_PRESSED_IMAGE = '../pictures/black_key_pressed.gif' ALL_KEYS = ['C1','C#1','D1','D#1','E1','F1','F#1','G1','G#1','A1', \ 'A#1','B1', 'C2','C#2','D2','D#2','E2','F2','F#2','G2',\ 'G#2','A2','A#2','B2'] KEYS = ['C','C#','D','D#','E','F','F#','G','G#','A','A#','B'] WHITE_KEY_NAMES = ['C1','D1', 'E1', 'F1', 'G1','A1', 'B1', \ 'C2','D2', 'E2', 'F2', 'G2','A2', 'B2'] BLACK_KEY_NAMES = ['C#1', 'D#1', 'F#1', 'G#1', 'A#1', 'C#2', 'D#2', \ 'F#2', 'G#2', 'A#2'] WHITE_KEY_X_COORDINATES = [0,40, 80,120, 160, 200, 240,280, 320, 360, \ 400, 440, 480,520] BLACK_KEY_X_COORDINATES = [30,70,150,190, 230, 310, 350, 430,470, 510] SCALES_JSON_FILE = '../json/scales.json' CHORDS_JSON_FILE = '../json/chords.json' PROGRESSIONS_JSON_FILE = '../json/progressions.json' ROMAN_TO_NUMBER = { 'I':0, 'II': 2, 'III':4, 'IV':5, 'V': 7, 'VI':9, 'VII': 11, 'i':0, 'ii': 2, 'iii':4, 'iv':5, 'v': 7, 'vi':9, 'vii': 11 } ================================================ FILE: Chapter 07/7.06/view.py ================================================ ''' 7.06 Implementing the Chord Progression tutor new imports here: import math from functools import partial new attributes here: self.progression_buttons method modified here load_json_files() # read the progressions.json file in self.chords variable build_progressions_frame() - added three combobox to the layout on_mode_changed added call to show_progression_buttons() methods defined here: on_progression_scale_changed on_progression_key_changed on_progression_changed destroy_current_progression_buttons show_progression_buttons on_progression_button_clicked ''' #!/usr/bin/python # -*- coding: utf-8 -*- from tkinter import Tk, Frame, Button, BOTH, Label, PhotoImage, \ StringVar, OptionMenu, ttk, font import math from functools import partial import json from collections import OrderedDict from audio import play_note, play_scale_in_new_thread, \ play_chord_in_new_thread from constants import * class PianoTutor: def __init__(self, root): self.root = root self.root.resizable(False, False) self.root.title('Piano Tutor') self.keys = [] self.progression_buttons = [] self.load_json_files() self.keys_to_highlight = [] self.build_mode_selector_frame() self.build_score_sheet_frame() self.build_controls_frame() self.build_keyboard_frame() self.build_chords_frame() self.build_progressions_frame() self.build_scales_frame() self.find_scale() def load_json_files(self): with open(SCALES_JSON_FILE, 'r') as f: self.scales = json.load(f, object_pairs_hook=OrderedDict) with open(CHORDS_JSON_FILE, 'r') as f: self.chords = json.load(f, object_pairs_hook=OrderedDict) with open(PROGRESSIONS_JSON_FILE, 'r') as f: self.progressions = json.load(f, object_pairs_hook=OrderedDict) def on_progression_scale_changed(self, event): selected_progression_scale = \ self.progression_scale_selector.get() progressions = [k for k in self.progressions[selected_progression_scale].keys()] self.progression_selector['values'] = progressions self.progression_selector.current(0) self.show_progression_buttons() def on_progression_key_changed(self, event): self.show_progression_buttons() def on_progression_changed(self, event): self.show_progression_buttons() def destroy_current_progression_buttons(self): for buttons in self.progression_buttons: buttons.destroy() def show_progression_buttons(self): self.destroy_current_progression_buttons() selected_progression_scale = \ self.progression_scale_selector.get() selected_progression = self.progression_selector.get().split('-' ) self.progression_buttons = [] for i in range(len(selected_progression)): self.progression_buttons.append(Button(self.progressions_frame, text=selected_progression[i], command=partial(self.on_progression_button_clicked, i))) sticky = ('W' if i == 0 else 'E') col = (i if i > 1 else 1) self.progression_buttons[i].grid(column=col, row=2, sticky=sticky, padx=10) def on_progression_button_clicked(self, i): self.remove_all_key_highlights() selected_progression = self.progression_selector.get().split('-' )[i] if any(x.isupper() for x in selected_progression): selected_chord = 'Major' else: selected_chord = 'Minor' key_offset = ROMAN_TO_NUMBER[selected_progression] selected_key = self.progression_key_selector.get() index_of_selected_key = (KEYS.index(selected_key) + key_offset) \ % 12 self.keys_to_highlight = [ALL_KEYS[j + index_of_selected_key] for j in self.chords[selected_chord]] self.highlight_list_of_keys(self.keys_to_highlight) play_chord_in_new_thread(self.keys_to_highlight) def on_chord_changed(self, event): self.remove_all_key_highlights() self.find_chord(event) def on_chords_key_changed(self, event): self.remove_all_key_highlights() self.find_chord(event) def find_chord(self, event=None): self.selected_chord = self.chords_selector.get() self.chords_selected_key = self.chords_key_selector.get() index_of_selected_key = KEYS.index(self.chords_selected_key) self.keys_to_highlight = [ALL_KEYS[i + index_of_selected_key] for i in self.chords[self.selected_chord]] self.highlight_list_of_keys(self.keys_to_highlight) play_chord_in_new_thread(self.keys_to_highlight) def on_scale_changed(self, event): self.remove_all_key_highlights() self.find_scale(event) def on_scale_key_changed(self, event): self.remove_all_key_highlights() self.find_scale(event) def find_scale(self, event=None): self.selected_scale = self.scale_selector.get() self.scale_selected_key = self.scale_key_selector.get() index_of_selected_key = KEYS.index(self.scale_selected_key) self.keys_to_highlight = [ALL_KEYS[i + index_of_selected_key] for i in self.scales[self.selected_scale]] self.highlight_list_of_keys(self.keys_to_highlight) play_scale_in_new_thread(self.keys_to_highlight) def highlight_list_of_keys(self, key_names): for key in key_names: self.highlight_key(key) def highlight_key(self, key_name): if len(key_name) == 2: img = WHITE_KEY_PRESSED_IMAGE elif len(key_name) == 3: img = BLACK_KEY_PRESSED_IMAGE key_img = PhotoImage(file=img) for widget in self.keys: if widget.name == key_name: widget.configure(image=key_img) widget.image = key_img def remove_key_highlight(self, key_name): if len(key_name) == 2: img = WHITE_KEY_IMAGE elif len(key_name) == 3: img = BLACK_KEY_IMAGE key_img = PhotoImage(file=img) for widget in self.keys: if widget.name == key_name: widget.configure(image=key_img) widget.image = key_img def remove_all_key_highlights(self): for key in self.keys_to_highlight: self.remove_key_highlight(key) self.keys_to_highlight = [] def build_mode_selector_frame(self): self.mode_selector_frame = Frame(self.root, width=WINDOW_WIDTH, height=MODE_SELECTOR_HEIGHT) self.mode_selector_frame.grid_propagate(False) self.mode_selector = ttk.Combobox(self.mode_selector_frame, values=CHOICES) self.mode_selector.bind('<>', self.on_mode_changed) self.mode_selector.current(0) self.mode_selector.grid(row=0, column=1, columnspan=3, padx=10, pady=10, sticky='nsew') self.mode_selector_frame.grid(row=0, column=0) def build_score_sheet_frame(self): self.score_sheet_frame = Frame(self.root, width=WINDOW_WIDTH, height=SCORE_DISPLAY_HEIGHT, background='SteelBlue1') self.score_sheet_frame.grid_propagate(False) Label(self.score_sheet_frame, text='placeholder for score sheet' , background='SteelBlue1').grid(row=1, column=1) self.score_sheet_frame.grid(row=1, column=0) def build_controls_frame(self): self.controls_frame = Frame(self.root, width=WINDOW_WIDTH, height=CONTROLS_FRAME_HEIGHT) self.controls_frame.grid_propagate(False) self.controls_frame.grid(row=2, column=0) def build_keyboard_frame(self): self.keyboard_frame = Frame(self.root, width=WINDOW_WIDTH, height=KEYBOARD_HEIGHT, background='LavenderBlush2') self.keyboard_frame.grid_propagate(False) self.keyboard_frame.grid(row=4, column=0, sticky='nsew') for (index, key) in enumerate(WHITE_KEY_NAMES): x = WHITE_KEY_X_COORDINATES[index] self.create_key(WHITE_KEY_IMAGE, key, x) for (index, key) in enumerate(BLACK_KEY_NAMES): x = BLACK_KEY_X_COORDINATES[index] self.create_key(BLACK_KEY_IMAGE, key, x) def build_scales_frame(self): self.scales_frame = Frame(self.controls_frame, width=WINDOW_WIDTH, height=CONTROLS_FRAME_HEIGHT) self.scales_frame.grid(row=1, column=0, sticky='nsew') Label(self.scales_frame, text='Select scale').grid(row=0, column=1, sticky='w', padx=10, pady=1) self.scale_selector = ttk.Combobox(self.scales_frame, values=[k for k in self.scales.keys()]) self.scale_selector.current(0) self.scale_selector.bind('<>', self.on_scale_changed) self.scale_selector.grid(row=1, column=1, sticky='e', padx=10, pady=10) Label(self.scales_frame, text='in the key of').grid(row=0, column=2, sticky='w', padx=10, pady=1) self.scale_key_selector = ttk.Combobox(self.scales_frame, values=[k for k in KEYS]) self.scale_key_selector.current(0) self.scale_key_selector.bind('<>', self.on_scale_key_changed) self.scale_key_selector.grid(row=1, column=2, sticky='e', padx=10, pady=10) def build_chords_frame(self): self.chords_frame = Frame(self.controls_frame, width=WINDOW_WIDTH, height=CONTROLS_FRAME_HEIGHT) self.chords_frame.grid_propagate(False) self.chords_frame.grid(row=1, column=0, sticky='nsew') Label(self.chords_frame, text='Select Chord').grid(row=0, column=1, sticky='w', padx=10, pady=1) self.chords_selector = ttk.Combobox(self.chords_frame, values=[k for k in self.chords.keys()]) self.chords_selector.current(0) self.chords_selector.bind('<>', self.on_chord_changed) self.chords_selector.grid(row=1, column=1, sticky='e', padx=10, pady=10) Label(self.chords_frame, text='in the key of').grid(row=0, column=2, sticky='w', padx=10, pady=1) self.chords_key_selector = ttk.Combobox(self.chords_frame, values=[k for k in KEYS]) self.chords_key_selector.current(0) self.chords_key_selector.bind('<>', self.on_chords_key_changed) self.chords_key_selector.grid(row=1, column=2, sticky='e', padx=10, pady=10) def build_progressions_frame(self): self.progressions_frame = Frame(self.controls_frame, width=WINDOW_WIDTH, height=CONTROLS_FRAME_HEIGHT) self.progressions_frame.grid_propagate(False) self.progressions_frame.grid(row=1, column=0, sticky='nsew') Label(self.progressions_frame, text='Select Scale').grid(row=0, column=1, sticky='w', padx=10, pady=1) Label(self.progressions_frame, text='Select Progression' ).grid(row=0, column=2, sticky='w', padx=10, pady=1) Label(self.progressions_frame, text='in the Key of' ).grid(row=0, column=3, sticky='w', padx=10, pady=1) self.progression_scale_selector = \ ttk.Combobox(self.progressions_frame, values=[k for k in self.progressions.keys()], width=18) self.progression_scale_selector.bind('<>', self.on_progression_scale_changed) self.progression_scale_selector.current(0) self.progression_scale_selector.grid(row=1, column=1, sticky='w' , padx=10, pady=10) self.progression_selector = \ ttk.Combobox(self.progressions_frame, values=[k for k in self.progressions['Major'].keys()], width=18) self.progression_selector.bind('<>', self.on_progression_changed) self.progression_selector.current(0) self.progression_selector.grid(row=1, column=2, sticky='w', padx=10, pady=10) self.progression_key_selector = \ ttk.Combobox(self.progressions_frame, values=KEYS, width=18) self.progression_key_selector.current(0) self.progression_key_selector.bind('<>', self.on_progression_key_changed) self.progression_key_selector.grid(row=1, column=3, sticky='w', padx=10, pady=10) def on_mode_changed(self, event): self.remove_all_key_highlights() selected_mode = self.mode_selector.get() if selected_mode == 'Scales': self.show_scales_frame() self.find_scale() elif selected_mode == 'Chords': self.show_chords_frame() self.find_chord() elif selected_mode == 'Chord Progressions': self.show_progressions_frame() self.show_progression_buttons() def show_scales_frame(self): self.chords_frame.grid_remove() self.progressions_frame.grid_remove() self.scales_frame.grid() def show_chords_frame(self): self.chords_frame.grid() self.progressions_frame.grid_remove() self.scales_frame.grid_remove() def show_progressions_frame(self): self.chords_frame.grid_remove() self.progressions_frame.grid() self.scales_frame.grid_remove() def create_key(self, img, key_name, x_coordinate): key_image = PhotoImage(file=img) label = Label(self.keyboard_frame, image=key_image, border=0) label.image = key_image label.place(x=x_coordinate, y=0) label.name = key_name label.bind('', self.on_key_pressed) label.bind('', self.on_key_released) self.keys.append(label) return label def change_image_to_pressed(self, event): if len(event.widget.name) == 2: img = WHITE_KEY_PRESSED_IMAGE elif len(event.widget.name) == 3: img = BLACK_KEY_PRESSED_IMAGE key_img = PhotoImage(file=img) event.widget.configure(image=key_img) event.widget.image = key_img def change_image_to_unpressed(self, event): if len(event.widget.name) == 2: img = WHITE_KEY_IMAGE elif len(event.widget.name) == 3: img = BLACK_KEY_IMAGE key_img = PhotoImage(file=img) event.widget.configure(image=key_img) event.widget.image = key_img def on_key_pressed(self, event): play_note(event.widget.name) self.change_image_to_pressed(event) def on_key_released(self, event): self.change_image_to_unpressed(event) def run(): root = Tk() SCREEN_WIDTH = root.winfo_screenwidth() SCREEN_HEIGHT = root.winfo_screenheight() SCREEN_X_CENTER = (SCREEN_WIDTH - WINDOW_WIDTH) / 2 SCREEN_Y_CENTER = (SCREEN_HEIGHT - WINDOW_HEIGHT) / 2 root.geometry('%dx%d+%d+%d' % (WINDOW_WIDTH, WINDOW_HEIGHT, SCREEN_X_CENTER, SCREEN_Y_CENTER)) root.resizable(False, False) PianoTutor(root) root.mainloop() if __name__ == '__main__': run() ================================================ FILE: Chapter 07/7.07/audio.py ================================================ import simpleaudio as sa from _thread import start_new_thread import time def play_note(note_name): wave_obj = sa.WaveObject.from_wave_file('../sounds/' + note_name + '.wav') wave_obj.play() def play_scale(scale): for note in scale: play_note(note) time.sleep(0.5) def play_scale_in_new_thread(scale): start_new_thread(play_scale,(scale,)) def play_chord(scale): for note in scale: play_note(note) def play_chord_in_new_thread(chord): start_new_thread(play_chord,(chord,)) ================================================ FILE: Chapter 07/7.07/constants.py ================================================ WINDOW_WIDTH = 560 MODE_SELECTOR_HEIGHT = 50 CONTROLS_FRAME_HEIGHT = 100 KEYBOARD_HEIGHT = 160 SCORE_DISPLAY_HEIGHT = 110 WINDOW_HEIGHT = KEYBOARD_HEIGHT + CONTROLS_FRAME_HEIGHT + \ MODE_SELECTOR_HEIGHT + SCORE_DISPLAY_HEIGHT CHOICES = ['Scales','Chords','Chord Progressions'] WHITE_KEY_IMAGE = '../pictures/white_key.gif' WHITE_KEY_PRESSED_IMAGE = '../pictures/white_key_pressed.gif' BLACK_KEY_IMAGE = '../pictures/black_key.gif' BLACK_KEY_PRESSED_IMAGE = '../pictures/black_key_pressed.gif' ALL_KEYS = ['C1','C#1','D1','D#1','E1','F1','F#1','G1','G#1','A1', \ 'A#1','B1', 'C2','C#2','D2','D#2','E2','F2','F#2','G2',\ 'G#2','A2','A#2','B2'] KEYS = ['C','C#','D','D#','E','F','F#','G','G#','A','A#','B'] WHITE_KEY_NAMES = ['C1','D1', 'E1', 'F1', 'G1','A1', 'B1', \ 'C2','D2', 'E2', 'F2', 'G2','A2', 'B2'] BLACK_KEY_NAMES = ['C#1', 'D#1', 'F#1', 'G#1', 'A#1', 'C#2', 'D#2', \ 'F#2', 'G#2', 'A#2'] WHITE_KEY_X_COORDINATES = [0,40, 80,120, 160, 200, 240,280, 320, 360, \ 400, 440, 480,520] BLACK_KEY_X_COORDINATES = [30,70,150,190, 230, 310, 350, 430,470, 510] SCALES_JSON_FILE = '../json/scales.json' CHORDS_JSON_FILE = '../json/chords.json' PROGRESSIONS_JSON_FILE = '../json/progressions.json' ROMAN_TO_NUMBER = { 'I':0, 'II': 2, 'III':4, 'IV':5, 'V': 7, 'VI':9, 'VII': 11, 'i':0, 'ii': 2, 'iii':4, 'iv':5, 'v': 7, 'vi':9, 'vii': 11 } ================================================ FILE: Chapter 07/7.07/score_maker.py ================================================ from tkinter import * import itertools class ScoreMaker: NOTES = ['C1','D1', 'E1', 'F1', 'G1','A1', 'B1', 'C2','D2', 'E2', 'F2', 'G2','A2', 'B2'] def __init__(self, container): self.canvas = Canvas(container, width=500, height = 110) self.canvas.grid(row=0, column = 1) container.update_idletasks() self.canvas_width = self.canvas.winfo_width() self.sharp_image = PhotoImage(file='../pictures/sharp.gif') self.treble_clef_image = PhotoImage(file='../pictures/treble-clef.gif') self.x_counter = itertools.count(start=50, step=30) def _clean_score_sheet(self): self.x_counter = itertools.count(start=50, step=30) self.canvas.delete("all") def _create_treble_staff(self): self._draw_five_lines() self.canvas.create_image(10, 20, image=self.treble_clef_image, anchor=NW) def draw_chord(self, chord): self._clean_score_sheet() self._create_treble_staff() for note in chord: self._draw_single_note(note, is_in_chord=True) def _draw_five_lines(self): w = self.canvas_width self.canvas.create_line(0,40,w,40, fill="#555") self.canvas.create_line(0,50,w,50, fill="#555") self.canvas.create_line(0,60,w,60, fill="#555") self.canvas.create_line(0,70,w,70, fill="#555") self.canvas.create_line(0,80,w,80, fill="#555") def draw_notes(self, notes): self._clean_score_sheet() self._create_treble_staff() for note in notes: self._draw_single_note(note) def _draw_single_note(self, note, is_in_chord=False): is_sharp = "#" in note note = note.replace("#","") radius = 9 if is_in_chord: x = 75 else: x = next(self.x_counter) i = self.NOTES.index(note) y = 85-(5*i) self.canvas.create_oval(x,y,x+radius, y+ radius, fill="#555") if is_sharp: self.canvas.create_image(x-10,y, image=self.sharp_image, anchor=NW) if note=="C1": self.canvas.create_line(x-5,90,x+15, 90, fill="#555") elif note=="G2": self.canvas.create_line(x-5,35,x+15, 35, fill="#555") elif note=="A2": self.canvas.create_line(x-5,35,x+15, 35, fill="#555") elif note=="B2": self.canvas.create_line(x-5,35,x+15, 35, fill="#555") self.canvas.create_line(x-5,25,x+15, 25, fill="#555") if __name__ == "__main__": root = Tk() s = ScoreMaker(root) #notes = ['C1','D1', 'E1', 'F1', 'G1','A1', 'B1', 'C2','D2', 'E2', 'F2', 'G2','A2', 'B2'] #s.draw_notes(notes) chord = ['C1', 'E1', 'G1', ] s.draw_chord(chord) root.mainloop() ================================================ FILE: Chapter 07/7.07/view.py ================================================ ''' 7.07 Implementing the Score Maker new imports here: from score_maker import ScoreMaker methods modified here build_score_sheet_frame # instantiated ScoreMaker on_progression_button_clicked find_chord find_scale ''' from tkinter import Tk, Frame, Button, BOTH, Label, PhotoImage, \ StringVar, OptionMenu, ttk, font from functools import partial import json from collections import OrderedDict from audio import play_note, play_scale_in_new_thread, \ play_chord_in_new_thread from constants import * from score_maker import ScoreMaker class PianoTutor: def __init__(self, root): self.root = root self.root.resizable(False, False) self.root.title('Piano Tutor') self.keys = [] self.progression_buttons = [] self.load_json_files() self.keys_to_highlight = [] self.build_mode_selector_frame() self.build_score_sheet_frame() self.build_controls_frame() self.build_keyboard_frame() self.build_chords_frame() self.build_progressions_frame() self.build_scales_frame() self.find_scale() def load_json_files(self): with open(SCALES_JSON_FILE, 'r') as f: self.scales = json.load(f, object_pairs_hook=OrderedDict) with open(CHORDS_JSON_FILE, 'r') as f: self.chords = json.load(f, object_pairs_hook=OrderedDict) with open(PROGRESSIONS_JSON_FILE, 'r') as f: self.progressions = json.load(f, object_pairs_hook=OrderedDict) def on_progression_scale_changed(self, event): selected_progression_scale = \ self.progression_scale_selector.get() progressions = [k for k in self.progressions[selected_progression_scale].keys()] self.progression_selector['values'] = progressions self.progression_selector.current(0) self.show_progression_buttons() def on_progression_key_changed(self, event): self.show_progression_buttons() def on_progression_changed(self, event): self.show_progression_buttons() def destroy_current_progression_buttons(self): for buttons in self.progression_buttons: buttons.destroy() def show_progression_buttons(self): self.destroy_current_progression_buttons() selected_progression_scale = \ self.progression_scale_selector.get() selected_progression = self.progression_selector.get().split('-' ) self.progression_buttons = [] for i in range(len(selected_progression)): self.progression_buttons.append(Button(self.progressions_frame, text=selected_progression[i], command=partial(self.on_progression_button_clicked, i))) sticky = ('W' if i == 0 else 'E') col = (i if i > 1 else 1) self.progression_buttons[i].grid(column=col, row=2, sticky=sticky, padx=10) def on_progression_button_clicked(self, i): self.remove_all_key_highlights() selected_progression = self.progression_selector.get().split('-' )[i] if any(x.isupper() for x in selected_progression): selected_chord = 'Major' else: selected_chord = 'Minor' key_offset = ROMAN_TO_NUMBER[selected_progression] selected_key = self.progression_key_selector.get() index_of_selected_key = (KEYS.index(selected_key) + key_offset) \ % 12 self.keys_to_highlight = [ALL_KEYS[j + index_of_selected_key] for j in self.chords[selected_chord]] self.score_maker.draw_chord(self.keys_to_highlight) self.highlight_list_of_keys(self.keys_to_highlight) play_chord_in_new_thread(self.keys_to_highlight) def on_chord_changed(self, event): self.remove_all_key_highlights() self.find_chord(event) def on_chords_key_changed(self, event): self.remove_all_key_highlights() self.find_chord(event) def find_chord(self, event=None): self.selected_chord = self.chords_selector.get() self.chords_selected_key = self.chords_key_selector.get() index_of_selected_key = KEYS.index(self.chords_selected_key) self.keys_to_highlight = [ALL_KEYS[i + index_of_selected_key] for i in self.chords[self.selected_chord]] self.score_maker.draw_chord(self.keys_to_highlight) self.highlight_list_of_keys(self.keys_to_highlight) play_chord_in_new_thread(self.keys_to_highlight) def on_scale_changed(self, event): self.remove_all_key_highlights() self.find_scale(event) def on_scale_key_changed(self, event): self.remove_all_key_highlights() self.find_scale(event) def find_scale(self, event=None): self.selected_scale = self.scale_selector.get() self.scale_selected_key = self.scale_key_selector.get() index_of_selected_key = KEYS.index(self.scale_selected_key) self.keys_to_highlight = [ALL_KEYS[i + index_of_selected_key] for i in self.scales[self.selected_scale]] self.score_maker.draw_notes(self.keys_to_highlight) self.highlight_list_of_keys(self.keys_to_highlight) play_scale_in_new_thread(self.keys_to_highlight) def highlight_list_of_keys(self, key_names): for key in key_names: self.highlight_key(key) def highlight_key(self, key_name): if len(key_name) == 2: img = WHITE_KEY_PRESSED_IMAGE elif len(key_name) == 3: img = BLACK_KEY_PRESSED_IMAGE key_img = PhotoImage(file=img) for widget in self.keys: if widget.name == key_name: widget.configure(image=key_img) widget.image = key_img def remove_key_highlight(self, key_name): if len(key_name) == 2: img = WHITE_KEY_IMAGE elif len(key_name) == 3: img = BLACK_KEY_IMAGE key_img = PhotoImage(file=img) for widget in self.keys: if widget.name == key_name: widget.configure(image=key_img) widget.image = key_img def remove_all_key_highlights(self): for key in self.keys_to_highlight: self.remove_key_highlight(key) self.keys_to_highlight = [] def build_mode_selector_frame(self): self.mode_selector_frame = Frame(self.root, width=WINDOW_WIDTH, height=MODE_SELECTOR_HEIGHT) self.mode_selector_frame.grid_propagate(False) self.mode_selector = ttk.Combobox(self.mode_selector_frame, values=CHOICES) self.mode_selector.bind('<>', self.on_mode_changed) self.mode_selector.current(0) self.mode_selector.grid(row=0, column=1, columnspan=3, padx=10, pady=10, sticky='nsew') self.mode_selector_frame.grid(row=0, column=0) def build_score_sheet_frame(self): self.score_sheet_frame = Frame(self.root, width=WINDOW_WIDTH, height=SCORE_DISPLAY_HEIGHT) self.score_sheet_frame.grid_propagate(False) self.score_sheet_frame.grid(row=1, column=0) self.score_maker = ScoreMaker(self.score_sheet_frame) def build_controls_frame(self): self.controls_frame = Frame(self.root, width=WINDOW_WIDTH, height=CONTROLS_FRAME_HEIGHT) self.controls_frame.grid_propagate(False) self.controls_frame.grid(row=2, column=0) def build_keyboard_frame(self): self.keyboard_frame = Frame(self.root, width=WINDOW_WIDTH, height=KEYBOARD_HEIGHT, background='LavenderBlush2') self.keyboard_frame.grid_propagate(False) self.keyboard_frame.grid(row=4, column=0, sticky='nsew') for (index, key) in enumerate(WHITE_KEY_NAMES): x = WHITE_KEY_X_COORDINATES[index] self.create_key(WHITE_KEY_IMAGE, key, x) for (index, key) in enumerate(BLACK_KEY_NAMES): x = BLACK_KEY_X_COORDINATES[index] self.create_key(BLACK_KEY_IMAGE, key, x) def build_scales_frame(self): self.scales_frame = Frame(self.controls_frame, width=WINDOW_WIDTH, height=CONTROLS_FRAME_HEIGHT) self.scales_frame.grid(row=1, column=0, sticky='nsew') Label(self.scales_frame, text='Select scale').grid(row=0, column=1, sticky='w', padx=10, pady=1) self.scale_selector = ttk.Combobox(self.scales_frame, values=[k for k in self.scales.keys()]) self.scale_selector.current(0) self.scale_selector.bind('<>', self.on_scale_changed) self.scale_selector.grid(row=1, column=1, sticky='e', padx=10, pady=10) Label(self.scales_frame, text='in the key of').grid(row=0, column=2, sticky='w', padx=10, pady=1) self.scale_key_selector = ttk.Combobox(self.scales_frame, values=[k for k in KEYS]) self.scale_key_selector.current(0) self.scale_key_selector.bind('<>', self.on_scale_key_changed) self.scale_key_selector.grid(row=1, column=2, sticky='e', padx=10, pady=10) def build_chords_frame(self): self.chords_frame = Frame(self.controls_frame, width=WINDOW_WIDTH, height=CONTROLS_FRAME_HEIGHT) self.chords_frame.grid_propagate(False) self.chords_frame.grid(row=1, column=0, sticky='nsew') Label(self.chords_frame, text='Select Chord').grid(row=0, column=1, sticky='w', padx=10, pady=1) self.chords_selector = ttk.Combobox(self.chords_frame, values=[k for k in self.chords.keys()]) self.chords_selector.current(0) self.chords_selector.bind('<>', self.on_chord_changed) self.chords_selector.grid(row=1, column=1, sticky='e', padx=10, pady=10) Label(self.chords_frame, text='in the key of').grid(row=0, column=2, sticky='w', padx=10, pady=1) self.chords_key_selector = ttk.Combobox(self.chords_frame, values=[k for k in KEYS]) self.chords_key_selector.current(0) self.chords_key_selector.bind('<>', self.on_chords_key_changed) self.chords_key_selector.grid(row=1, column=2, sticky='e', padx=10, pady=10) def build_progressions_frame(self): self.progressions_frame = Frame(self.controls_frame, width=WINDOW_WIDTH, height=CONTROLS_FRAME_HEIGHT) self.progressions_frame.grid_propagate(False) self.progressions_frame.grid(row=1, column=0, sticky='nsew') Label(self.progressions_frame, text='Select Scale').grid(row=0, column=1, sticky='w', padx=10, pady=1) Label(self.progressions_frame, text='Select Progression' ).grid(row=0, column=2, sticky='w', padx=10, pady=1) Label(self.progressions_frame, text='in the Key of' ).grid(row=0, column=3, sticky='w', padx=10, pady=1) self.progression_scale_selector = \ ttk.Combobox(self.progressions_frame, values=[k for k in self.progressions.keys()], width=18) self.progression_scale_selector.bind('<>', self.on_progression_scale_changed) self.progression_scale_selector.current(0) self.progression_scale_selector.grid(row=1, column=1, sticky='w' , padx=10, pady=10) self.progression_selector = \ ttk.Combobox(self.progressions_frame, values=[k for k in self.progressions['Major'].keys()], width=18) self.progression_selector.bind('<>', self.on_progression_changed) self.progression_selector.current(0) self.progression_selector.grid(row=1, column=2, sticky='w', padx=10, pady=10) self.progression_key_selector = \ ttk.Combobox(self.progressions_frame, values=KEYS, width=18) self.progression_key_selector.current(0) self.progression_key_selector.bind('<>', self.on_progression_key_changed) self.progression_key_selector.grid(row=1, column=3, sticky='w', padx=10, pady=10) def on_mode_changed(self, event): self.remove_all_key_highlights() selected_mode = self.mode_selector.get() if selected_mode == 'Scales': self.show_scales_frame() self.find_scale() elif selected_mode == 'Chords': self.show_chords_frame() self.find_chord() elif selected_mode == 'Chord Progressions': self.show_progressions_frame() self.show_progression_buttons() def show_scales_frame(self): self.chords_frame.grid_remove() self.progressions_frame.grid_remove() self.scales_frame.grid() def show_chords_frame(self): self.chords_frame.grid() self.progressions_frame.grid_remove() self.scales_frame.grid_remove() def show_progressions_frame(self): self.chords_frame.grid_remove() self.progressions_frame.grid() self.scales_frame.grid_remove() def create_key(self, img, key_name, x_coordinate): key_image = PhotoImage(file=img) label = Label(self.keyboard_frame, image=key_image, border=0) label.image = key_image label.place(x=x_coordinate, y=0) label.name = key_name label.bind('', self.on_key_pressed) label.bind('', self.on_key_released) self.keys.append(label) return label def change_image_to_pressed(self, event): if len(event.widget.name) == 2: img = WHITE_KEY_PRESSED_IMAGE elif len(event.widget.name) == 3: img = BLACK_KEY_PRESSED_IMAGE key_img = PhotoImage(file=img) event.widget.configure(image=key_img) event.widget.image = key_img def change_image_to_unpressed(self, event): if len(event.widget.name) == 2: img = WHITE_KEY_IMAGE elif len(event.widget.name) == 3: img = BLACK_KEY_IMAGE key_img = PhotoImage(file=img) event.widget.configure(image=key_img) event.widget.image = key_img def on_key_pressed(self, event): play_note(event.widget.name) self.change_image_to_pressed(event) def on_key_released(self, event): self.change_image_to_unpressed(event) def run(): root = Tk() SCREEN_WIDTH = root.winfo_screenwidth() SCREEN_HEIGHT = root.winfo_screenheight() SCREEN_X_CENTER = (SCREEN_WIDTH - WINDOW_WIDTH) / 2 SCREEN_Y_CENTER = (SCREEN_HEIGHT - WINDOW_HEIGHT) / 2 root.geometry('%dx%d+%d+%d' % (WINDOW_WIDTH, WINDOW_HEIGHT, SCREEN_X_CENTER, SCREEN_Y_CENTER)) root.resizable(False, False) PianoTutor(root) root.mainloop() if __name__ == '__main__': run() ================================================ FILE: Chapter 07/handle_widget_resize.py ================================================ ''' Chapter 7 Handling Widget Resize ''' from tkinter import Tk, Label, Pack root= Tk() label = Label(root, text = 'I am a Frame', bg='red') label.pack(fill='both', expand=True) def on_label_resized(event): print('New Width', label.winfo_width()) print('New Height', label.winfo_height()) label.bind("", on_label_resized) root.mainloop() ================================================ FILE: Chapter 07/json/chords.json ================================================ { "Major" : [0, 4, 7], "Minor" : [0, 3, 7], "Sus4" : [0, 5, 7], "5" : [0, 4, 6], "Diminished" : [0, 3, 6], "Augmented" : [0, 4, 8], "Major6" : [0, 4, 7, 9], "Minor6" : [0, 3, 7, 8], "9 or dom7" : [0, 4, 7, 9], "7sus4" : [0, 5, 7, 9], "Minor7" : [0, 3, 7, 9], "Major7" : [0, 4, 7, 10], "Major7#4" : [0, 4, 8, 10], "MinMaj7" : [0, 3, 7, 10], "7b5" : [0, 4, 6, 9], "Minor7b5" : [0, 3, 6, 9], "Aug7" : [0, 4, 8, 10], "Dim7" : [0, 3, 6, 9] } ================================================ FILE: Chapter 07/json/progressions.json ================================================ { "Major": { "I-IV-V": [ "0", "5", "7" ], "ii-V-I": [ "2", "7", "0" ], "I-V-vi-IV": [ "0", "7", "9", "5" ], "vi-V-IV-V": [ "9", "7", "5", "7" ], "I-vi-IV-V": [ "0", "9", "5", "7" ], "I-IV-vi-V": [ "0", "5","9", "7" ], "I-V-IV-V": [ "0", "7", "5", "7" ], "vi-ii-v-I": [ "9", "2", "7","0" ], "I-vi-ii-V": [ "0","9", "2", "7" ], "vi-IV-I-V": [ "9","5","0", "7" ], "i-VI-III-VII": [ "0", "9","4","11"], "I-IV-ii-V": ["0", "5","2","7" ], "vi-V-IV-iii": ["9","7", "5","4"], "IV-I-V-iv": ["5", "0","7","5"], "I-ii-vi-IV": ["0","2","9","5"], "I-iii-vi-IV": [ "0", "4","9","5"], "I-V-ii-IV": ["0", "7", "2", "5" ], "ii-IV-I-V": ["2", "5", "0", "7" ] }, "Minor": { "i-VI-VII": [ "0", "9", "11"], "i-iv-VII": [ "0", "5", "11"], "i-iv-v": [ "0", "5", "7" ], "i-VI-III-VII": [ "0", "9", "4","11" ], "ii-v-i": [ "2","7", "0" ], "i-iv-v-i": ["0", "5", "7", "0" ], "VI-VII-i-i": [ "9", "11", "0", "0" ], "i-VII-VI-VII": ["0", "11","9", "11" ], "i-iv-i": ["0", "5", "0"] } } ================================================ FILE: Chapter 07/json/scales.json ================================================ { "Major": [ 0, 2, 4, 5, 7, 9, 11 ], "Minor": [ 0, 2, 3, 5, 7, 8, 10 ], "Harmonic minor": [ 0, 2, 3, 5, 7, 8, 11 ], "Melodic minor": [ 0, 2, 3, 5, 7, 9, 11 ], "Major blues": [ 0, 2, 3, 4, 7, 9 ], "Minor blues": [ 0, 3, 5, 6, 7, 10 ], "Major pentatonic": [ 0, 2, 4, 7, 9 ], "Minor pentatonic": [ 0, 3, 5, 7, 10 ], "Lydian": [ 0, 2, 4, 6, 7, 9, 11 ], "Phyrgian": [ 0, 1, 3, 5, 7, 8, 10 ], "Arabic": [ 0, 1, 4, 5, 7, 8, 11 ], "Aeolian": [ 0, 2, 3, 5, 7, 8, 10 ], "Dorian": [ 0, 2, 3, 5, 7, 9, 10 ], "Enigmatic": [ 0, 1, 4, 6, 8, 10, 11 ], "Locrian": [0, 1, 3, 5, 6, 8, 10 ], "Mixolydian": [ 0, 2, 4, 5, 7, 9, 10], "Whole tone": [0, 2, 4,6, 8, 10] } ================================================ FILE: Chapter 07/nonresponsive.py ================================================ ''' Chapter 7 A note on responsiveness This is an example of nonresponsive widgets Run this program and resize the window. Notice that the buttons remain fixed in size. If the window size is made smaller, some of the buttons disappear from view ''' from tkinter import Tk, Button root = Tk() for x in range(10): btn = Button(root, text=x ) btn.grid(column=x, row=1, sticky='nsew') root.mainloop() ================================================ FILE: Chapter 07/responsive.py ================================================ ''' Chapter 7 A demonstration of a responsive window using Grid.rowconfigure and Grid.columnconfigure ''' from tkinter import Tk, Button, Grid root = Tk() for x in range(10): btn = Button(root, text=x ) btn.grid(column=x, row=1, sticky='nsew') Grid.rowconfigure(root, 2, weight=x) Grid.columnconfigure(root, 2, weight=x) root.mainloop() ================================================ FILE: Chapter 08/8.01_screensaver.py ================================================ """ Code illustration: 8.01 Screensaver Tkinter GUI Application Development Blueprints """ from tkinter import Tk, Canvas, BOTH from random import randint class RandomBall: def __init__(self, canvas): self.canvas = canvas self.screen_width = canvas.winfo_screenwidth() self.screen_height = canvas.winfo_screenheight() self.create_ball() def create_ball(self): self.generate_random_attributes() self.create_oval() def generate_random_attributes(self): self.radius = r = randint(40, 70) self.x_coordinate = randint(r, self.screen_width - r) self.y_coordinate = randint(r, self.screen_height - r) self.x_velocity = randint(6, 12) self.y_velocity = randint(6, 12) self.color = self.generate_random_color() def generate_random_color(self): r = lambda: randint(0, 0xffff) return '#{:04x}{:04x}{:04x}'.format(r(), r(), r()) def create_oval(self): x1 = self.x_coordinate - self.radius y1 = self.y_coordinate - self.radius x2 = self.x_coordinate + self.radius y2 = self.y_coordinate + self.radius self.ball = self.canvas.create_oval( x1, y1, x2, y2, fill=self.color, outline=self.color) def move_ball(self): self.check_screen_bounds() self.x_coordinate += self.x_velocity self.y_coordinate += self.y_velocity self.canvas.move(self.ball, self.x_velocity, self.y_velocity) def check_screen_bounds(self): r = self.radius if not r < self.y_coordinate < self.screen_height - r: self.y_velocity = -self.y_velocity if not r < self.x_coordinate < self.screen_width - r: self.x_velocity = -self.x_velocity class ScreenSaver: balls = [] def __init__(self, number_of_balls): self.root = Tk() self.number_of_balls = number_of_balls self.root.attributes('-fullscreen', True) self.root.attributes('-alpha', 0.1) self.root.wm_attributes('-alpha',0.1) self.quit_on_interaction() self.create_screensaver() self.root.mainloop() def create_screensaver(self): self.create_canvas() self.add_balls_to_canvas() self.animate_balls() def create_canvas(self): self.canvas = Canvas(self.root) self.canvas.pack(expand=1, fill=BOTH) def add_balls_to_canvas(self): for i in range(self.number_of_balls): self.balls.append(RandomBall(self.canvas)) def quit_on_interaction(self): for seq in ('', '', ''): self.root.bind(seq, self.quit_screensaver) def animate_balls(self): for ball in self.balls: ball.move_ball() self.root.after(30, self.animate_balls) def quit_screensaver(self, event): self.root.destroy() if __name__ == "__main__": ScreenSaver(number_of_balls=18) ================================================ FILE: Chapter 08/8.02_pie_chart.py ================================================ """ Code illustration: 8.02 Pie chart Tkinter GUI Application Development Blueprints """ import tkinter root = tkinter.Tk() total_value_to_represent_by_pie_chart = 1000 def angle(n): return 360.0 * n / total_value_to_represent_by_pie_chart tkinter.Label(root, text='Pie Chart').pack() canvas = tkinter.Canvas(width=154, height=154) canvas.pack() canvas.create_arc((2, 2, 152, 152), fill="#FAF402", outline="#FAF402", start=angle(0), extent=angle(200)) canvas.create_arc((2, 2, 152, 152), fill="#00AC36", outline="#00AC36", start=angle(200), extent=angle(300)) canvas.create_arc((2, 2, 152, 152), fill="#7A0871", outline="#7A0871", start=angle(500), extent=angle(150)) canvas.create_arc((2, 2, 152, 152), fill="#E00022", outline="#E00022", start=angle(650), extent=angle(200)) canvas.create_arc((2, 2, 152, 152), fill="#294994", outline="#294994", start=angle(850), extent=angle(150)) root.mainloop() ================================================ FILE: Chapter 08/8.03_bar_graph.py ================================================ """ Code illustration: 8.03 Bar graph Tkinter GUI Application Development Blueprints """ import tkinter import random root = tkinter.Tk() canvas_width = 250 canvas_height = 220 bar_width = 20 canv = tkinter.Canvas( root, width=canvas_width, height=canvas_height, bg='white') canv.pack() plot_data = [random.randint(75, 200) for r in range(12)] for x, y in enumerate(plot_data): x1 = x + x * bar_width y1 = canvas_height - y x2 = x + x * bar_width + bar_width y2 = canvas_height canv.create_rectangle(x1, y1, x2, y2, fill="blue") canv.create_text(x1 + 3, y1, font=("", 6), text=str(y), anchor='sw') root.mainloop() ================================================ FILE: Chapter 08/8.04_scatter_plot.py ================================================ """ Code illustration: 8.04 Scatter Plot Tkinter GUI Application Development Blueprints """ import tkinter import random root = tkinter.Tk() def motion(event): x, y = event.x, event.y print("canvas x,y", '{}, {}'.format(x, y)) print("plot xy", '{}, {}'.format(x - 50, 250 - y)) root.bind('', motion) c = tkinter.Canvas(root, width=350, height=280, bg='white') c.grid() # create x-axis c.create_line(50, 250, 300, 250, width=3) for i in range(12): x = 50 + (i * 20) c.create_text(x, 255, font=("", 6), anchor='n', text='{}'.format(20 * i)) # y-axis c.create_line(50, 250, 50, 20, width=3) for i in range(12): y = 250 - (i * 20) c.create_text(45, y, font=("", 6), anchor='e', text='{}'.format(20 * i)) # create scatter plots from random x-y values for i in range(35): x, y = random.randint(0, 160) + 50, 250 - random.randint(50, 220) c.create_oval(x - 3, y - 3, x + 3, y + 3, width=1, fill='red') root.mainloop() ================================================ FILE: Chapter 08/8.05_matplotlib_embedding_graphs.py ================================================ """ Code illustration: 8.05 Embedding Matplotlib graph on tkinter Tkinter GUI Application Development Blueprints """ import tkinter as tk from numpy import arange, sin, pi from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg from matplotlib.figure import Figure root = tk.Tk() # creating the graph f = Figure(figsize=(5, 4), dpi=100) a = f.add_subplot(111) t = arange(-1.0, 1.0, 0.001) s = t * sin(1 / t) a.plot(t, s) # embedding matplotlib figure f on a tk.DrawingArea canvas = FigureCanvasTkAgg(f, master=root) canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1) # creating toolbar toolbar = NavigationToolbar2TkAgg(canvas, root) toolbar.update() root.mainloop() ================================================ FILE: Chapter 08/8.06_polar_plot.py ================================================ """ Code illustration: 8.06 Polar Plot Tkinter GUI Application Development Blueprints """ from tkinter import Tk, Canvas, W import math width = 400 height = 400 x_center = width //2 y_center = height//2 scaling_factor = 60 def polar_to_cartesian(r, theta, scaling_factor, x_center, y_center): x = r * math.cos(theta) * scaling_factor + x_center y = r * math.sin(theta) * scaling_factor + y_center return(x, y) root = Tk() root.title("Polar Plot Demo") c = Canvas(width=width, height=height, bg='white') c.pack() # draw radial lines at interval of 15 degrees for theta in range(0,360,15): r = 180 x, y = x_center + math.cos(math.radians(theta))*r, \ y_center - math.sin(math.radians(theta)) *r c.create_line(x_center, y_center, x, y, fill='green', dash=(2, 4),\ activedash=(6, 5, 2, 4) ) c.create_text(x, y, anchor=W, font="Purisa 8", text=str(theta) + '°') # draw concentric_circles for radius in range(1,4): x_max = x_center + radius * scaling_factor x_min = x_center - radius * scaling_factor y_max = y_center + radius * scaling_factor y_min = y_center - radius * scaling_factor c.create_oval(x_max, y_max, x_min, y_min, width=1, outline='grey', \ dash=(2, 4), activedash=(6, 5, 2, 4)) for theta in range(0, 3000): r = 2*math.sin(2*theta) # a few equations that look good on polar plot - # uncomment one line at a time to see the individual plots # also change parameters of equations to see their effect on the plots # r = 0.0006 * theta #r = 1 + 2*math.cos(theta) #r = 3 * math.cos(theta) #r = 2*math.sin(5*theta) #r = 2 * math.cos(3*theta) #r = 2 * math.sin(theta)**2 #r = (4 * math.sin(2*theta))**1/2 #r = (4 * math.cos(2*theta))**1/2 x, y = polar_to_cartesian(r, theta, scaling_factor, x_center, y_center) c.create_oval(x, y, x, y, width=1, outline='navy') root.mainloop() ================================================ FILE: Chapter 08/8.07_gravity_simulation.py ================================================ """ Code illustration: 8.07 Gravity Simulation Tkinter GUI Application Development Blueprints """ import math import tkinter as tk w = 700 h = 700 root = tk.Tk() root.geometry('{}x{}'.format(w, h)) canvas = tk.Canvas(root, height=h, width=w, bg='black') canvas.pack() root.update_idletasks() class Planet: sun_mass = 1.989 * math.pow(10, 30) G = 6.67 * math.pow(10, -11) def __init__(self, name, mass, distance, radius, color, canvas): self.name = name self.mass = mass self.distance = distance self.radius = radius self.canvas = canvas self.color = color self.angular_velocity = -math.sqrt(self.gravitational_force() / (self.mass * self.distance)) self.oval_id = self.draw_initial_planet() self.scaled_radius = self.radius_scaler(self.radius) self.scaled_distance = self.distance_scaler(self.distance) def distance_scaler(self, value): #[57.91, 4497.1] scaled to [0, self.canvas.winfo_width()/2] return (self.canvas.winfo_width() / 2 - 1) * (value - 1e10) / ( 2.27e11 - 1e10) + 1 def radius_scaler(self, value): #[2439, 6051.8] scaled to [0, self.canvas.winfo_width()/2] return (16 * (value - 2439) / (6052 - 2439)) + 2 def draw_initial_planet(self): screen_dim = self.canvas.winfo_width() scaled_distance = self.distance_scaler(self.distance) scaled_radius = self.radius_scaler(self.radius) y = screen_dim / 2 x = screen_dim / 2 + scaled_distance oval_id = self.canvas.create_oval( x - scaled_radius, y - scaled_radius, x + scaled_radius, y + scaled_radius, fill=self.color, outline=self.color) return oval_id def gravitational_force(self): f = self.G * (self.mass * self.sun_mass) / math.pow(self.distance, 2) return f def angular_position(self, t): theta = (0 + self.angular_velocity * t) return theta def coordinates(self, theta): screen_dim = self.canvas.winfo_width() y = self.scaled_distance * math.sin(theta) + screen_dim / 2 x = self.scaled_distance * math.cos(theta) + screen_dim / 2 return (x, y) def update_location(self, t): theta = self.angular_position(t) x, y = self.coordinates(theta) scaled_radius = self.scaled_radius self.canvas.create_rectangle(x, y, x, y, outline="grey") self.canvas.coords(self.oval_id, x - scaled_radius, y - scaled_radius, x + scaled_radius, y + scaled_radius) class Moon(Planet): earth_mass = 5.973 * math.pow(10, 24) def __init__(self, name, mass, distance, radius, color, canvas, earth): super().__init__(name, mass, distance, radius, color, canvas) self.angular_velocity = -math.sqrt(self.gravitational_force() / (self.mass * self.distance)) self.earth = earth self.scaled_distance = 25 # since distance scaling was based on distance from sun self.scaled_radius = 2 # since moon's scaled radius was getting negative def gravitational_force(self): f = self.G * (self.mass * self.earth_mass) / math.pow(self.distance, 2) return f def coordinates(self, t): theta = self.angular_position(t) earth_x, earth_y = self.earth.coordinates(self.earth.angular_position(t)) dist = self.scaled_distance x = earth_x + dist * math.cos(theta) y = earth_y + dist * math.sin(theta) return (x, y) def update_location(self, t): x, y = self.coordinates(t) scaled_radius = self.scaled_radius self.canvas.create_rectangle(x, y, x, y, outline="red") self.canvas.coords(self.oval_id, x - scaled_radius, y - scaled_radius, x + scaled_radius, y + scaled_radius) #name,mass,distance,radius, color, canvas mercury = Planet("Mercury", 3.302e23, 5.7e10, 2439.7, 'red2', canvas) venus = Planet("Venus", 4.8685e24, 1.08e11, 6051.8, 'CadetBlue1', canvas) earth = Planet("Earth", 5.973e24, 1.49e11, 6378, 'RoyalBlue1', canvas) mars = Planet("Mars", 6.4185e23, 2.27e11, 3396, 'tomato2', canvas) planets = [mercury, venus, earth, mars] moon = Moon("Moon", 7.347e22, 3.844e5, 173, 'white', canvas, earth) time = 0 time_step = 100000 def update_bodies_position(): global time, time_step for planet in planets: planet.update_location(time) moon.update_location(time) time = time + time_step root.after(100, update_bodies_position) update_bodies_position() root.mainloop() ================================================ FILE: Chapter 08/8.08_Mandelbrot.py ================================================ """ Code illustration: 8.08 Mandelbrot Set *** This may take some time to compute *** Tkinter GUI Application Development Blueprints """ from tkinter import Tk, Canvas import math image_width = 512 image_height = 512 max_number_of_iterations = 50 min_real, max_real, min_imaginary, max_imaginary = -1.5, 0.7, -1.0, 1.0 root = Tk() canvas = Canvas(root, height=image_height, width=image_width) canvas.pack() def mandelbrot_set_check(real, imaginary): iteration_count = 0 z_real = 0.0 z_imaginary = 0.0 while iteration_count < max_number_of_iterations and \ z_real * z_real + z_imaginary * z_imaginary < 4.0: temp = z_real * z_real - z_imaginary * z_imaginary + real z_imaginary = 2.0 * z_real * z_imaginary + imaginary z_real = temp iteration_count += 1 return iteration_count def get_color(num_iterations): if num_iterations == max_number_of_iterations: r,g,b = (0, 0, 0) elif num_iterations < max_number_of_iterations//8: r,g,b = (num_iterations * 2)%255, 0, 0 elif num_iterations < max_number_of_iterations//7: r,g,b = (num_iterations * 4) % 255, 0, 0 elif num_iterations < max_number_of_iterations//6: r,g,b = (num_iterations * 8) % 255, 0, 0 elif (num_iterations < max_number_of_iterations//5): r,g,b = 255, (num_iterations * 16) % 255 , 0 elif (num_iterations < max_number_of_iterations//4): r,g,b = 255, (num_iterations * 64) % 255, 0 elif (num_iterations < max_number_of_iterations//3): r,g,b = 255, (num_iterations * 128) % 255, 0 elif (num_iterations < max_number_of_iterations//2): r,g,b = (255, (num_iterations * 256) % 255 , 0) else: r, g, b = (255, 255, 0) rgb = '#%02x%02x%02x' % (r, g, b) return rgb def map_pixels_to_real(x): real_range = max_real - min_real return x * (real_range / image_width) + min_real def map_pixels_to_imaginary(y): imaginary_range = max_imaginary - min_imaginary return y * (imaginary_range / image_height) + min_imaginary for y in range(image_height): for x in range(image_width): real = map_pixels_to_real(x) imaginary = map_pixels_to_imaginary(y) num_iterations = mandelbrot_set_check(real, imaginary) rgb = get_color(num_iterations) canvas.create_rectangle([x, y, x, y], fill=rgb, width=0) root.mainloop() ================================================ FILE: Chapter 08/8.09_vornoi_diagram.py ================================================ """ Code illustration: 8.09 Vornoi Diagrams *** Warnig - this code takes up to a few minutes to compute *** Tkinter GUI Application Development Blueprints """ from tkinter import Tk, Canvas import random import math width = 800 height = 500 number_of_attractor_points = 125 def create_voronoi_diagram(canvas, w, h, number_of_attractor_points): attractor_points = [] colors = [] for i in range(number_of_attractor_points): attractor_points.append((random.randrange(w), random.randrange(h))) colors.append('#%02x%02x%02x' % (random.randrange(256), random.randrange(256), random.randrange(256))) for y in range(h): for x in range(w): minimum_distance = math.hypot(w , h ) index_of_nearest_attractor_point = -1 for i in range(number_of_attractor_points): distance = math.hypot(attractor_points[i][0] - x, attractor_points[i][1] - y) if distance < minimum_distance: minimum_distance = distance index_of_nearest_attractor_point = i canvas.create_rectangle([x, y, x, y], fill=colors[index_of_nearest_attractor_point], width=0) for point in attractor_points: x, y = point dot = [x - 1, y - 1, x + 1, y + 1] canvas.create_rectangle(dot, fill='blue', width=1) root = Tk() canvas = Canvas(root, height=height, width=width) canvas.pack() create_voronoi_diagram(canvas, width, height, number_of_attractor_points) root.mainloop() ================================================ FILE: Chapter 08/8.10_spring_pendulum.py ================================================ """ Code illustration: 8.10 Spring Pendulum Simulation Tkinter GUI Application Development Blueprints """ from tkinter import Tk, Canvas import numpy as np from scipy.integrate import odeint UNSTRETCHED_SPRING_LENGHT = 30 SPRING_CONSTANT = 0.1 MASS = 0.3 GRAVITY = 9.8 NUMBER_OF_STEPS_IN_SIMULATION = 500 state_vector = [1, 1, 2, 1] # 4 values represent 'l', 'dl/dt', 'θ', 'dθ/dt' respectively # i;e 'spring_length', 'dl/dt - velocity', 'angle', 'anglular velocity' def differential_functions(state_vector, time): func1 = state_vector[1] func2 = (UNSTRETCHED_SPRING_LENGHT + state_vector[0] ) * state_vector[3]**2 - (SPRING_CONSTANT / MASS * state_vector[0] ) + GRAVITY * np.cos(state_vector[2]) func3 = state_vector[3] func4 = -(GRAVITY * np.sin(state_vector[2]) + 2.0 * state_vector[1] * state_vector[3]) / ( UNSTRETCHED_SPRING_LENGHT + state_vector[0]) return np.array([func1, func2, func3, func4]) time = np.linspace(0, 37, NUMBER_OF_STEPS_IN_SIMULATION) ode_solution = odeint(differential_functions, state_vector, time) x_coordinates = (UNSTRETCHED_SPRING_LENGHT + ode_solution[:, 0]) * np.sin( ode_solution[:, 2]) y_coordinates = (UNSTRETCHED_SPRING_LENGHT + ode_solution[:, 0]) * np.cos( ode_solution[:, 2]) w = 250 h = 300 plot_step = 0 root = Tk() canvas = Canvas(root, bg="LemonChiffon3", height=h, width=w) canvas.pack(side='left') def update_graph(): global plot_step if plot_step == NUMBER_OF_STEPS_IN_SIMULATION: # simulation ended plot_step = 0 # repeat the simulation x, y = int( x_coordinates[plot_step]) + w / 2, int(y_coordinates[plot_step] + h / 2) canvas.delete('all') canvas.create_line(w / 2, 0, x, y, dash=(2, 1), width=1, fill="gold4") canvas.create_oval( x - 10, y - 10, x + 10, y + 10, outline="gold4", fill="lavender") plot_step = plot_step + 1 root.after(15, update_graph) update_graph() root.mainloop() ================================================ FILE: Chapter 08/8.11_chaos_game.py ================================================ """ Code illustration: 8.11 Chaos Game Tkinter GUI Application Development Blueprints """ import random from tkinter import Tk, Canvas import math WIDTH = 800 HEIGHT = 500 v1 = (float(WIDTH/2), 0.0) v2 = (0.00, float(HEIGHT)) v3 = (float(WIDTH), float(HEIGHT)) last_point = None root = Tk() canvas = Canvas(root, background="#660099", width = WIDTH, height = HEIGHT) canvas.pack() def midway_point(p1, p2): x = p1[0] + (p2[0] - p1[0]) //2 y = p1[1] + (p2[1] - p1[1]) //2 return (x,y) def random_point_inside_triangle(v1, v2, v3): a = random.random() b = random.random() if a + b > 1: a = 1-a b = 1-b c = 1 - a -b x = (a*v1[0])+(b*v2[0])+(c*v3[0]); y = (a*v1[1])+(b*v2[1])+(c*v3[1]); return (x,y) last_point = random_point_inside_triangle(v1, v2, v3) def get_next_point(): global last_point roll = random.choice(range(6))+1 mid_point = None if roll == 1 or roll == 2: mid_point = midway_point(last_point, v1) elif roll == 3 or roll == 4: mid_point = midway_point(last_point, v2) elif roll == 5 or roll == 6: mid_point = midway_point(last_point, v3) last_point = mid_point return mid_point def update(): x,y = get_next_point() canvas.create_rectangle(x, y, x, y, outline="#FFFF33") root.after(1, update) update() root.mainloop() ================================================ FILE: Chapter 08/8.12_phyllotaxis.py ================================================ """ Code illustration: 8.12 Screensaver Tkinter GUI Application Development Blueprints """ from tkinter import Tk, Canvas import numpy as np import random width = 500 height = 500 number_of_dots = 2000 angle = 137.5 scaling_factor = 4 dot_size = 4 n = np.arange(number_of_dots) r = np.zeros(number_of_dots) phi = np.zeros(number_of_dots) x = np.zeros(number_of_dots) y = np.zeros(number_of_dots) dots = [] colors = [] root = Tk() canvas = Canvas(root, width=width, height=height, bg='grey6') canvas.pack() for i in n: r = (scaling_factor * np.sqrt(i) * 6) % 256 color = '#%02x%02x%02x' % (int(r), 0, 0) colors.append(color) dots.append( canvas.create_oval( x[i] - dot_size, y[i] - dot_size, x[i] + dot_size, y[i] + dot_size, fill=color)) def update(): global angle angle += 0.000001 phi = angle * n r = scaling_factor * np.sqrt(n) x = r * np.cos(phi) + width / 2 y = r * np.sin(phi) + height / 2 for i in n: canvas.coords(dots[i], x[i] - dot_size, y[i] - dot_size, x[i] + dot_size, y[i] + dot_size) root.after(15, update) update() root.mainloop() ================================================ FILE: Chapter 08/8.13_3D_graphics.py ================================================ """ Code illustration: 8.13_3D_graphics 3D Graphics Tkinter GUI Application Development Blueprints """ from tkinter import Tk, Canvas, BOTH, YES,ALL from math import * class MatrixHelpers: def transpose_matrix(self, matrix): return list(zip(*matrix)) def translate_vector(self, x, y, dx, dy): return x + dx, y + dy def matrix_multiply(self, matrix_a, matrix_b): zip_b = list(zip(*matrix_b)) return [[ sum(ele_a * ele_b for ele_a, ele_b in zip(row_a, col_b)) for col_b in zip_b ] for row_a in matrix_a] def rotate_along_x(self, x, shape): return self.matrix_multiply( [[1, 0, 0], [0, cos(x), -sin(x)], [0, sin(x), cos(x)]], shape) def rotate_along_y(self, y, shape): return self.matrix_multiply( [[cos(y), 0, sin(y)], [0, 1, 0], [-sin(y), 0, cos(y)]], shape) def rotate_along_z(self, z, shape): return self.matrix_multiply( [[cos(z), sin(z), 0], [-sin(z), cos(z), 0], [0, 0, 1]], shape) class Cube(MatrixHelpers): last_x = 0 last_y = 0 fg_color = 'red' bg_color = 'khaki' def __init__(self, root): self.root = root self.init_data() self.create_canvas() self.draw_cube() self.bind_mouse_buttons() self.continually_rotate() self.epsilon = lambda d: d * 0.01 def init_data(self): self.cube = self.transpose_matrix([[-100, -100, -100], [-100, 100, -100], [ -100, -100, 100 ], [-100, 100, 100], [100, -100, -100], [100, 100, -100], [100, -100, 100], [100, 100, 100]]) def create_canvas(self): self.canvas = Canvas( self.root, width=400, height=400, background=self.bg_color) self.canvas.pack(fill=BOTH, expand=YES) def bind_mouse_buttons(self): self.canvas.bind("", self.on_mouse_clicked) self.canvas.bind("", self.on_mouse_motion) def draw_cube(self): cube_points = [[0, 1, 2, 4], [3, 1, 2, 7], [5, 1, 4, 7], [6, 2, 4, 7]] w = self.canvas.winfo_width() / 2 h = self.canvas.winfo_height() / 2 self.canvas.delete(ALL) for i in cube_points: for j in i: self.canvas.create_line( self.translate_vector(self.cube[0][i[0]], self.cube[1][i[0]], w, h), self.translate_vector(self.cube[0][j], self.cube[1][j], w, h), fill=self.fg_color) def continually_rotate(self): self.cube = self.rotate_along_x(0.01, self.cube) self.cube = self.rotate_along_y(0.01, self.cube) self.cube = self.rotate_along_z(0.01, self.cube) self.draw_cube() self.root.after(15, self.continually_rotate) def on_mouse_clicked(self, event): self.last_x = event.x self.last_y = event.y def on_mouse_motion(self, event): dx = self.last_y - event.y self.cube = self.rotate_along_x(self.epsilon(-dx), self.cube) dy = self.last_x - event.x self.cube = self.rotate_along_y(self.epsilon(dy), self.cube) self.draw_cube() self.on_mouse_clicked(event) def main(): root = Tk() Cube(root) root.mainloop() if __name__ == '__main__': main() ================================================ FILE: Chapter 09/9.01_race_condition.py ================================================ """ Code illustration: 9.01 Race Condition Demo Tkinter GUI Application Development Blueprints """ import threading class RaceConditionDemo: def __init__(self): self.shared_var = 0 self.total_count = 100000 self.demo_of_race_condition() def increment(self): for i in range(self.total_count): self.shared_var += 1 def decrement(self): for i in range(self.total_count): self.shared_var -= 1 def demo_of_race_condition(self): t1 = threading.Thread(target=self.increment) t2 = threading.Thread(target=self.decrement) t1.start() t2.start() t1.join() t2.join() print("value of shared_var after all increments & decrements :", self.shared_var) if __name__ == "__main__": for i in range(100): RaceConditionDemo() ================================================ FILE: Chapter 09/9.02_lock_demo.py ================================================ """ Code illustration: 9.02 Lock Demo Tkinter GUI Application Development Blueprints """ import threading class LockDemo(): def __init__(self): self.shared_var = 0 self.total_count = 100000 self.lock = threading.Lock() self.demo_of_lock_to_avoid_race_condition() def increment(self): for i in range(self.total_count): self.lock.acquire() self.shared_var += 1 self.lock.release() def decrement(self): for i in range(self.total_count): self.lock.acquire() self.shared_var -= 1 self.lock.release() def demo_of_lock_to_avoid_race_condition(self): t1 = threading.Thread(target=self.increment) t2 = threading.Thread(target=self.decrement) t1.start() t2.start() t1.join() t2.join() print("value of shared_var after all increments & decrements :", self.shared_var) if __name__ == "__main__": for i in range(100): LockDemo() ================================================ FILE: Chapter 09/9.03_threading_with queue.py ================================================ """ Code illustration: 9.03 Threading with Queue Simple Demo Tkinter GUI Application Development Blueprints """ import queue import threading class Consumer(threading.Thread): def __init__(self, queue): threading.Thread.__init__(self) self.queue = queue def run(self): while True: job = self.queue.get() self.do_task(job) def do_task(self, task): print ('doing task{}'.format(task)) self.queue.task_done() def producer(tasks): my_queque = queue.Queue() # populate queue with tasks for task in tasks: my_queque.put(task) # create 6 threads and pass the queue as its argument for i in range(6): my_thread = Consumer(my_queque) my_thread.daemon = True my_thread.start() # wait for the queue to finish my_queque.join() print ('all tasks completed') if __name__ == "__main__": tasks = 'A B C D E F'.split() producer(tasks) ================================================ FILE: Chapter 09/9.04_game_of_ snake.py ================================================ """ Code illustration: 9.04 Game of Snake Tkinter GUI Application Development Blueprints """ import threading import queue import random import time from tkinter import Tk, Canvas, Button class View(Tk): def __init__(self, queue): Tk.__init__(self) self.queue = queue self.create_gui() self.queue_handler() def create_gui(self): self.canvas = Canvas(self, width=495, height=305, bg='#FF75A0') self.canvas.pack() self.snake = self.canvas.create_line( (0, 0), (0, 0), fill='#FFCC4C', width=10) self.food = self.canvas.create_rectangle( 0, 0, 0, 0, fill='#FFCC4C', outline='#FFCC4C') self.points_earned = self.canvas.create_text( 455, 15, fill='white', text='Score: 0') def queue_handler(self): try: while True: task = self.queue.get_nowait() if 'game_over' in task: self.game_over() elif 'move' in task: points = [x for point in task['move'] for x in point] self.canvas.coords(self.snake, *points) elif 'food' in task: self.canvas.coords(self.food, *task['food']) elif 'points_earned' in task: self.canvas.itemconfigure( self.points_earned, text='Score: {}'.format(task['points_earned'])) self.queue.task_done() except queue.Empty: self.after(100, self.queue_handler) def game_over(self): self.canvas.create_text(200, 150, fill='white', text='Game Over') quit_button = Button(self, text='Quit', command=self.destroy) self.canvas.create_window(200, 180, anchor='nw', window=quit_button) class Food: def __init__(self, queue): self.queue = queue self.generate_food() def generate_food(self): x = random.randrange(5, 480, 10) y = random.randrange(5, 295, 10) self.position = (x, y) rectangle_position = (x - 5, y - 5, x + 5, y + 5) self.queue.put({'food': rectangle_position}) class Snake(threading.Thread): is_game_over = False def __init__(self, queue): threading.Thread.__init__(self) self.queue = queue self.daemon = True self.points_earned = 0 self.snake_points = [ (495, 55), (485, 55), (475, 55), (465, 55), (455, 55)] self.food = Food(queue) self.direction = 'Left' self.start() def run(self): while not self.is_game_over: self.queue.put({'move': self.snake_points}) time.sleep(0.1) self.move() def on_keypress(self, e): self.direction = e.keysym def move(self): new_snake_point = self.calculate_new_coordinates() if self.food.position == new_snake_point: self.points_earned += 1 self.queue.put({'points_earned': self.points_earned}) self.food.generate_food() else: self.snake_points.pop(0) self.check_game_over(new_snake_point) self.snake_points.append(new_snake_point) def calculate_new_coordinates(self): last_x, last_y = self.snake_points[-1] if self.direction == 'Up': new_snake_point = (last_x, last_y - 10) elif self.direction == 'Down': new_snake_point = (last_x, last_y + 10) elif self.direction == 'Left': new_snake_point = (last_x - 10, last_y) elif self.direction == 'Right': new_snake_point = (last_x + 10, last_y) return new_snake_point def check_game_over(self, snake_point): x, y = snake_point if not -5 < x < 505 or not -5 < y < 315 or snake_point in self.snake_points: self.is_game_over = True self.queue.put({'game_over': True}) def main(): q = queue.Queue() gui = View(q) snake = Snake(q) for key in ("Left", "Right", "Up", "Down"): gui.bind("".format(key), snake.on_keypress) gui.mainloop() if __name__ == '__main__': main() ================================================ FILE: Chapter 09/9.05_urllib_demo.py ================================================ """ Code illustration: 9.05 urllib demo Tkinter GUI Application Development Blueprints """ import urllib.request with urllib.request.urlopen('http://www.packtpub.com/') as f: print(f.read()) ================================================ FILE: Chapter 09/9.06_weather_ reporter.py ================================================ """ Code illustration: 9.06 Weather reporter Tkinter GUI Application Development Blueprints """ import sys import json import datetime from tkinter import Tk, Canvas, Entry, Button, Frame, Label, StringVar, ALL from tkinter import ttk from tkinter import messagebox import urllib.request import urllib.parse class WeatherReporter: weather_data = None APIKEY = 'ENTER_YOUR_API_KEY_HERE' def __init__(self, root): self.root = root self.create_top_frame() self.create_weather_display_frame() def create_top_frame(self): frame = Frame(self.root) frame.pack(side="top") Label(frame, text='Enter Location').pack(side="left") self.location = StringVar() Entry(frame, textvariable=self.location).pack(side="left") ttk.Button(frame, text='Go', command=self.on_show_weather_button_clicked).pack( side="left") def create_weather_display_frame(self): self.canvas = Canvas( self.root, height='425', width='340', background='black') self.canvas.create_rectangle(10, 10, 330, 415, fill='#F6AF06') self.canvas.pack(side="bottom") def on_show_weather_button_clicked(self): if not self.location.get(): return self.clear_canvas() self.get_weather_data() self.format_data() self.display_data() def get_weather_data(self): self.weather_data = self.get_data_from_url() self.weather_data = self.json_to_dict(self.weather_data) def clear_canvas(self): self.canvas.delete(ALL) self.canvas.create_rectangle(10, 10, 330, 415, fill='#F6AF06') def format_data(self): data = self.weather_data self.name = data['name'] self.latitude = self.str2num(data['lat'], 3) self.longitude = self.str2num(data['lon'], 3) self.country = data['country'] self.time_now = self.time_stamp_to_data(data['dt']) self.description = data['description'] self.icon_name = "weatherimages/{}.png".format(data['icon'].lower()) self.clouds = data['all'] + ' %' self.sunrise_time = self.time_stamp_to_time(data['sunrise']) self.sunset_time = self.time_stamp_to_time(data['sunset']) self.temp_now_in_celcius = self.str2num( self.kelvin_to_celsius(float(data['temp'])), 2) + u' \u2103' self.temp_now_in_fahrenheit = self.str2num( self.kelvin_to_fahrenheit(float(data['temp'])), 2) + u' \u2109' self.temp_min_in_celcius = self.str2num( self.kelvin_to_celsius(float(data['temp_min'])), 2) + u' \u2103' self.temp_max_in_celcius = self.str2num( self.kelvin_to_celsius(float(data['temp_max'])), 2) + u' \u2103' def kelvin_to_celsius(self, k): return k - 273.15 def kelvin_to_fahrenheit(self, k): return (k * 9 / 5 - 459.67) def str2num(self, string, precision): return "%0.*f" % (precision, float(string)) def display_data(self): if not self.weather_data: messagebox.showerror( 'Name not found', 'Unable to fetch record - Name not found') return data = self.weather_data opts = {'fill': 'white', 'font': 'Helvetica 12'} self.canvas.create_text(52, 30, text=self.name, **opts) self.canvas.create_text( 245, 35, text='Latitude :' + self.latitude, **opts) self.canvas.create_text( 245, 53, text='Longitude: ' + self.longitude, **opts) self.canvas.create_text( 55, 50, text='Country : ' + self.country, **opts) self.canvas.create_text(155, 80, text=self.time_now, **opts) self.canvas.create_text(85, 105, text='NOW', **opts) self.img = PhotoImage(file=self.icon_name) self.canvas.create_image(140, 105, image=self.img) self.canvas.create_text(240, 105, text=self.description, **opts) self.canvas.create_text(85, 155, text='Temperature', **opts) self.canvas.create_text( 87, 175, text=self.temp_min_in_celcius + ' ~ ' + self.temp_max_in_celcius, **opts) self.canvas.create_text( 225, 140, text=self.temp_now_in_celcius, **opts) self.canvas.create_text( 225, 180, text=self.temp_now_in_fahrenheit, **opts) self.canvas.create_text(95, 215, text='Relative Humidity', **opts) self.canvas.create_text(198, 215, text=data['humidity'] + ' %', **opts) self.canvas.create_text(77, 235, text='Wind Speed', **opts) self.canvas.create_text(205, 235, text=data['speed'] + ' m/s ', **opts) self.canvas.create_text(80, 255, text='Wind Degree', **opts) self.canvas.create_text( 223, 255, text=data['deg'] + ' degrees', **opts) self.canvas.create_text(80, 275, text='Pressure(at.)', **opts) self.canvas.create_text( 225, 275, text=data['pressure'] + ' millibars', **opts) if '3h' in data: self.canvas.create_text(83, 293, text='Rain (Last 3h)', **opts) self.canvas.create_text( 200, 293, text=data['3h'] + ' mm', **opts) # rain self.canvas.create_text(58, 310, text='Clouds', **opts) self.canvas.create_text(200, 310, text=self.clouds, **opts) # clouds self.canvas.create_text(60, 328, text='Sunrise', **opts) self.canvas.create_text(200, 328, text=self.sunrise_time, **opts) self.canvas.create_text(59, 343, text='Sunset', **opts) self.canvas.create_text(200, 343, text=self.sunset_time, **opts) self.canvas.create_text(159, 378, text='Powered by:', **opts) self.canvas.create_text( 159, 398, text='www.openweathermap.org', **opts) def time_stamp_to_time(self, ts): return (datetime.datetime.fromtimestamp(int(ts)).strftime('%H:%M:%S')) def time_stamp_to_data(self, ts): return (datetime.datetime.fromtimestamp(int(ts)).strftime('%Y-%m-%d %H:%M:%S')) def get_data_from_url(self): try: params = urllib.parse.urlencode( {'q': self.location.get(), 'APPID': self.APIKEY}, encoding="utf-8") api_url = ( 'http://api.openweathermap.org/data/2.5/weather?{}' .format(params) ) with urllib.request.urlopen(api_url) as f: json_data = f.read() return json_data except IOError as e: messagebox.showerror( 'Unable to connect', 'Unable to connect %s' % e) sys.exit(1) def json_to_dict(self, json_data): decoder = json.JSONDecoder() decoded_json_data = decoder.decode(json_data.decode("utf-8")) flattened_dict = {} for key, value in decoded_json_data.items(): if key == 'weather': for ke, va in value[0].items(): flattened_dict[str(ke)] = str(va).upper() continue try: for k, v in value.items(): flattened_dict[str(k)] = str(v).upper() except: flattened_dict[str(key)] = str(value).upper() return flattened_dict def main(): root = Tk() WeatherReporter(root) root.mainloop() if __name__ == '__main__': main() ================================================ FILE: Chapter 09/9.07_socket_demo.py ================================================ """ Code illustration: 7.08 Socket Programming Demo Tkinter GUI Application Development Blueprints """ import socket import sys try: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) except socket.error: print( 'Failed to create socket') sys.exit() print ('Socket Created') host = 'effbot.org' port = 80 try: ip = socket.gethostbyname( host ) except socket.gaierror: print('Could not resolve Hostname') sys.exit() #Connect to remote server s.connect((ip , port)) print ('Socket Connected to ', host, ' on ip ', ip) #Send some data to remote server message = "GET / HTTP/1.1 \r\nHost:" + host + "\r\n\r\nAccept: text/html\r\n\r\n" try : #send the message s.sendall(message.encode('utf-8')) except socket.error: print('Send failed') sys.exit() print('Message send successfully') #Now receive data received_message = s.recv(4098) print (received_message ) ================================================ FILE: Chapter 09/9.08_port_scanner.py ================================================ """ Code illustration: 7.09 Port Scanner Tkinter GUI Application Development Blueprints """ import socket from tkinter import Tk, Label, Entry, Button, Frame, Scrollbar, W, EW, E, Text, \ DISABLED, Y, BOTH, NORMAL, END from threading import Thread class PortScanner(): stop = False url = "google.com" start_port = 70 end_port = 85 def __init__(self, root): self.root = root self.create_gui() def on_scan_button_clicked(self): self.empty_console() self.scan_in_a_new_thread() def empty_console(self): self.console_text.config(state=NORMAL) self.console_text.delete("1.0", END) self.console_text.config(state=DISABLED) def scan_in_a_new_thread(self): url = self.host_entry.get() start_port = int(self.start_port_entry.get()) end_port = int(self.end_port_entry.get()) thread = Thread(target=self.start_scan, args=(url, start_port, end_port)) thread.start() def start_scan(self, url, start_port, end_port): for port in range(start_port, end_port + 1): if not self.stop: self.output_to_console("Scanning port {}".format(port)) if self.is_port_open(url, port): self.output_to_console(" -- Port {} open \n".format(port)) else: self.output_to_console("-- Port {} closed \n".format(port)) def is_port_open(self, url, port): try: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.settimeout(1) s.connect((socket.gethostbyname(url), port)) s.close() return True except: return False def on_stop_button_clicked(self): self.stop = True def output_to_console(self, new_text): self.console_text.config(state=NORMAL) self.console_text.insert(END, new_text) self.console_text.see(END) self.console_text.config(state=DISABLED) def create_gui(self): Label(self.root, text='Host :').grid(row="1", column="1", sticky=W) self.host_entry = Entry(self.root) self.host_entry.insert(0, self.url) self.host_entry.grid(row="1", column="2", sticky=EW) Label(self.root, text='Start Port :').grid( row="2", column="1", sticky=W) self.start_port_entry = Entry(self.root) self.start_port_entry.insert(0, self.start_port) self.start_port_entry.grid(row="2", column="2", sticky=EW) Label(self.root, text='End Port :').grid(row="3", column="1", sticky=W) self.end_port_entry = Entry(self.root) self.end_port_entry.insert(0, self.end_port) self.end_port_entry.grid(row="3", column="2", sticky=EW) Button(self.root, text='Scan', command=self.on_scan_button_clicked).grid( row="4", column="2", sticky=E) Button(self.root, text='Stop', command=self.on_stop_button_clicked).grid( row="4", column="2", sticky=W) Label(self.root, text='Scan Result :').grid( row="5", column="1", sticky=W) console_frame = Frame(self.root) console_frame.grid(row="6", column="1", columnspan="2") self.console_text = Text( console_frame, fg="green", bg="black", state=DISABLED) scrollbar = Scrollbar(console_frame, command=self.console_text.yview) scrollbar.pack(side="right", fill=Y) self.console_text.pack(expand=1, fill=BOTH) self.console_text['yscrollcommand'] = scrollbar.set if __name__ == '__main__': root = Tk() PortScanner(root) root.mainloop() ================================================ FILE: Chapter 09/9.09_chat_server.py ================================================ """ Code illustration: 9.09 Chat server Tkinter GUI Application Development Blueprints """ import socket import threading class ChatServer: clients_list = [] last_received_message = "" def __init__(self): self.create_listening_server() def create_listening_server(self): self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) local_ip = '127.0.0.1' local_port = 10319 self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.server_socket.bind((local_ip, local_port)) print("Listening for incoming messages..") self.server_socket.listen(5) self.receive_messages_in_a_new_thread() def receive_messages(self, so): while True: incoming_buffer = so.recv(256) if not incoming_buffer: break self.last_received_message = incoming_buffer.decode('utf-8') self.broadcast_to_all_clients(so) so.close() def broadcast_to_all_clients(self, senders_socket): for client in self.clients_list: socket, (ip, port) = client if socket is not senders_socket: socket.sendall(self.last_received_message.encode('utf-8')) def receive_messages_in_a_new_thread(self): while 1: client = so, (ip, port) = self.server_socket.accept() self.add_to_clients_list(client) print ('Connected to ', ip, ':', str(port)) t = threading.Thread(target=self.receive_messages, args=(so,)) t.start() def add_to_clients_list(self, client): if client not in self.clients_list: self.clients_list.append(client) if __name__ == "__main__": ChatServer() ================================================ FILE: Chapter 09/9.10_chat_client.py ================================================ ''' Code illustration: 9.10 Chat Client *** NOTE Run atleast 2 instances of this program to see chat in action*** Tkinter GUI Application Development Blueprints ''' from tkinter import Tk, Frame, Scrollbar, Label, END, Entry, Text, VERTICAL import socket import threading from tkinter import messagebox class ChatClient: client_socket = None last_received_message = None def __init__(self, root): self.root = root self.initialize_socket() self.initialize_gui() self.listen_for_incoming_messages_in_a_thread() def initialize_socket(self): self.client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) remote_ip = '127.0.0.1' remote_port = 10319 self.client_socket.connect((remote_ip, remote_port)) def initialize_gui(self): self.display_name_section() self.display_chat_transcript() self.display_chat_entrybox() def listen_for_incoming_messages_in_a_thread(self): t = threading.Thread( target=self.recieve_message_from_server, args=(self.client_socket,)) t.start() def recieve_message_from_server(self, so): while True: buf = so.recv(256) if not buf: break self.chat_transcript_area.insert('end', buf.decode('utf-8') + '\n') self.chat_transcript_area.yview(END) so.close() def display_name_section(self): frame = Frame() Label(frame, text='Enter your name:').pack(side='left') self.name_widget = Entry(frame, width=20) self.name_widget.pack(side='left', anchor='e') frame.pack(side='top', anchor='w') def display_chat_transcript(self): frame = Frame() Label(frame, text='Chat Transcript:').pack(side='top', anchor='w') self.chat_transcript_area = Text(frame, width=60, height=20) scrollbar = Scrollbar( frame, command=self.chat_transcript_area.yview, orient=VERTICAL) self.chat_transcript_area.config(yscrollcommand=scrollbar.set) self.chat_transcript_area.bind('', lambda e: 'break') self.chat_transcript_area.pack(side='left') scrollbar.pack(side='right', fill='y') frame.pack(side='top') def display_chat_entrybox(self): frame = Frame() Label(frame, text='Enter chat messages:').pack(side='top', anchor='w') self.enter_text_widget = Text(frame, width=60, height=8) scrollbar = Scrollbar( self.root, command=self.enter_text_widget.yview, orient=VERTICAL) self.enter_text_widget.config(yscrollcommand=scrollbar.set) self.enter_text_widget.pack(side='left') scrollbar.pack(side='right', fill='y') self.enter_text_widget.bind('', self.on_enter_key_pressed) frame.pack(side='top') def on_enter_key_pressed(self, event): if len(self.name_widget.get()) == 0: messagebox.showerror( "Enter your name", "Enter your name to send a message") return self.send_chat() self.clear_text() def clear_text(self): self.enter_text_widget.delete(1.0, 'end') def send_chat(self): senders_name = self.name_widget.get().strip() + ":" data = self.enter_text_widget.get(1.0, 'end').strip() message = (senders_name + data).encode('utf-8') self.chat_transcript_area.insert('end', message.decode('utf-8') + '\n') self.chat_transcript_area.yview(END) self.client_socket.send(message) self.enter_text_widget.delete(1.0, 'end') return 'break' if __name__ == '__main__': root = Tk() ChatClient(root) root.mainloop() ================================================ FILE: Chapter 09/9.11_phonebook.py ================================================ ''' Code illustration: 9.11 Phonebook Application Tkinter GUI Application Development Blueprints ''' from tkinter import Tk, Button, PhotoImage, Label, LabelFrame, W, E, Entry, END, \ Toplevel from tkinter import ttk import sqlite3 class PhoneBook: db_filename = 'phonebook.db' def __init__(self, root): self.root = root self.create_gui() def execute_db_query(self, query, parameters=()): with sqlite3.connect(self.db_filename) as conn: cursor = conn.cursor() query_result = cursor.execute(query, parameters) conn.commit() return query_result def create_gui(self): self.create_left_icon() self.create_label_frame() self.create_message_area() self.create_tree_view() self.create_bottom_buttons() self.view_records() def create_left_icon(self): photo = PhotoImage(file='icons/phonebookicon.gif') label = Label(image=photo) label.image = photo label.grid(row=0, column=0) def create_label_frame(self): labelframe = LabelFrame(self.root, text='Create New Record') labelframe.grid(row=0, column=1, padx=8, pady=8, sticky='ew') Label(labelframe, text='Name:').grid(row=1, column=1, sticky=W, pady=2) self.namefield = Entry(labelframe) self.namefield.grid(row=1, column=2, sticky=W, padx=5, pady=2) Label(labelframe, text='Contact Number:').grid( row=2, column=1, sticky=W, pady=2) self.numfield = Entry(labelframe) self.numfield.grid(row=2, column=2, sticky=W, padx=5, pady=2) ttk.Button(labelframe, text='Add Record', command=self.on_add_record_button_clicked).grid( row=3, column=2, sticky=E, padx=5, pady=2) def create_message_area(self): self.message = Label(text='', fg='red') self.message.grid(row=3, column=1, sticky=W) def create_tree_view(self): self.tree = ttk.Treeview(height=5, columns=2) self.tree.grid(row=4, column=0, columnspan=2) self.tree.heading('#0', text='Name', anchor=W) self.tree.heading(2, text='Phone Number', anchor=W) def create_bottom_buttons(self): ttk.Button(text='Delete Selected', command=self.on_delete_selected_button_clicked).grid( row=5, column=0, sticky=W) ttk.Button(text='Modify Selected', command=self.on_modify_selected_button_clicked).grid( row=5, column=1, sticky=W) def on_add_record_button_clicked(self): self.add_new_record() def on_delete_selected_button_clicked(self): self.message['text'] = '' try: self.tree.item(self.tree.selection())['values'][0] except IndexError as e: self.message['text'] = 'No item selected to delete' return self.delete_record() def on_modify_selected_button_clicked(self): self.message['text'] = '' try: self.tree.item(self.tree.selection())['values'][0] except IndexError as e: self.message['text'] = 'No item selected to modify' return self.open_modify_window() def add_new_record(self): if self.new_records_validated(): query = 'INSERT INTO contacts VALUES(NULL,?, ?)' parameters = (self.namefield.get(), self.numfield.get()) self.execute_db_query(query, parameters) self.message['text'] = 'Phone record of {} added'.format( self.namefield.get()) self.namefield.delete(0, END) self.numfield.delete(0, END) else: self.message['text'] = 'name and phone number cannot be blank' self.view_records() def new_records_validated(self): return len(self.namefield.get()) != 0 and len(self.numfield.get()) != 0 def view_records(self): items = self.tree.get_children() for item in items: self.tree.delete(item) query = 'SELECT * FROM contacts ORDER BY name desc' phone_book_entries = self.execute_db_query(query) for row in phone_book_entries: self.tree.insert('', 0, text=row[1], values=row[2]) def delete_record(self): self.message['text'] = '' name = self.tree.item(self.tree.selection())['text'] query = 'DELETE FROM contacts WHERE name = ?' self.execute_db_query(query, (name,)) self.message['text'] = 'Phone record for {} deleted'.format(name) self.view_records() def open_modify_window(self): name = self.tree.item(self.tree.selection())['text'] old_phone_number = self.tree.item(self.tree.selection())['values'][0] self.transient = Toplevel() Label(self.transient, text='Name:').grid(row=0, column=1) Entry(self.transient, textvariable=StringVar( self.transient, value=name), state='readonly').grid(row=0, column=2) Label(self.transient, text='Old Phone Number:').grid(row=1, column=1) Entry(self.transient, textvariable=StringVar( self.transient, value=old_phone_number), state='readonly').grid(row=1, column=2) Label(self.transient, text='New Phone Number:').grid( row=2, column=1) new_phone_number_entry_widget = Entry(self.transient) new_phone_number_entry_widget.grid(row=2, column=2) Button(self.transient, text='Update Record', command=lambda: self.update_record( new_phone_number_entry_widget.get(), old_phone_number, name)).grid(row=3, column=2, sticky=E) self.transient.mainloop() def update_record(self, newphone, old_phone_number, name): query = 'UPDATE contacts SET contactnumber=? WHERE contactnumber=? AND name=?' parameters = (newphone, old_phone_number, name) self.execute_db_query(query, parameters) self.transient.destroy() self.message['text'] = 'Phone number of {} modified'.format(name) self.view_records() if __name__ == '__main__': root = Tk() application = PhoneBook(root) root.mainloop() ================================================ FILE: Chapter 09/9.12_async_demo.py ================================================ from tkinter import Tk, Button import asyncio import threading import random def asyncio_thread(event_loop): print('The tasks of fetching multiple URLs begins') event_loop.run_until_complete(simulate_fetch_all_urls()) def execute_tasks_in_a_new_thread(event_loop): """ Button-Event-Handler starting the asyncio part. """ threading.Thread(target=asyncio_thread, args=(event_loop, )).start() async def simulate_fetch_one_url(url): """ We simulate fetching of URL by sleeping for a random time """ seconds = random.randint(1, 8) await asyncio.sleep(seconds) return 'url: {}\t fetched in {} seconds'.format(url, seconds) async def simulate_fetch_all_urls(): """ Creating and starting 10 i/o bound tasks. """ all_tasks = [simulate_fetch_one_url(url) for url in range(10)] completed, pending = await asyncio.wait(all_tasks) results = [task.result() for task in completed] print('\n'.join(results)) def check_if_button_freezed(): print( 'This button is responsive even when a list of i/o tasks are in progress' ) def main(event_loop): root = Tk() Button( master=root, text='Fetch All URLs', command=lambda: execute_tasks_in_a_new_thread(event_loop)).pack() Button( master=root, text='This will not Freeze', command=check_if_button_freezed).pack() root.mainloop() if __name__ == '__main__': event_loop = asyncio.get_event_loop() main(event_loop) ================================================ FILE: Chapter 09/9.13.arduino_sketch.ino ================================================ const int triggerPin = 8; const int echoBackPin = 7; void setup() { Serial.begin(9600); pinMode(triggerPin, OUTPUT); pinMode(echoBackPin, INPUT); } void loop() { long duration, distanceIncm; // trigger ultrasound ping digitalWrite(triggerPin, LOW); delayMicroseconds(2); digitalWrite(triggerPin, HIGH); delayMicroseconds(5); digitalWrite(triggerPin, LOW); // receive input from the sensor duration = pulseIn(echoBackPin, HIGH); //calculate distance distanceIncm = duration / 29 / 2; // send data over serial port Serial.print('distance in cm is :'); Serial.print(distanceIncm); Serial.println(); delay(100); } ================================================ FILE: Chapter 09/9.14_read_from_serial_port.py ================================================ from tkinter import Tk, Label import serial ser = serial.Serial() ser.port = "/dev/ttyUSB0" ser.baudrate = 9600 try: ser.open() except serial.SerialException: print("Could not open serial port: " + ser.port) root = Tk() root.geometry('{}x{}'.format(200, 100)) label = Label(root, font=("Helvetica", 26)) label.pack(fill='both') def read_serial_data(): if ser.isOpen(): try: response = ser.readline() print(response) label.config( text='Distance : \n' + response.decode("utf-8").rstrip() + ' cm') except serial.SerialException: print("no message received") root.after(100, read_serial_data) read_serial_data() root.mainloop() ================================================ FILE: Chapter 09/readme.txt ================================================ ==================================================================== Code Readme Tkinter GUI Application Development Blueprints Chapter 9: Multiple Fun Projects ==================================================================== Code Description: Code 9.01: Race condition demo Code 9.02: Avoiding race condition with locks Code 9.03: Threading with Queue Simple Demo Code 9.04: Snake Game Code 9.05: Fetching web content with urllib - demo Code 9.06: Weather Reporter Code 9.07: Socket Demo Code 9.08: Port Scanner Code 9.09: Chat Server Code 9.10: Chat Client Code 9.11: Phone Book Code 9.12: Asyncio Demo Code 9.13: Arduino Skecth.ino Code 9.14: Reading from Serial Port Directory 'icons' contains all images used in the programs Directory 'weatherimages' contains all images used by Weather Reporter program phonebook.db - the sqlite database accompanying code 9.11 Phone Book ================================================ FILE: Chapter 10/10.01_trace_variable.py ================================================ """ Code illustration: 10.01 Tkinter Trace Variable Demo Tkinter GUI Application Development Blueprints """ from tkinter import Tk, Label, Entry, StringVar root = Tk() my_variable = StringVar() def trace_when_my_variable_written(var, indx, mode): print ("Traced variable {}".format(my_variable.get())) my_variable.trace_variable("w", trace_when_my_variable_written) Label(root, textvariable=my_variable).pack(padx=5, pady=5) Entry(root, textvariable=my_variable).pack(padx=5, pady=5) root.mainloop() ================================================ FILE: Chapter 10/10.02_widget_traversal.py ================================================ """ Code illustration: 10.02 Widget Traversal Tkinter GUI Application Development Blueprints """ from tkinter import Tk, Text, NSEW, Frame,Entry, Button, Radiobutton, Scale, X,\ Label, END, HORIZONTAL class TraversalDemo: def __init__(self, root): frame = Frame( root, takefocus=1, highlightthickness=2, highlightcolor='red') entry = Entry(frame) entry.pack(fill=X, expand=1) entry.insert(END, 'Tabs jumps to next widget') frame.pack(fill=X, expand=1) frame.focus_force() frame = Frame(root, highlightthickness=2, highlightcolor='red') for button_text, column_number in (('A', 1), ('B', 2), ('C', 3), ('D', 4)): Button(frame, text=button_text, highlightthickness=2).grid( padx=10, pady=6, row=0, column=column_number, sticky=NSEW) frame.pack(fill=X, expand=1) frame = Frame( root, takefocus=1, highlightthickness=2, highlightcolor='red') for i in range(4): Radiobutton(frame, text=i, value=i).grid( padx=10, pady=6, row=1, column=i, sticky=NSEW) frame.pack(fill=X, expand=1) frame = Frame( root, takefocus=1, highlightthickness=2, highlightcolor='red') text = Text(frame, height=4) text.insert( END, 'Tabs does not jump to the next widget from inside the Text widget.\nUse Ctrl + Tab to traverse') text.grid(row=0, column=1, columnspan=3) frame.pack(fill=X, expand=1) frame = Frame(root, takefocus=0) Label(frame, text='use left/right key').pack() Scale(frame, from_=0.0, to=100.0, orient=HORIZONTAL, takefocus=1, highlightthickness=2, highlightcolor='red').pack() frame.pack(fill=X, expand=1) root = Tk() app = TraversalDemo(root) root.mainloop() ================================================ FILE: Chapter 10/10.03_validation_mode_demo.py ================================================ """ Code illustration: 10.03 Validation Modes Demo Tkinter GUI Application Development Blueprints """ import tkinter as tk class ValidateModeDemo(): def __init__(self): self.root = tk.Tk() vcmd = (self.root.register(self.validate_data), '%V') # validate = none mode - will not call validate_data method ever. tk.Label(text='None').pack() tk.Entry(self.root, validate="none", validatecommand=vcmd).pack() # validate = focus mode - will call validate method_data on focusin and # focusout tk.Label(text='Focus').pack() tk.Entry(self.root, validate="focus", validatecommand=vcmd).pack() # validate = focusin mode - - will call validate_data method on focusin tk.Label(text='Focusin').pack() tk.Entry(self.root, validate="focusin", validatecommand=vcmd).pack() # validate = focusout mode - will call validate_data method on focusout tk.Label(text='Focus Out').pack() tk.Entry(self.root, validate="focusout", validatecommand=vcmd).pack() # validate = Key mode - will call validate_data method only when you # type something or edit the entry tk.Label(text='key').pack() tk.Entry(self.root, validate="key", validatecommand=vcmd).pack() # validate = all mode - will call validate_data method on focus and key # events tk.Label(text='all').pack() tk.Entry(self.root, validate="all", validatecommand=vcmd).pack() self.root.mainloop() def validate_data(self, v): print ('validate_data called via mode : {}'.format(v)) # this is where you will validate your data and return True or False # depending on wether the data is valid or not # for now let us just return True for all cases. return True app = ValidateModeDemo() ================================================ FILE: Chapter 10/10.04_percent _substitutions _demo.py ================================================ """ Code illustration: 10.04 Demonstration of percent substitutions in data validation Tkinter GUI Application Development Blueprints """ import tkinter as tk class PercentSubstitutionsDemo(): def __init__(self): self.root = tk.Tk() tk.Label(text='Type Something Below').pack() vcmd = (self.root.register(self.validate), '%d', '%i', '%P', '%s', '%S', '%v', '%V', '%W') tk.Entry(self.root, validate="all", validatecommand=vcmd).pack() self.root.mainloop() def validate(self, d, i, P, s, S, v, V, W): print("Following Data is received for running our validation checks:") print("d:{}".format(d)) print("i:{}".format(i)) print("P:{}".format(P)) print("s:{}".format(s)) print("S:{}".format(S)) print("v:{}".format(v)) print("V:{}".format(V)) print("W:{}".format(W)) # returning true for now # in actual validation you return true if data is valid else return # false return True app = PercentSubstitutionsDemo() ================================================ FILE: Chapter 10/10.05_key_validation.py ================================================ """ Code illustration: 10.05 validate='key' demo Tkinter GUI Application Development Blueprints """ import tkinter as tk class KeyValidationDemo(): def __init__(self): root = tk.Tk() tk.Label( root, text='Enter your name / only alpabets & space allowed').pack() vcmd = (root.register(self.validate_data), '%S') invcmd = (root.register(self.invalid_name), '%S') tk.Entry(root, validate="key", validatecommand=vcmd, invalidcommand=invcmd).pack(pady=5, padx=5) self.error_message = tk.Label(root, text='', fg='red') self.error_message.pack() root.mainloop() def validate_data(self, S): print("S={}".format(S)) self.error_message.config(text='') return (S.isalpha() or S == ' ') def invalid_name(self, S): self.error_message.config( text='Invalid character %s \n name can only have alphabets and spaces' % S) app = KeyValidationDemo() ================================================ FILE: Chapter 10/10.06_focus_out _validation.py ================================================ """ Code illustration: 10.06 validate='focusout' demo Tkinter GUI Application Development Blueprints """ import tkinter as tk import re class FocusOutValidationDemo(): def __init__(self): self.master = tk.Tk() self.error_message = tk.Label(text='', fg='red') self.error_message.pack() tk.Label(text='Enter Email Address').pack() vcmd = (self.master.register(self.validate_email), '%P') invcmd = (self.master.register(self.invalid_email), '%P') self.email_entry = tk.Entry( self.master, validate="focusout", validatecommand=vcmd, invalidcommand=invcmd) self.email_entry.pack() tk.Button(self.master, text="Login").pack() tk.mainloop() def validate_email(self, P): self.error_message.config(text='') x = re.match(r"[^@]+@[^@]+\.[^@]+", P) return (x != None) def invalid_email(self, P): self.error_message.config(text='Invalid Email Address') self.email_entry.focus_set() app = FocusOutValidationDemo() ================================================ FILE: Chapter 10/10.07_formatting_entry_widget_to_display_date.py ================================================ """ Code illustration: 10.07 Formatting Entry Widget Tkinter GUI Application Development Blueprints """ from tkinter import Tk, Entry, Label, StringVar, INSERT class FormatEntryWidgetDemo: def __init__(self, root): Label(root, text='Date(MM/DD/YYYY)').pack() self.entered_date = StringVar() self.date_entry = Entry(textvariable=self.entered_date) self.date_entry.pack(padx=5, pady=5) self.date_entry.focus_set() self.slash_positions = [2, 5] root.bind('', self.format_date_entry_widget) def format_date_entry_widget(self, event): entry_list = [c for c in self.entered_date.get() if c != '/'] for pos in self.slash_positions: if len(entry_list) > pos: entry_list.insert(pos, '/') self.entered_date.set(''.join(entry_list)) # Controlling cursor cursor_position = self.date_entry.index( INSERT) # current cursor position for pos in self.slash_positions: if cursor_position == (pos + 1): # if cursor position is on slash cursor_position += 1 if event.keysym not in ['BackSpace', 'Right', 'Left', 'Up', 'Down']: self.date_entry.icursor(cursor_position) root = Tk() FormatEntryWidgetDemo(root) root.mainloop() ================================================ FILE: Chapter 10/10.08_font_demo.py ================================================ """ Code illustration: 10.08 font Demo Tkinter GUI Application Development Blueprints """ from tkinter import Tk, Label, Pack, font root = Tk() label = Label(root, text="Humpty Dumpty was pushed") label.pack() current_font = font.Font(font=label['font']) print ('Actual :', str(current_font.actual())) print ('Family : ', current_font.cget("family")) print ('Weight : ', current_font.cget("weight")) print ('Text width of Dumpty : {}'.format(current_font.measure("Dumpty"))) print ('Metrics:', str(current_font.metrics())) current_font.config(size=14) label.config(font=current_font) print ('New Actual :', str(current_font.actual())) root.mainloop() ================================================ FILE: Chapter 10/10.09_all_fonts_on_a_system.py ================================================ """ Code illustration: 10.09 Fetching tuple of all fonts installed on a system Tkinter GUI Application Development Blueprints """ from tkinter import Tk, font root = Tk() all_fonts = font.families() print(all_fonts) ================================================ FILE: Chapter 10/10.10_font_selector.py ================================================ """ Code illustration: 10.10 Font Selector Tkinter GUI Application Development Blueprints """ from tkinter import Tk, StringVar, Label, Entry, Text, BooleanVar, Checkbutton, \ INSERT, DISABLED, ttk, font class FontSelectorDemo(): def __init__(self): self.current_font = font.Font(font=('Times New Roman', 12)) self.family = StringVar(value='Times New Roman') self.size = StringVar(value='12') self.weight = StringVar(value=font.NORMAL) self.slant = StringVar(value=font.ROMAN) self.underline = BooleanVar(value=False) self.overstrike = BooleanVar(value=False) self.sample_text = 'The quick brown fox jumps over the lazy dog' self.gui_creator() def gui_creator(self): # font family selector combobox Label(text='Font Family').grid(row=0, column=0) font_list = ttk.Combobox(textvariable=self.family) font_list.grid( row=1, column=0, columnspan=2, sticky='nsew', padx=10) font_list.bind('<>', self.on_value_change) all_fonts = list(font.families()) all_fonts.sort() font_list['values'] = all_fonts # Font Sizes Label(text='Font Size').grid(row=0, column=2) sizeList = ttk.Combobox(textvariable=self.size) sizeList.bind('<>', self.on_value_change) sizeList.grid( row=1, column=2, columnspan=2, sticky='nsew', padx=10) all_sizes = list(range(6, 70)) sizeList['values'] = all_sizes # Font Styles Checkbutton(text='bold', variable=self.weight, command=self.on_value_change, onvalue='bold', offvalue='normal').grid(row=2, column=0) Checkbutton(text='italic', variable=self.slant, command=self.on_value_change, onvalue='italic', offvalue='roman').grid(row=2, column=1) Checkbutton(text='underline', variable=self.underline, command=self.on_value_change, onvalue=True, offvalue=False).grid(row=2, column=2) Checkbutton(text='overstrike', variable=self.overstrike, command=self.on_value_change, onvalue=True, offvalue=False).grid(row=2, column=3) self.text = Text() self.text.columnconfigure(1, weight=1) self.text.grid( row=3, column=0, columnspan=10, padx=10, pady=10, sticky='ew') self.text.insert(INSERT, '{}\n{}'.format( self.sample_text, self.sample_text.upper()), 'fontspecs') self.text.config(state=DISABLED) def on_value_change(self, event=None): self.current_font.config(family=self.family.get(), size=self.size.get(), weight=self.weight.get(), slant=self.slant.get(), underline=self.underline.get(), overstrike=self.overstrike.get()) self.text.tag_config('fontspecs', font=self.current_font) if __name__ == '__main__': root = Tk() root.resizable(0, 0) font = FontSelectorDemo() root.mainloop() ================================================ FILE: Chapter 10/10.11_reading_from_command_line.py ================================================ """ Code illustration: 10.11 Reading from the command line Tkinter GUI Application Development Blueprints """ from tkinter import Tk, Text, END from subprocess import Popen, PIPE root = Tk() text = Text(root) text.pack() # replace "ls" with "dir" in the next line on windows platform with Popen(["ls"], stdout=PIPE, bufsize=1, universal_newlines=True) as p: for line in p.stdout: text.insert(END, line) root.mainloop() ================================================ FILE: Chapter 10/10.12_tkinter_class_hierarchy.py ================================================ """ Code illustration: 10.12 Tkinter Class Hierarchy Inspect Tkinter GUI Application Development Blueprints """ import tkinter import inspect print ('Class Hierarchy for Frame Widget') for i, classname in enumerate(inspect.getmro(tkinter.Frame)): print('\t{}: {}'.format(i, classname)) print ('Class Hierarchy for Toplevel') for i, classname in enumerate(inspect.getmro(tkinter.Toplevel)): print ('\t{}:{}'.format(i, classname)) print ('Class Hierarchy for Tk') for i, classname in enumerate(inspect.getmro(tkinter.Tk)): print ('\t{}: {}'.format(i, classname)) ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2018 Packt Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # Tkinter GUI Application Development Blueprints - Second Edition This is the code repository for [Tkinter GUI Application Development Blueprints - Second Edition](https://www.packtpub.com/application-development/tkinter-gui-application-development-blueprints-second-edition?utm_source=github&utm_medium=repository&utm_campaign=9781788837460), published by [Packt](https://www.packtpub.com/?utm_source=github). It contains all the supporting project files necessary to work through the book from start to finish. ## About the Book Tkinter is the built-in GUI package that comes with standard Python distributions. It is a cross-platform package, which means you build once and deploy everywhere. It is simple to use and intuitive in nature, making it suitable for programmers and non-programmers alike. This book will help you master the art of GUI programming. It delivers the bigger picture of GUI programming by building real-world, productive, and fun applications such as a text editor, drum machine, game of chess, audio player, drawing application, piano tutor, chat application, screen saver, port scanner, and much more. In every project, you will build on the skills acquired in the previous project and gain more expertise. You will learn to write multithreaded programs, network programs, database-driven programs, asyncio based programming and more. You will also get to know the modern best practices involved in writing GUI apps. ## Instructions and Navigation All of the code is organized into folders. Each folder starts with a number followed by the application name. For example, Chapter02. All chapters have code files placed in their respective folder. The code will look like the following: ``` def toggle_play_button_state(self): if self.now_playing: self.play_button.config(state="disabled") else: self.play_button.config(state="normal") ``` We assume an introductory level familiarity with the basic constructs of Python programming language. We use Python version 3.6 with Tkinter 8.6, and it is recommended to stick to these exact versions to avoid compatibility issues. The programs discussed in this book have been developed on the Linux Mint platform. However, given the multiplatform abilities of Tkinter, you can easily work along on other platforms such as Windows, Mac OS, and other distributions of Linux. The links to download and install other project-specific modules and software are mentioned in the respective chapters. ## Related Products * [Tkinter GUI Application Development Projects [Video]](https://www.packtpub.com/application-development/tkinter-gui-application-development-projects-video?utm_source=github&utm_medium=repository&utm_campaign=9781787280151) * [Tkinter GUI Application Development Blueprints](https://www.packtpub.com/application-development/tkinter-gui-application-development-blueprints?utm_source=github&utm_medium=repository&utm_campaign=9781785889738) * [Tkinter GUI Application Development HOTSHOT](https://www.packtpub.com/application-development/tkinter-gui-application-development-hotshot?utm_source=github&utm_medium=repository&utm_campaign=9781849697941) ### Download a free PDF If you have already purchased a print or Kindle version of this book, you can get a DRM-free PDF version at no cost.
Simply click on the link to claim your free PDF.

https://packt.link/free-ebook/9781788837460