├── .gitignore ├── Ch1.zip ├── Ch1 ├── ch1-1.py ├── ch1-2.py ├── ch1-3.py ├── ch1-4.py ├── ch1-5.py └── ch1-6.py ├── Ch10 ├── addfriendwindow.py ├── avatarwindow.py ├── chatwindow.py ├── demo │ ├── demo-thread.py │ ├── demo.py │ └── req.py ├── friendslist.py ├── images │ └── default.png ├── listeningthread.py ├── requester.py ├── server │ ├── conversation.py │ ├── create_database.py │ ├── database.py │ ├── requirements.txt │ └── server.py ├── smilies │ ├── mikulka-smile-cool.png │ ├── mikulka-smile-grin.png │ ├── mikulka-smile-license.txt │ ├── mikulka-smile-overview.png.ignore │ ├── mikulka-smile-razz.png │ ├── mikulka-smile-sad.png │ ├── mikulka-smile-smile.png │ ├── mikulka-smile-source.xcf │ ├── mikulka-smile-surprised.png │ └── mikulka-smile-wink.png └── smilieselect.py ├── Ch11 ├── appimage │ └── tkedit.desktop ├── tkedit-snap │ └── snap │ │ └── snapcraft.yaml ├── tkedit │ ├── colourchooser.py │ ├── findwindow.py │ ├── fontchooser.py │ ├── highlighter.py │ ├── languages │ │ ├── python.yaml │ │ └── sql.yaml │ ├── linenumbers.py │ ├── requirements.txt │ ├── schemes │ │ ├── default.yaml │ │ └── font.yaml │ ├── setup.py │ ├── setup_cx_freeze.py │ ├── textarea.py │ ├── texteditor.py │ └── tkedit.py └── widgets │ └── label_frame.py ├── Ch2 ├── Ch2-class-2.py ├── Ch2-class.py └── ch2.py ├── Ch3 ├── ch3-canvas.py └── ch3.py ├── Ch4 ├── .gitignore ├── casino │ ├── __init__.py │ ├── card.py │ ├── deck.py │ ├── hand.py │ └── player.py ├── casino_sounds │ └── __init__.py └── ch4.py ├── Ch5 ├── custom_events.py ├── events.py ├── findwindow.py ├── textarea.py ├── texteditor.py ├── tk_Style_example.py ├── ttk_Example.py ├── ttk_inheritance.py └── ttk_style_example.py ├── Ch6 ├── demo │ ├── indexing.py │ ├── indexing2.py │ ├── searching.py │ └── tagging.py ├── findwindow.py ├── highlighter.py ├── languages │ └── python.yaml ├── linenumbers.py ├── requirements.txt ├── textarea.py └── texteditor.py ├── Ch7 ├── .gitignore ├── colourchooser.py ├── demo │ └── menu.py ├── findwindow.py ├── fontchooser.py ├── highlighter.py ├── languages │ ├── python.yaml │ └── sql.yaml ├── linenumbers.py ├── requirements.txt ├── schemes │ ├── default.yaml │ └── font.yaml ├── textarea.py └── texteditor.py ├── Ch8 ├── chatwindow.py ├── demo │ └── demo.py ├── friendslist.py ├── images │ └── avatar.png ├── smilies │ ├── mikulka-smile-cool.png │ ├── mikulka-smile-grin.png │ ├── mikulka-smile-license.txt │ ├── mikulka-smile-overview.png.ignore │ ├── mikulka-smile-razz.png │ ├── mikulka-smile-sad.png │ ├── mikulka-smile-smile.png │ ├── mikulka-smile-source.xcf │ ├── mikulka-smile-surprised.png │ └── mikulka-smile-wink.png └── smilieselect.py ├── Ch9 ├── chatwindow.py ├── friendslist.py ├── images │ └── avatar.png ├── requester.py ├── server │ ├── conversation.py │ ├── create_database.py │ ├── database.py │ ├── database_schema.sql │ ├── requirements.txt │ └── server.py ├── smilies │ ├── mikulka-smile-cool.png │ ├── mikulka-smile-grin.png │ ├── mikulka-smile-license.txt │ ├── mikulka-smile-overview.png.ignore │ ├── mikulka-smile-razz.png │ ├── mikulka-smile-sad.png │ ├── mikulka-smile-smile.png │ ├── mikulka-smile-source.xcf │ ├── mikulka-smile-surprised.png │ └── mikulka-smile-wink.png └── smilieselect.py ├── assets ├── README.md ├── resize.py ├── sounds │ ├── cardPlace1.wav │ ├── cardShuffle.wav │ ├── chipsStack6.wav │ └── readme.txt └── tabletop.png └── ch4-mod ├── counter ├── __init__.py ├── countdown.py └── countup.py └── mymod.py /.gitignore: -------------------------------------------------------------------------------- 1 | assets/* 2 | card-art/* 3 | -------------------------------------------------------------------------------- /Ch1.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dvlv/Tkinter-Gui-Programming-By-Example-Packt/6ddac5df32297b9ebaa19d1f08212337724dfed7/Ch1.zip -------------------------------------------------------------------------------- /Ch1/ch1-1.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | 3 | class Window(tk.Tk): 4 | def __init__(self): 5 | super().__init__() 6 | self.title("Hello Tkinter") 7 | 8 | label = tk.Label(self, text="Hello World!") 9 | label.pack(fill=tk.BOTH, expand=1, padx=100, pady=50) 10 | 11 | 12 | if __name__ == "__main__": 13 | window = Window() 14 | window.mainloop() 15 | 16 | -------------------------------------------------------------------------------- /Ch1/ch1-2.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | 3 | class Window(tk.Tk): 4 | def __init__(self): 5 | super().__init__() 6 | self.title("Hello Tkinter") 7 | 8 | self.label = tk.Label(self, text="Choose One") 9 | self.label.pack(fill=tk.BOTH, expand=1, padx=100, pady=30) 10 | 11 | hello_button = tk.Button(self, text="Say Hello", command=self.say_hello) 12 | hello_button.pack(side=tk.LEFT, padx=(20, 0), pady=(0, 20)) 13 | 14 | goodbye_button = tk.Button(self, text="Say Goodbye", command=self.say_goodbye) 15 | goodbye_button.pack(side=tk.RIGHT, padx=(0, 20), pady=(0, 20)) 16 | 17 | def say_hello(self): 18 | self.label.configure(text="Hello World!") 19 | 20 | def say_goodbye(self): 21 | self.label.configure(text="Goodbye! \n (Closing in 2 seconds)") 22 | self.after(2000, self.destroy) 23 | 24 | 25 | if __name__ == "__main__": 26 | window = Window() 27 | window.mainloop() 28 | 29 | -------------------------------------------------------------------------------- /Ch1/ch1-3.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | 3 | class Window(tk.Tk): 4 | def __init__(self): 5 | super().__init__() 6 | self.title("Hello Tkinter") 7 | self.label_text = tk.StringVar() 8 | self.label_text.set("Choose One") 9 | 10 | self.label = tk.Label(self, textvar=self.label_text) 11 | self.label.pack(fill=tk.BOTH, expand=1, padx=100, pady=30) 12 | 13 | hello_button = tk.Button(self, text="Say Hello", command=self.say_hello) 14 | hello_button.pack(side=tk.LEFT, padx=(20, 0), pady=(0, 20)) 15 | 16 | goodbye_button = tk.Button(self, text="Say Goodbye", command=self.say_goodbye) 17 | goodbye_button.pack(side=tk.RIGHT, padx=(0, 20), pady=(0, 20)) 18 | 19 | def say_hello(self): 20 | self.label_text.set("Hello World") 21 | 22 | def say_goodbye(self): 23 | self.label_text.set("Goodbye! \n (Closing in 2 seconds)") 24 | self.after(2000, self.destroy) 25 | 26 | 27 | if __name__ == "__main__": 28 | window = Window() 29 | window.mainloop() 30 | 31 | -------------------------------------------------------------------------------- /Ch1/ch1-4.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | import tkinter.messagebox as msgbox 3 | 4 | class Window(tk.Tk): 5 | def __init__(self): 6 | super().__init__() 7 | self.title("Hello Tkinter") 8 | self.label_text = tk.StringVar() 9 | self.label_text.set("Choose One") 10 | 11 | self.label = tk.Label(self, textvar=self.label_text) 12 | self.label.pack(fill=tk.BOTH, expand=1, padx=100, pady=30) 13 | 14 | hello_button = tk.Button(self, text="Say Hello", command=self.say_hello) 15 | hello_button.pack(side=tk.LEFT, padx=(20, 0), pady=(0, 20)) 16 | 17 | goodbye_button = tk.Button(self, text="Say Goodbye", command=self.say_goodbye) 18 | goodbye_button.pack(side=tk.RIGHT, padx=(0, 20), pady=(0, 20)) 19 | 20 | def say_hello(self): 21 | msgbox.showinfo("Hello", "Hello World!") 22 | 23 | def say_goodbye(self): 24 | self.label_text.set("Window will close in 2 seconds") 25 | msgbox.showinfo("Goodbye!", "Goodbye, it's been fun!") 26 | self.after(2000, self.destroy) 27 | 28 | 29 | if __name__ == "__main__": 30 | window = Window() 31 | window.mainloop() 32 | 33 | -------------------------------------------------------------------------------- /Ch1/ch1-5.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | import tkinter.messagebox as msgbox 3 | 4 | class Window(tk.Tk): 5 | def __init__(self): 6 | super().__init__() 7 | self.title("Hello Tkinter") 8 | self.label_text = tk.StringVar() 9 | self.label_text.set("Choose One") 10 | 11 | self.label = tk.Label(self, textvar=self.label_text) 12 | self.label.pack(fill=tk.BOTH, expand=1, padx=100, pady=30) 13 | 14 | hello_button = tk.Button(self, text="Say Hello", command=self.say_hello) 15 | hello_button.pack(side=tk.LEFT, padx=(20, 0), pady=(0, 20)) 16 | 17 | goodbye_button = tk.Button(self, text="Say Goodbye", command=self.say_goodbye) 18 | goodbye_button.pack(side=tk.RIGHT, padx=(0, 20), pady=(0, 20)) 19 | 20 | def say_hello(self): 21 | msgbox.showinfo("Hello", "Hello World!") 22 | 23 | def say_goodbye(self): 24 | if msgbox.askyesno("Close Window?", "Would you like to close this window?"): 25 | self.label_text.set("Window will close in 2 seconds") 26 | self.after(2000, self.destroy) 27 | else: 28 | msgbox.showinfo("Not Closing", "Great! This window will stay open.") 29 | 30 | 31 | if __name__ == "__main__": 32 | window = Window() 33 | window.mainloop() 34 | 35 | -------------------------------------------------------------------------------- /Ch1/ch1-6.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | import tkinter.messagebox as msgbox 3 | 4 | class Window(tk.Tk): 5 | def __init__(self): 6 | super().__init__() 7 | self.title("Hello Tkinter") 8 | self.label_text = tk.StringVar() 9 | self.label_text.set("My Name Is: ") 10 | 11 | self.name_text = tk.StringVar() 12 | 13 | self.label = tk.Label(self, textvar=self.label_text) 14 | self.label.pack(fill=tk.BOTH, expand=1, padx=100, pady=10) 15 | 16 | self.name_entry = tk.Entry(self, textvar=self.name_text) 17 | self.name_entry.pack(fill=tk.BOTH, expand=1, padx=20, pady=20) 18 | 19 | hello_button = tk.Button(self, text="Say Hello", command=self.say_hello) 20 | hello_button.pack(side=tk.LEFT, padx=(20, 0), pady=(0, 20)) 21 | 22 | goodbye_button = tk.Button(self, text="Say Goodbye", command=self.say_goodbye) 23 | goodbye_button.pack(side=tk.RIGHT, padx=(0, 20), pady=(0, 20)) 24 | 25 | def say_hello(self): 26 | message = "Hello there " + self.name_entry.get() 27 | msgbox.showinfo("Hello", message) 28 | 29 | def say_goodbye(self): 30 | if msgbox.askyesno("Close Window?", "Would you like to close this window?"): 31 | message = "Window will close in 2 seconds - goodybye " + self.name_entry.get() 32 | self.label_text.set(message) 33 | self.after(2000, self.destroy) 34 | else: 35 | msgbox.showinfo("Not Closing", "Great! This window will stay open.") 36 | 37 | 38 | if __name__ == "__main__": 39 | window = Window() 40 | window.mainloop() 41 | 42 | -------------------------------------------------------------------------------- /Ch10/addfriendwindow.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | import tkinter.ttk as ttk 3 | 4 | 5 | class AddFriendWindow(tk.Toplevel): 6 | def __init__(self, master): 7 | super().__init__() 8 | self.master = master 9 | 10 | self.transient(master) 11 | self.geometry("250x100") 12 | self.title("Add a Friend") 13 | 14 | main_frame = ttk.Frame(self) 15 | 16 | username_label = ttk.Label(main_frame, text="Username") 17 | self.username_entry = ttk.Entry(main_frame) 18 | 19 | add_button = ttk.Button(main_frame, text="Add", command=self.add_friend) 20 | 21 | username_label.grid(row=0, column=0) 22 | self.username_entry.grid(row=0, column=1) 23 | self.username_entry.focus_force() 24 | 25 | add_button.grid(row=1, column=0, columnspan=2) 26 | 27 | for i in range(2): 28 | tk.Grid.columnconfigure(main_frame, i, weight=1) 29 | tk.Grid.rowconfigure(main_frame, i, weight=1) 30 | 31 | main_frame.pack(fill=tk.BOTH, expand=1) 32 | 33 | def add_friend(self): 34 | username = self.username_entry.get() 35 | 36 | if username: 37 | if self.master.add_friend(username): 38 | self.username_entry.delete(0, tk.END) 39 | -------------------------------------------------------------------------------- /Ch10/avatarwindow.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import os 3 | from PIL import Image 4 | import tkinter as tk 5 | import tkinter.ttk as ttk 6 | from tkinter import filedialog 7 | 8 | avatar_file_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "images/avatar.png")) 9 | 10 | 11 | class AvatarWindow(tk.Toplevel): 12 | def __init__(self, master): 13 | super().__init__() 14 | 15 | self.master = master 16 | self.transient(master) 17 | 18 | self.title("Change Avatar") 19 | self.geometry("350x200") 20 | 21 | self.image_file_types = [ 22 | ("Png Images", ("*.png", "*.PNG")), 23 | ] 24 | 25 | self.current_avatar_image = tk.PhotoImage(file=avatar_file_path) 26 | 27 | self.current_avatar = ttk.Label(self, image=self.current_avatar_image) 28 | choose_file_button = ttk.Button(self, text="Choose File", command=self.choose_image) 29 | 30 | self.current_avatar.pack() 31 | choose_file_button.pack() 32 | 33 | def choose_image(self): 34 | image_file = filedialog.askopenfilename(filetypes=self.image_file_types) 35 | 36 | if image_file: 37 | avatar = Image.open(image_file) 38 | avatar.thumbnail((128, 128)) 39 | avatar.save(avatar_file_path, "PNG") 40 | 41 | img_contents = "" 42 | img_b64 = "" 43 | with open(avatar_file_path, "rb") as img: 44 | img_contents = img.read() 45 | img_b64 = base64.urlsafe_b64encode(img_contents) 46 | 47 | self.master.requester.update_avatar(self.master.username, img_b64) 48 | 49 | self.current_avatar_image = tk.PhotoImage(file=avatar_file_path) 50 | self.current_avatar.configure(image=self.current_avatar_image) 51 | 52 | 53 | if __name__ == "__main__": 54 | win = tk.Tk() 55 | aw = AvatarWindow(win) 56 | win.mainloop() -------------------------------------------------------------------------------- /Ch10/chatwindow.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | 4 | import tkinter as tk 5 | import tkinter.ttk as ttk 6 | 7 | from listeningthread import ListeningThread 8 | from smilieselect import SmilieSelect 9 | 10 | 11 | class ChatWindow(tk.Toplevel): 12 | def __init__(self, master, friend_name, friend_username, friend_avatar, **kwargs): 13 | super().__init__(**kwargs) 14 | self.master = master 15 | self.title(friend_name) 16 | self.geometry('540x640') 17 | self.minsize(540, 640) 18 | 19 | self.friend_username = friend_username 20 | 21 | self.right_frame = tk.Frame(self) 22 | self.left_frame = tk.Frame(self) 23 | self.bottom_frame = tk.Frame(self.left_frame) 24 | 25 | self.messages_area = tk.Text(self.left_frame, bg="white", fg="black", wrap=tk.WORD, width=30) 26 | self.scrollbar = ttk.Scrollbar(self.left_frame, orient='vertical', command=self.messages_area.yview) 27 | self.messages_area.configure(yscrollcommand=self.scrollbar.set) 28 | 29 | self.text_area = tk.Text(self.bottom_frame, bg="white", fg="black", height=3, width=30) 30 | self.text_area.smilies = [] 31 | self.send_button = ttk.Button(self.bottom_frame, text="Send", command=self.send_message, style="send.TButton") 32 | 33 | self.smilies_image = tk.PhotoImage(file="smilies/mikulka-smile-cool.png") 34 | self.smilie_button = ttk.Button(self.bottom_frame, image=self.smilies_image, command=self.smilie_chooser, style="smilie.TButton") 35 | 36 | self.profile_picture = tk.PhotoImage(file="images/avatar.png") 37 | self.friend_profile_picture = tk.PhotoImage(file=friend_avatar) 38 | 39 | self.profile_picture_area = tk.Label(self.right_frame, image=self.profile_picture, relief=tk.RIDGE) 40 | self.friend_profile_picture_area = tk.Label(self.right_frame, image=self.friend_profile_picture, relief=tk.RIDGE) 41 | 42 | self.left_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=1) 43 | self.scrollbar.pack(side=tk.LEFT, fill=tk.Y) 44 | self.messages_area.pack(side=tk.TOP, fill=tk.BOTH, expand=1) 45 | self.messages_area.configure(state='disabled') 46 | 47 | self.right_frame.pack(side=tk.LEFT, fill=tk.Y) 48 | self.profile_picture_area.pack(side=tk.BOTTOM) 49 | self.friend_profile_picture_area.pack(side=tk.TOP) 50 | 51 | self.bottom_frame.pack(side=tk.BOTTOM, fill=tk.X) 52 | self.smilie_button.pack(side=tk.LEFT, pady=5) 53 | self.text_area.pack(side=tk.LEFT, fill=tk.X, expand=1, pady=5) 54 | self.send_button.pack(side=tk.RIGHT, pady=5) 55 | 56 | self.configure_styles() 57 | self.bind_events() 58 | self.load_history() 59 | self.protocol("WM_DELETE_WINDOW", self.close) 60 | self.listening_thread = None 61 | self.listen() 62 | 63 | def bind_events(self): 64 | self.bind("", self.send_message) 65 | self.text_area.bind("", self.send_message) 66 | 67 | self.text_area.bind('', self.smilie_chooser) 68 | 69 | def load_history(self): 70 | history = self.master.requester.prepare_conversation(self.master.username, self.friend_username) 71 | 72 | if len(history['history']): 73 | for message in history['history']: 74 | self.receive_message(message['author'], message['message']) 75 | 76 | def listen(self): 77 | self.listening_thread = ListeningThread(self, self.master.username, self.friend_username) 78 | self.listening_thread.start() 79 | 80 | def close(self): 81 | if hasattr(self, "listening_thread"): 82 | self.listening_thread.running = False 83 | self.after(100, self.close) 84 | else: 85 | self.destroy() 86 | 87 | 88 | def send_message(self, event=None): 89 | message = self.text_area.get(1.0, tk.END) 90 | 91 | if message.strip() or len(self.text_area.smilies): 92 | self.master.requester.send_message( 93 | self.master.username, 94 | self.friend_username, 95 | message, 96 | ) 97 | 98 | message = "Me: " + message 99 | self.messages_area.configure(state='normal') 100 | self.messages_area.insert(tk.END, message) 101 | 102 | if len(self.text_area.smilies): 103 | last_line_no = self.messages_area.index(tk.END) 104 | last_line_no = str(last_line_no).split('.')[0] 105 | last_line_no = str(int(last_line_no) - 2) 106 | 107 | for index, file in self.text_area.smilies: 108 | char_index = str(index).split('.')[1] 109 | char_index = str(int(char_index) + 4) 110 | smilile_index = last_line_no + '.' + char_index 111 | self.messages_area.image_create(smilile_index, image=file) 112 | 113 | self.text_area.smilies = [] 114 | 115 | self.messages_area.configure(state='disabled') 116 | 117 | self.text_area.delete(1.0, tk.END) 118 | 119 | return "break" 120 | 121 | def smilie_chooser(self, event=None): 122 | SmilieSelect(self) 123 | 124 | def add_smilie(self, smilie): 125 | smilie_index = self.text_area.index(self.text_area.image_create(tk.END, image=smilie)) 126 | self.text_area.smilies.append((smilie_index, smilie)) 127 | 128 | def receive_message(self, author, message): 129 | self.messages_area.configure(state='normal') 130 | 131 | if author == self.master.username: 132 | author = "Me" 133 | 134 | message_with_author = author + ": " + message 135 | 136 | self.messages_area.insert(tk.END, message_with_author) 137 | 138 | self.messages_area.configure(state='disabled') 139 | 140 | def configure_styles(self): 141 | style = ttk.Style() 142 | style.configure("send.TButton", background='#dddddd', foreground="black", padding=16) 143 | 144 | 145 | if __name__ == '__main__': 146 | w = tk.Tk() 147 | c = ChatWindow(w, 'friend', 'friend', 'images/avatar.png') 148 | c.protocol("WM_DELETE_WINDOW", w.destroy) 149 | w.mainloop() 150 | -------------------------------------------------------------------------------- /Ch10/demo/demo-thread.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | import time 3 | import threading 4 | 5 | 6 | class WorkThread(threading.Thread): 7 | def run(self): 8 | label.configure(text="Doing work") 9 | time.sleep(5) 10 | label.configure(text="Finished") 11 | 12 | return 13 | 14 | 15 | win = tk.Tk() 16 | win.geometry("200x150") 17 | 18 | counter = tk.IntVar() 19 | label = tk.Label(win, text="Ready to Work") 20 | counter_label = tk.Label(win, textvar=counter) 21 | 22 | 23 | def increase_counter(): 24 | counter.set(counter.get() + 1) 25 | 26 | 27 | def work(): 28 | thread = WorkThread() 29 | thread.start() 30 | 31 | 32 | counter_button = tk.Button(win, text="Increase Counter", command=increase_counter) 33 | work_button = tk.Button(win, text="Work", command=work) 34 | 35 | label.pack() 36 | counter_label.pack() 37 | 38 | counter_button.pack() 39 | work_button.pack() 40 | 41 | win.mainloop() -------------------------------------------------------------------------------- /Ch10/demo/demo.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | import time 3 | 4 | win = tk.Tk() 5 | win.geometry("200x150") 6 | 7 | counter = tk.IntVar() 8 | label = tk.Label(win, text="Ready to Work") 9 | counter_label = tk.Label(win, textvar=counter) 10 | 11 | 12 | def increase_counter(): 13 | counter.set(counter.get() + 1) 14 | 15 | 16 | def work(): 17 | label.configure(text="Doing work") 18 | time.sleep(5) 19 | label.configure(text="Finished") 20 | 21 | 22 | counter_button = tk.Button(win, text="Increase Counter", command=increase_counter) 23 | work_button = tk.Button(win, text="Work", command=work) 24 | 25 | label.pack() 26 | counter_label.pack() 27 | 28 | counter_button.pack() 29 | work_button.pack() 30 | 31 | win.mainloop() -------------------------------------------------------------------------------- /Ch10/demo/req.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | 4 | data = { 5 | "username": "david", 6 | } 7 | r = requests.post("http://127.0.0.1:5000/user_exists", data=data) 8 | 9 | print(r.text) 10 | -------------------------------------------------------------------------------- /Ch10/friendslist.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import os 3 | import tkinter as tk 4 | import tkinter.messagebox as msg 5 | import tkinter.ttk as ttk 6 | 7 | from functools import partial 8 | 9 | from chatwindow import ChatWindow 10 | from requester import Requester 11 | from avatarwindow import AvatarWindow 12 | from addfriendwindow import AddFriendWindow 13 | 14 | friend_avatars_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "images/friends")) 15 | default_avatar_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "images/default.png")) 16 | 17 | 18 | class FriendsList(tk.Tk): 19 | def __init__(self, **kwargs): 20 | super().__init__(**kwargs) 21 | 22 | self.title('Tk Chat') 23 | self.geometry('700x500') 24 | 25 | self.menu = tk.Menu(self, bg="lightgrey", fg="black", tearoff=0) 26 | 27 | self.friends_menu = tk.Menu(self.menu, fg="black", bg="lightgrey", tearoff=0) 28 | self.friends_menu.add_command(label="Add Friend", command=self.show_add_friend_window) 29 | 30 | self.avatar_menu = tk.Menu(self.menu, fg="black", bg="lightgrey", tearoff=0) 31 | self.avatar_menu.add_command(label="Change Avatar", command=self.change_avatar) 32 | 33 | self.menu.add_cascade(label="Friends", menu=self.friends_menu) 34 | self.menu.add_cascade(label="Avatar", menu=self.avatar_menu) 35 | 36 | self.requester = Requester() 37 | 38 | self.show_login_screen() 39 | 40 | def show_login_screen(self): 41 | self.login_frame = ttk.Frame(self) 42 | 43 | username_label = ttk.Label(self.login_frame, text="Username") 44 | self.username_entry = ttk.Entry(self.login_frame) 45 | self.username_entry.focus_force() 46 | 47 | real_name_label = ttk.Label(self.login_frame, text="Real Name") 48 | self.real_name_entry = ttk.Entry(self.login_frame) 49 | 50 | login_button = ttk.Button(self.login_frame, text="Login", command=self.login) 51 | create_account_button = ttk.Button(self.login_frame, text="Create Account", command=self.create_account) 52 | 53 | username_label.grid(row=0, column=0, sticky='e') 54 | self.username_entry.grid(row=0, column=1) 55 | 56 | real_name_label.grid(row=1, column=0, sticky='e') 57 | self.real_name_entry.grid(row=1, column=1) 58 | 59 | login_button.grid(row=2, column=0, sticky='e') 60 | create_account_button.grid(row=2, column=1) 61 | 62 | for i in range(3): 63 | tk.Grid.rowconfigure(self.login_frame, i, weight=1) 64 | tk.Grid.columnconfigure(self.login_frame, i, weight=1) 65 | 66 | self.login_frame.pack(fill=tk.BOTH, expand=1) 67 | 68 | self.login_event = self.bind("", self.login) 69 | 70 | def login(self, event=None): 71 | username = self.username_entry.get() 72 | real_name = self.real_name_entry.get() 73 | 74 | if self.requester.login(username, real_name): 75 | self.username = username 76 | self.real_name = real_name 77 | self.unbind("", self.login_event) 78 | self.show_friends() 79 | else: 80 | msg.showerror("Failed", f"Could not log in as {username}") 81 | 82 | def create_account(self): 83 | username = self.username_entry.get() 84 | real_name = self.real_name_entry.get() 85 | 86 | if self.requester.create_account(username, real_name): 87 | self.username = username 88 | self.real_name = real_name 89 | 90 | self.show_friends() 91 | else: 92 | msg.showerror("Failed", "Account already exists!") 93 | 94 | def show_friends(self): 95 | self.configure(menu=self.menu) 96 | self.login_frame.pack_forget() 97 | 98 | self.canvas = tk.Canvas(self, bg="white") 99 | self.canvas_frame = tk.Frame(self.canvas) 100 | 101 | self.scrollbar = ttk.Scrollbar(self, orient="vertical", command=self.canvas.yview) 102 | self.canvas.configure(yscrollcommand=self.scrollbar.set) 103 | 104 | self.scrollbar.pack(side=tk.LEFT, fill=tk.Y) 105 | self.canvas.pack(side=tk.LEFT, expand=1, fill=tk.BOTH) 106 | 107 | self.friends_area = self.canvas.create_window((0, 0), window=self.canvas_frame, anchor="nw") 108 | 109 | self.bind_events() 110 | 111 | self.load_friends() 112 | 113 | def bind_events(self): 114 | self.bind('', self.on_frame_resized) 115 | self.canvas.bind('', self.friends_width) 116 | 117 | def friends_width(self, event): 118 | canvas_width = event.width 119 | self.canvas.itemconfig(self.friends_area, width=canvas_width) 120 | 121 | def on_frame_resized(self, event=None): 122 | self.canvas.configure(scrollregion=self.canvas.bbox("all")) 123 | 124 | def load_friends(self): 125 | my_friends = self.requester.get_friends(self.username) 126 | for user in my_friends["friends"]: 127 | if user['username'] != self.username: 128 | friend_frame = ttk.Frame(self.canvas_frame) 129 | 130 | friend_avatar_path = os.path.join(friend_avatars_dir, f"{user['username']}.png") 131 | 132 | if user["avatar"]: 133 | with open(friend_avatar_path, 'wb') as friend_avatar: 134 | img = base64.urlsafe_b64decode(user['avatar']) 135 | friend_avatar.write(img) 136 | else: 137 | friend_avatar_path = default_avatar_path 138 | 139 | profile_photo = tk.PhotoImage(file=friend_avatar_path) 140 | profile_photo_label = ttk.Label(friend_frame, image=profile_photo) 141 | profile_photo_label.image = profile_photo 142 | 143 | friend_name = ttk.Label(friend_frame, text=user['real_name'], anchor=tk.W) 144 | 145 | message_this_friend = partial(self.open_chat_window, username=user["username"], real_name=user["real_name"], avatar=friend_avatar_path) 146 | block_this_friend = partial(self.block_friend, username=user["username"]) 147 | 148 | message_button = ttk.Button(friend_frame, text="Chat", command=message_this_friend) 149 | block_button = ttk.Button(friend_frame, text="Block", command=block_this_friend) 150 | 151 | profile_photo_label.pack(side=tk.LEFT) 152 | friend_name.pack(side=tk.LEFT) 153 | message_button.pack(side=tk.RIGHT) 154 | block_button.pack(side=tk.RIGHT, padx=(0, 30)) 155 | 156 | friend_frame.pack(fill=tk.X, expand=1) 157 | 158 | def reload_friends(self): 159 | for child in self.canvas_frame.winfo_children(): 160 | child.pack_forget() 161 | self.load_friends() 162 | 163 | def show_add_friend_window(self): 164 | AddFriendWindow(self) 165 | 166 | def add_friend(self, username): 167 | if self.requester.add_friend(self.username, username): 168 | msg.showinfo("Friend Added", "Friend Added") 169 | success = True 170 | self.reload_friends() 171 | else: 172 | msg.showerror("Add Failed", "Friend was not found") 173 | success = False 174 | 175 | return success 176 | 177 | def open_chat_window(self, username, real_name, avatar): 178 | cw = ChatWindow(self, real_name, username, avatar) 179 | 180 | def block_friend(self, username): 181 | self.requester.block_friend(self.username, username) 182 | self.reload_friends() 183 | 184 | def change_avatar(self): 185 | AvatarWindow(self) 186 | 187 | if __name__ == '__main__': 188 | f = FriendsList() 189 | f.mainloop() 190 | 191 | 192 | 193 | -------------------------------------------------------------------------------- /Ch10/images/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dvlv/Tkinter-Gui-Programming-By-Example-Packt/6ddac5df32297b9ebaa19d1f08212337724dfed7/Ch10/images/default.png -------------------------------------------------------------------------------- /Ch10/listeningthread.py: -------------------------------------------------------------------------------- 1 | import arrow 2 | import threading 3 | import time 4 | 5 | from requester import Requester 6 | 7 | 8 | class ListeningThread(threading.Thread): 9 | def __init__(self, master, user_one, user_two): 10 | super().__init__() 11 | self.master = master 12 | self.user_one = user_one 13 | self.user_two = user_two 14 | self.requester = Requester() 15 | self.running = True 16 | self.last_checked_time = arrow.now().timestamp 17 | 18 | def run(self): 19 | while self.running: 20 | new_messages = self.requester.get_new_messages(self.last_checked_time, self.user_one, self.user_two) 21 | self.last_checked_time = arrow.now().timestamp 22 | for message in new_messages['messages']: 23 | self.master.receive_message(message["author"], message["message"]) 24 | 25 | time.sleep(3) 26 | 27 | del self.master.listening_thread 28 | 29 | return 30 | -------------------------------------------------------------------------------- /Ch10/requester.py: -------------------------------------------------------------------------------- 1 | import json 2 | import requests 3 | 4 | 5 | class Requester: 6 | def __init__(self): 7 | self.url = "http://127.0.0.1:5000" 8 | 9 | def request(self, method, endpoint, params=None): 10 | url = self.url + endpoint 11 | 12 | if method == "GET": 13 | r = requests.get(url, params=params) 14 | 15 | return r.text 16 | else: 17 | r = requests.post(url, data=params) 18 | 19 | return r.json() 20 | 21 | def login(self, username, real_name): 22 | endpoint = "/user_exists" 23 | params = {"username": username, "real_name": real_name} 24 | 25 | user_exists = self.request("POST", endpoint, params) 26 | 27 | if user_exists["exists"]: 28 | return True 29 | 30 | return False 31 | 32 | def create_account(self, username, real_name): 33 | endpoint = "/user_exists" 34 | params = {"username": username} 35 | exists = self.request("POST", endpoint, params) 36 | 37 | if exists["exists"]: 38 | return False 39 | 40 | endpoint = "/add_user" 41 | params = {"username": username, "real_name": real_name} 42 | 43 | self.request("POST", endpoint, params) 44 | 45 | return True 46 | 47 | def get_all_users(self): 48 | endpoint = "/get_all_users" 49 | 50 | users = self.request("GET", endpoint) 51 | 52 | return json.loads(users) 53 | 54 | def prepare_conversation(self, user_one, user_two): 55 | endpoint = "/create_conversation_db" 56 | params = {"user_one": user_one, "user_two": user_two} 57 | 58 | self.request("POST", endpoint, params) 59 | 60 | endpoint = "/get_message_history" 61 | history = self.request("POST", endpoint, params) 62 | 63 | return history 64 | 65 | def send_message(self, author, friend_name, message): 66 | endpoint = f"/send_message/{friend_name}" 67 | params = { 68 | "author": author, 69 | "message": message, 70 | } 71 | 72 | self.request("POST", endpoint, params) 73 | 74 | return True 75 | 76 | def get_user_avatar(self, username): 77 | endpoint = f"/get_user_avatar/{username}" 78 | 79 | avatar = self.request("GET", endpoint) 80 | 81 | return json.loads(avatar) 82 | 83 | def update_avatar(self, username, img_b64): 84 | endpoint = f"/update_avatar/{username}" 85 | params = { 86 | "img_b64": img_b64 87 | } 88 | 89 | self.request("POST", endpoint, params) 90 | 91 | return True 92 | 93 | def get_new_messages(self, timestamp, user_one, user_two): 94 | """ user_one is the author's username, and user_two is the friend's """ 95 | endpoint = "/get_new_messages" 96 | params = { 97 | "timestamp": timestamp, 98 | "user_one": user_one, 99 | "user_two": user_two, 100 | } 101 | 102 | new_messages = self.request("POST", endpoint, params) 103 | 104 | return new_messages 105 | 106 | def add_friend(self, user_one, user_two): 107 | endpoint = "/add_friend" 108 | params = { 109 | "user_one": user_one, 110 | "user_two": user_two, 111 | } 112 | 113 | success = self.request("POST", endpoint, params) 114 | 115 | return success["success"] 116 | 117 | def get_friends(self, username): 118 | endpoint = f"/get_friends/{username}" 119 | 120 | friends = self.request("GET", endpoint) 121 | 122 | return json.loads(friends) 123 | 124 | def block_friend(self, user_one, user_two): 125 | endpoint = "/block_friend" 126 | params = { 127 | "user_one": user_one, 128 | "user_two": user_two, 129 | } 130 | 131 | self.request("POST", endpoint, params) 132 | 133 | return True 134 | -------------------------------------------------------------------------------- /Ch10/server/conversation.py: -------------------------------------------------------------------------------- 1 | import arrow 2 | import sqlite3 3 | 4 | 5 | class Conversation: 6 | def __init__(self, database): 7 | self.database = database 8 | 9 | def initialise_table(self): 10 | sql = "CREATE TABLE conversation (author text, message text, date_sent text)" 11 | conn = sqlite3.connect(self.database) 12 | cursor = conn.cursor() 13 | cursor.execute(sql) 14 | conn.commit() 15 | conn.close() 16 | 17 | def get_history(self): 18 | sql = "SELECT * FROM conversation" 19 | conn = sqlite3.connect(self.database) 20 | conn.row_factory = sqlite3.Row 21 | cursor = conn.cursor() 22 | cursor.execute(sql) 23 | results = [dict(row) for row in cursor.fetchall()] 24 | conn.close() 25 | 26 | return results 27 | 28 | def add_message(self, author, message, date_sent): 29 | sql = "INSERT INTO conversation VALUES (?, ?, ?)" 30 | params = (author, message, date_sent) 31 | conn = sqlite3.connect(self.database) 32 | cursor = conn.cursor() 33 | cursor.execute(sql, params) 34 | conn.commit() 35 | conn.close() 36 | 37 | def get_new_messages(self, timestamp, username): 38 | sql = "SELECT author, message FROM conversation WHERE date_sent > ? AND author <> ?" 39 | params = (timestamp, username) 40 | 41 | conn = sqlite3.connect(self.database) 42 | conn.row_factory = sqlite3.Row 43 | cursor = conn.cursor() 44 | cursor.execute(sql, params) 45 | results = [dict(row) for row in cursor.fetchall()] 46 | conn.close() 47 | 48 | return results -------------------------------------------------------------------------------- /Ch10/server/create_database.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | 3 | database = sqlite3.connect("chat.db") 4 | cursor = database.cursor() 5 | 6 | create_users_sql = "CREATE TABLE users (username TEXT, real_name TEXT)" 7 | cursor.execute(create_users_sql) 8 | 9 | database.commit() 10 | database.close() 11 | -------------------------------------------------------------------------------- /Ch10/server/database.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | 3 | 4 | class Database: 5 | def __init__(self): 6 | self.database = "chat.db" 7 | 8 | def perform_insert(self, sql, params): 9 | conn = sqlite3.connect(self.database) 10 | cursor = conn.cursor() 11 | cursor.execute(sql, params) 12 | conn.commit() 13 | conn.close() 14 | 15 | def perform_select(self, sql, params): 16 | conn = sqlite3.connect(self.database) 17 | conn.row_factory = sqlite3.Row 18 | cursor = conn.cursor() 19 | cursor.execute(sql, params) 20 | results = [dict(row) for row in cursor.fetchall()] 21 | conn.close() 22 | 23 | return results 24 | 25 | def add_user(self, username, real_name): 26 | sql = "INSERT INTO users (username, real_name) VALUES (?,?)" 27 | query_params = (username, real_name) 28 | 29 | self.perform_insert(sql, query_params) 30 | 31 | def get_all_users(self): 32 | sql = "SELECT username, real_name, avatar FROM users" 33 | params = [] 34 | 35 | return self.perform_select(sql, params) 36 | 37 | def user_exists(self, username): 38 | sql = "SELECT username FROM users WHERE username = ?" 39 | params = (username,) 40 | 41 | results = self.perform_select(sql, params) 42 | 43 | if len(results): 44 | return True 45 | 46 | return False 47 | 48 | def update_avatar(self, username, img_b64): 49 | sql = "UPDATE users SET avatar=? WHERE username=?" 50 | params = (img_b64, username) 51 | 52 | return self.perform_insert(sql, params) 53 | 54 | def get_user_avatar(self, username): 55 | sql = "SELECT avatar FROM users WHERE username=?" 56 | params = (username,) 57 | 58 | return self.perform_select(sql, params) 59 | 60 | def add_friend(self, user_one, user_two): 61 | sql = "INSERT INTO friends (user_one, user_two, blocked) VALUES (?,?,0)" 62 | query_params = (user_one, user_two) 63 | 64 | self.perform_insert(sql, query_params) 65 | 66 | def get_friends(self, username): 67 | all_friends = [] 68 | sql = "SELECT user_two FROM friends WHERE user_one=? AND blocked=0" 69 | params = (username,) 70 | friends = self.perform_select(sql, params) 71 | 72 | sql = "SELECT user_one FROM friends WHERE user_two=? AND blocked=0" 73 | friends2 = self.perform_select(sql, params) 74 | 75 | for friend in friends: 76 | all_friends.append(friend["user_two"]) 77 | for friend in friends2: 78 | all_friends.append(friend["user_one"]) 79 | 80 | return all_friends 81 | 82 | def get_users_by_usernames(self, usernames): 83 | question_marks = ','.join(['?' for user in usernames]) 84 | sql = f"SELECT * FROM users WHERE username IN ({question_marks})" 85 | params = [user for user in usernames] 86 | 87 | friends = self.perform_select(sql, params) 88 | 89 | return friends 90 | 91 | def block_friend(self, username, contact_to_block): 92 | sql = "UPDATE friends SET blocked=1 WHERE (user_one = ? AND user_two = ?) OR (user_two = ? AND user_one = ?)" 93 | query_params = (username, contact_to_block, username, contact_to_block) 94 | 95 | self.perform_insert(sql, query_params) 96 | -------------------------------------------------------------------------------- /Ch10/server/requirements.txt: -------------------------------------------------------------------------------- 1 | arrow==0.12.1 2 | certifi==2018.1.18 3 | chardet==3.0.4 4 | click==6.7 5 | Flask==1.0 6 | idna==2.6 7 | itsdangerous==0.24 8 | Jinja2==2.10 9 | MarkupSafe==1.0 10 | Pillow==5.0.0 11 | python-dateutil==2.7.0 12 | requests==2.22.0 13 | six==1.11.0 14 | urllib3==1.24.2 15 | Werkzeug==0.15.3 16 | -------------------------------------------------------------------------------- /Ch10/server/server.py: -------------------------------------------------------------------------------- 1 | import os 2 | import arrow 3 | 4 | from flask import Flask, session, url_for, request, g, jsonify, json 5 | 6 | from database import Database 7 | from conversation import Conversation 8 | 9 | 10 | app = Flask(__name__) 11 | app.config.from_object(__name__) 12 | 13 | app.secret_key = "tkinterguiprogrammingbyexample" 14 | 15 | database = Database() 16 | 17 | conversations_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), 'conversations/')) 18 | 19 | 20 | @app.route("/", methods=["GET"]) 21 | def index(): 22 | data = { 23 | "cats": 5, 24 | "people": 8, 25 | "dogs": 4 26 | } 27 | 28 | return jsonify(data) 29 | 30 | 31 | @app.route("/send_me_data", methods=["POST"]) 32 | def send_me_data(): 33 | data = request.form 34 | for key, value in data.items(): 35 | print("received", key, "with value", value) 36 | 37 | return "Thanks" 38 | 39 | 40 | @app.route("/get_all_users") 41 | def get_all_users(): 42 | all_users = database.get_all_users() 43 | 44 | return jsonify(all_users) 45 | 46 | 47 | @app.route("/add_user", methods=["POST"]) 48 | def add_user(): 49 | data = request.form 50 | username = data["username"] 51 | real_name = data["real_name"] 52 | 53 | database.add_user(username, real_name) 54 | 55 | return jsonify( 56 | "User Created" 57 | ) 58 | 59 | 60 | @app.route("/user_exists", methods=["POST"]) 61 | def user_exists(): 62 | username = request.form.get("username") 63 | exists = database.user_exists(username) 64 | 65 | return jsonify({ 66 | "exists": exists 67 | }) 68 | 69 | 70 | @app.route("/create_conversation_db", methods=["POST"]) 71 | def create_conversation_db(): 72 | conversation_db_path = get_conversation_db_path_for_users(request.form) 73 | 74 | if not os.path.exists(conversation_db_path): 75 | with open(conversation_db_path, 'w') as f: 76 | pass 77 | conversation = Conversation(conversation_db_path) 78 | conversation.initialise_table() 79 | 80 | return jsonify({ 81 | "success": True, 82 | }) 83 | 84 | 85 | @app.route("/get_message_history", methods=["POST"]) 86 | def get_message_history(): 87 | conversation_db_path = get_conversation_db_path_for_users(request.form) 88 | conversation = Conversation(conversation_db_path) 89 | 90 | history = conversation.get_history() 91 | 92 | return jsonify({ 93 | "history": history 94 | }) 95 | 96 | 97 | @app.route("/send_message/", methods=["POST"]) 98 | def send_message(username): 99 | data = request.form 100 | author = data["author"] 101 | message = data["message"] 102 | date_sent = arrow.now().timestamp 103 | 104 | conversation_db_path = get_conversation_db_path_for_users({"user_one": author, "user_two": username}) 105 | conversation = Conversation(conversation_db_path) 106 | conversation.add_message(author, message, date_sent) 107 | 108 | return jsonify({ 109 | "success": True 110 | }) 111 | 112 | 113 | @app.route("/update_avatar/", methods=["POST"]) 114 | def update_avatar(username): 115 | img_b64 = request.form.get("img_b64") 116 | database.update_avatar(username, img_b64) 117 | 118 | return jsonify({ 119 | "success": True 120 | }) 121 | 122 | 123 | @app.route("/get_user_avatar/") 124 | def get_avatar(username): 125 | 126 | avatar_b64 = database.get_user_avatar(username)['avatar'] 127 | 128 | return jsonify({ 129 | "avatar": avatar_b64 130 | }) 131 | 132 | 133 | @app.route("/get_new_messages", methods=["POST"]) 134 | def get_new_messages(): 135 | data = request.form 136 | conversation_db_path = get_conversation_db_path_for_users(data) 137 | conversation_db = Conversation(conversation_db_path) 138 | 139 | timestamp = data["timestamp"] 140 | requester_username = data["user_one"] 141 | 142 | new_messages = conversation_db.get_new_messages(timestamp, requester_username) 143 | 144 | return jsonify({ 145 | "messages": new_messages 146 | }) 147 | 148 | 149 | @app.route("/add_friend", methods=["POST"]) 150 | def add_friend(): 151 | data = request.form 152 | user_one = data['user_one'] 153 | user_two = data['user_two'] 154 | 155 | if database.user_exists(user_two) and database.user_exists(user_one): 156 | database.add_friend(user_one, user_two) 157 | success = True 158 | else: 159 | success = False 160 | 161 | return jsonify({ 162 | "success": success 163 | }) 164 | 165 | 166 | @app.route("/block_friend", methods=["POST"]) 167 | def block_friend(): 168 | data = request.form 169 | user_one = data['user_one'] 170 | user_two = data['user_two'] 171 | 172 | database.block_friend(user_one, user_two) 173 | 174 | return jsonify({ 175 | "success": True 176 | }) 177 | 178 | 179 | @app.route("/get_friends/") 180 | def get_friends(username): 181 | friends = database.get_friends(username) 182 | 183 | if len(friends): 184 | all_friends = database.get_users_by_usernames(friends) 185 | else: 186 | all_friends = [] 187 | 188 | return jsonify({ 189 | "friends": all_friends 190 | }) 191 | 192 | 193 | def get_conversation_db_path_for_users(data): 194 | user_one = data["user_one"] 195 | user_two = data["user_two"] 196 | 197 | users_in_order = sorted([user_one, user_two]) 198 | users_in_order = "_".join(users_in_order) 199 | 200 | conversation_db = users_in_order + ".db" 201 | conversation_db_path = os.path.join(conversations_dir, conversation_db) 202 | 203 | return conversation_db_path 204 | 205 | if __name__ == '__main__': 206 | app.run(debug=True) 207 | -------------------------------------------------------------------------------- /Ch10/smilies/mikulka-smile-cool.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dvlv/Tkinter-Gui-Programming-By-Example-Packt/6ddac5df32297b9ebaa19d1f08212337724dfed7/Ch10/smilies/mikulka-smile-cool.png -------------------------------------------------------------------------------- /Ch10/smilies/mikulka-smile-grin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dvlv/Tkinter-Gui-Programming-By-Example-Packt/6ddac5df32297b9ebaa19d1f08212337724dfed7/Ch10/smilies/mikulka-smile-grin.png -------------------------------------------------------------------------------- /Ch10/smilies/mikulka-smile-license.txt: -------------------------------------------------------------------------------- 1 | 2008 mikulka 2 | 3 | The person or persons who have associated work with this document (the "Dedicator" or "Certifier") hereby either (a) certifies that, to the best of his knowledge, the work of authorship identified is in the public domain of the country from which the work is published, or (b) hereby dedicates whatever copyright the dedicators holds in the work of authorship identified below (the "Work") to the public domain. A certifier, moreover, dedicates any copyright interest he may have in the associated work, and for these purposes, is described as a "dedicator" below. 4 | 5 | A certifier has taken reasonable steps to verify the copyright status of this work. Certifier recognizes that his good faith efforts may not shield him from liability if in fact the work certified is not in the public domain. 6 | 7 | Dedicator makes this dedication for the benefit of the public at large and to the detriment of the Dedicator's heirs and successors. Dedicator intends this dedication to be an overt act of relinquishment in perpetuity of all present and future rights under copyright law, whether vested or contingent, in the Work. Dedicator understands that such relinquishment of all rights includes the relinquishment of all rights to enforce (by lawsuit or otherwise) those copyrights in the Work. 8 | 9 | Dedicator recognizes that, once placed in the public domain, the Work may be freely reproduced, distributed, transmitted, used, modified, built upon, or otherwise exploited by anyone for any purpose, commercial or non-commercial, and in any way, including by methods that have not yet been invented or conceived. 10 | -------------------------------------------------------------------------------- /Ch10/smilies/mikulka-smile-overview.png.ignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dvlv/Tkinter-Gui-Programming-By-Example-Packt/6ddac5df32297b9ebaa19d1f08212337724dfed7/Ch10/smilies/mikulka-smile-overview.png.ignore -------------------------------------------------------------------------------- /Ch10/smilies/mikulka-smile-razz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dvlv/Tkinter-Gui-Programming-By-Example-Packt/6ddac5df32297b9ebaa19d1f08212337724dfed7/Ch10/smilies/mikulka-smile-razz.png -------------------------------------------------------------------------------- /Ch10/smilies/mikulka-smile-sad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dvlv/Tkinter-Gui-Programming-By-Example-Packt/6ddac5df32297b9ebaa19d1f08212337724dfed7/Ch10/smilies/mikulka-smile-sad.png -------------------------------------------------------------------------------- /Ch10/smilies/mikulka-smile-smile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dvlv/Tkinter-Gui-Programming-By-Example-Packt/6ddac5df32297b9ebaa19d1f08212337724dfed7/Ch10/smilies/mikulka-smile-smile.png -------------------------------------------------------------------------------- /Ch10/smilies/mikulka-smile-source.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dvlv/Tkinter-Gui-Programming-By-Example-Packt/6ddac5df32297b9ebaa19d1f08212337724dfed7/Ch10/smilies/mikulka-smile-source.xcf -------------------------------------------------------------------------------- /Ch10/smilies/mikulka-smile-surprised.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dvlv/Tkinter-Gui-Programming-By-Example-Packt/6ddac5df32297b9ebaa19d1f08212337724dfed7/Ch10/smilies/mikulka-smile-surprised.png -------------------------------------------------------------------------------- /Ch10/smilies/mikulka-smile-wink.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dvlv/Tkinter-Gui-Programming-By-Example-Packt/6ddac5df32297b9ebaa19d1f08212337724dfed7/Ch10/smilies/mikulka-smile-wink.png -------------------------------------------------------------------------------- /Ch10/smilieselect.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import tkinter as tk 4 | import tkinter.ttk as ttk 5 | 6 | 7 | class SmilieSelect(tk.Toplevel): 8 | smilies_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), 'smilies/')) 9 | 10 | def __init__(self, master, **kwargs): 11 | super().__init__(**kwargs) 12 | self.master = master 13 | self.transient(master) 14 | self.position_window() 15 | 16 | smilie_files = [file for file in os.listdir(self.smilies_dir) if file.endswith(".png")] 17 | 18 | self.smilie_images = [] 19 | 20 | for file in smilie_files: 21 | full_path = os.path.join(self.smilies_dir, file) 22 | image = tk.PhotoImage(file=full_path) 23 | self.smilie_images.append(image) 24 | 25 | for index, file in enumerate(self.smilie_images): 26 | row, col = divmod(index, 3) 27 | button = ttk.Button(self, image=file, command=lambda s=file: self.insert_smilie(s)) 28 | button.grid(row=row, column=col, sticky='nsew') 29 | 30 | for i in range(3): 31 | tk.Grid.columnconfigure(self, i, weight=1) 32 | tk.Grid.rowconfigure(self, i, weight=1) 33 | 34 | def position_window(self): 35 | master_pos_x = self.master.winfo_x() 36 | master_pos_y = self.master.winfo_y() 37 | 38 | master_width = self.master.winfo_width() 39 | master_height = self.master.winfo_height() 40 | 41 | my_width = 100 42 | my_height = 100 43 | 44 | pos_x = (master_pos_x + (master_width // 2)) - (my_width // 2) 45 | pos_y = (master_pos_y + (master_height // 2)) - (my_height // 2) 46 | 47 | geometry = f"{my_width}x{my_height}+{pos_x}+{pos_y}" 48 | self.geometry(geometry) 49 | 50 | def insert_smilie(self, smilie): 51 | self.master.add_smilie(smilie) 52 | self.destroy() 53 | 54 | 55 | if __name__ == '__main__': 56 | w = tk.Tk() 57 | s = SmilieSelect(w) 58 | w.mainloop() -------------------------------------------------------------------------------- /Ch11/appimage/tkedit.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=tkedit 3 | Exec=tkedit 4 | Icon=tkedit.png 5 | Comment=A text editor with python syntax highlighting 6 | -------------------------------------------------------------------------------- /Ch11/tkedit-snap/snap/snapcraft.yaml: -------------------------------------------------------------------------------- 1 | name: tkedit # you probably want to 'snapcraft register ' 2 | version: '0.1' # just for humans, typically '1.2+git' or '1.3.2' 3 | summary: edit python files # 79 char long summary 4 | description: | 5 | This is a python text editor with syntax highlighting 6 | 7 | grade: devel # must be 'stable' to release into candidate/stable channels 8 | confinement: devmode # use 'strict' once you have the right plugs and slots 9 | 10 | apps: 11 | tkedit: 12 | command: tkedit 13 | 14 | parts: 15 | tkedit: 16 | source: /home/dvlv/tkedit 17 | plugin: python3 18 | python-version: python3 19 | stage-packages: [python3-tk] 20 | -------------------------------------------------------------------------------- /Ch11/tkedit/colourchooser.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | import tkinter.ttk as ttk 3 | from tkinter.colorchooser import askcolor 4 | 5 | 6 | class ColourChooser(tk.Toplevel): 7 | def __init__(self, master, **kwargs): 8 | super().__init__(**kwargs) 9 | self.master = master 10 | 11 | self.transient(self.master) 12 | self.geometry('400x300') 13 | self.title('Colour Scheme') 14 | self.configure(bg=self.master.background) 15 | 16 | self.chosen_background_colour = tk.StringVar() 17 | self.chosen_foreground_colour = tk.StringVar() 18 | self.chosen_text_background_colour = tk.StringVar() 19 | self.chosen_text_foreground_colour = tk.StringVar() 20 | 21 | self.chosen_background_colour.set(self.master.background) 22 | self.chosen_foreground_colour.set(self.master.foreground) 23 | self.chosen_text_background_colour.set(self.master.text_background) 24 | self.chosen_text_foreground_colour.set(self.master.text_foreground) 25 | 26 | window_frame = tk.Frame(self, bg=self.master.background) 27 | window_foreground_frame = tk.Frame(window_frame, bg=self.master.background) 28 | window_background_frame = tk.Frame(window_frame, bg=self.master.background) 29 | 30 | text_frame = tk.Frame(self, bg=self.master.background) 31 | text_foreground_frame = tk.Frame(text_frame, bg=self.master.background) 32 | text_background_frame = tk.Frame(text_frame, bg=self.master.background) 33 | 34 | self.all_frames = [window_frame, window_foreground_frame, window_background_frame, 35 | text_frame, text_foreground_frame, text_background_frame] 36 | 37 | window_label = ttk.Label(window_frame, text="Window:", anchor=tk.W, style="editor.TLabel") 38 | foreground_label = ttk.Label(window_foreground_frame, text="Foreground:", anchor=tk.E, style="editor.TLabel") 39 | background_label = ttk.Label(window_background_frame, text="Background:", anchor=tk.E, style="editor.TLabel") 40 | 41 | text_label = ttk.Label(text_frame, text="Editor:", anchor=tk.W, style="editor.TLabel") 42 | text_foreground_label = ttk.Label(text_foreground_frame, text="Foreground:", anchor=tk.E, style="editor.TLabel") 43 | text_background_label = ttk.Label(text_background_frame, text="Background:", anchor=tk.E, style="editor.TLabel") 44 | 45 | foreground_colour_chooser = ttk.Button(window_foreground_frame, text="Change Foreground Colour", width=26, style="editor.TButton", 46 | command=lambda sv=self.chosen_foreground_colour: self.set_colour(sv)) 47 | background_colour_chooser = ttk.Button(window_background_frame, text="Change Background Colour", width=26, 48 | style="editor.TButton", 49 | command=lambda sv=self.chosen_background_colour: self.set_colour(sv)) 50 | text_foreground_colour_chooser = ttk.Button(text_foreground_frame, text="Change Text Foreground Colour", 51 | width=26, style="editor.TButton", 52 | command=lambda sv=self.chosen_text_foreground_colour: self.set_colour(sv)) 53 | text_background_colour_chooser = ttk.Button(text_background_frame, text="Change Text Background Colour", 54 | width=26, style="editor.TButton", 55 | command=lambda sv=self.chosen_text_background_colour: self.set_colour(sv)) 56 | 57 | foreground_colour_preview = ttk.Label(window_foreground_frame, textvar=self.chosen_foreground_colour, 58 | style="editor.TLabel") 59 | background_colour_preview = ttk.Label(window_background_frame, textvar=self.chosen_background_colour, 60 | style="editor.TLabel") 61 | text_foreground_colour_preview = ttk.Label(text_foreground_frame, textvar=self.chosen_text_foreground_colour, 62 | style="editor.TLabel") 63 | text_background_colour_preview = ttk.Label(text_background_frame, textvar=self.chosen_text_background_colour, 64 | style="editor.TLabel") 65 | 66 | 67 | window_frame.pack(side=tk.TOP, fill=tk.X, expand=1) 68 | window_label.pack(side=tk.TOP, fill=tk.X) 69 | 70 | window_foreground_frame.pack(side=tk.TOP, fill=tk.X, expand=1) 71 | window_background_frame.pack(side=tk.TOP, fill=tk.X, expand=1) 72 | 73 | foreground_label.pack(side=tk.LEFT, padx=30, pady=10) 74 | foreground_colour_chooser.pack(side=tk.LEFT) 75 | foreground_colour_preview.pack(side=tk.LEFT, expand=1, fill=tk.X, padx=(15, 0)) 76 | 77 | background_label.pack(side=tk.LEFT, fill=tk.X, padx=(30, 27)) 78 | background_colour_chooser.pack(side=tk.LEFT) 79 | background_colour_preview.pack(side=tk.LEFT, expand=1, fill=tk.X, padx=(15, 0)) 80 | 81 | text_frame.pack(side=tk.TOP, fill=tk.X, expand=1) 82 | text_label.pack(side=tk.TOP, fill=tk.X) 83 | 84 | text_foreground_frame.pack(side=tk.TOP, fill=tk.X, expand=1) 85 | text_background_frame.pack(side=tk.TOP, fill=tk.X, expand=1) 86 | 87 | text_foreground_label.pack(side=tk.LEFT, padx=30, pady=10) 88 | text_foreground_colour_chooser.pack(side=tk.LEFT) 89 | text_foreground_colour_preview.pack(side=tk.LEFT, expand=1, fill=tk.X, padx=(15, 0)) 90 | 91 | text_background_label.pack(side=tk.LEFT, fill=tk.X, padx=(30, 27)) 92 | text_background_colour_chooser.pack(side=tk.LEFT) 93 | text_background_colour_preview.pack(side=tk.LEFT, expand=1, fill=tk.X, padx=(15, 0)) 94 | 95 | save_button = ttk.Button(self, text="save", command=self.save, style="editor.TButton") 96 | save_button.pack(side=tk.BOTTOM, pady=(0, 20)) 97 | 98 | def set_colour(self, sv): 99 | choice = askcolor()[1] 100 | sv.set(choice) 101 | 102 | def save(self): 103 | yaml_file_contents = f"background: '{self.chosen_background_colour.get()}'\n" \ 104 | + f"foreground: '{self.chosen_foreground_colour.get()}'\n" \ 105 | + f"text_background: '{self.chosen_text_background_colour.get()}'\n" \ 106 | + f"text_foreground: '{self.chosen_text_foreground_colour.get()}'\n" 107 | 108 | with open(self.master.default_scheme_path, "w") as yaml_file: 109 | yaml_file.write(yaml_file_contents) 110 | 111 | self.master.apply_colour_scheme(self.chosen_foreground_colour.get(), self.chosen_background_colour.get(), 112 | self.chosen_text_foreground_colour.get(), self.chosen_text_background_colour.get()) 113 | for frame in self.all_frames: 114 | frame.configure(bg=self.chosen_background_colour.get()) 115 | 116 | self.configure(bg=self.chosen_background_colour.get()) 117 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /Ch11/tkedit/findwindow.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | import tkinter.ttk as ttk 3 | 4 | 5 | class FindWindow(tk.Toplevel): 6 | def __init__(self, master, **kwargs): 7 | super().__init__(**kwargs) 8 | 9 | self.master = master 10 | 11 | self.geometry('350x100') 12 | self.title('Find and Replace') 13 | self.transient(self.master) 14 | self.configure(bg=self.master.master.background) 15 | 16 | self.text_to_find = tk.StringVar() 17 | self.text_to_replace_with = tk.StringVar() 18 | 19 | top_frame = tk.Frame(self, bg=self.master.master.background) 20 | middle_frame = tk.Frame(self, bg=self.master.master.background) 21 | bottom_frame = tk.Frame(self, bg=self.master.master.background) 22 | 23 | find_entry_label = ttk.Label(top_frame, text="Find: ", style="editor.TLabel") 24 | self.find_entry = ttk.Entry(top_frame, textvar=self.text_to_find) 25 | 26 | replace_entry_label = ttk.Label(middle_frame, text="Replace: ", style="editor.TLabel") 27 | self.replace_entry = ttk.Entry(middle_frame, textvar=self.text_to_replace_with) 28 | 29 | self.find_button = ttk.Button(bottom_frame, text="Find", command=self.on_find, style="editor.TButton") 30 | self.replace_button = ttk.Button(bottom_frame, text="Replace", command=self.on_replace, style="editor.TButton") 31 | self.cancel_button = ttk.Button(bottom_frame, text="Cancel", command=self.on_cancel, style="editor.TButton") 32 | 33 | find_entry_label.pack(side=tk.LEFT, padx=(20, 0)) 34 | self.find_entry.pack(side=tk.LEFT, fill=tk.X, expand=1) 35 | 36 | replace_entry_label.pack(side=tk.LEFT) 37 | self.replace_entry.pack(side=tk.LEFT, fill=tk.X, expand=1) 38 | 39 | self.find_button.pack(side=tk.LEFT, padx=(85, 0)) 40 | self.replace_button.pack(side=tk.LEFT, padx=(20, 20)) 41 | self.cancel_button.pack(side=tk.RIGHT, padx=(0, 30)) 42 | 43 | top_frame.pack(side=tk.TOP, expand=1, fill=tk.X, padx=30) 44 | middle_frame.pack(side=tk.TOP, expand=1, fill=tk.X, padx=30) 45 | bottom_frame.pack(side=tk.TOP, expand=1, fill=tk.X) 46 | 47 | self.find_entry.focus_force() 48 | 49 | self.protocol("WM_DELETE_WINDOW", self.on_cancel) 50 | 51 | def on_find(self): 52 | self.master.find(self.text_to_find.get()) 53 | 54 | def on_replace(self): 55 | self.master.replace_text(self.text_to_find.get(), self.text_to_replace_with.get()) 56 | 57 | def on_cancel(self): 58 | self.master.cancel_find() 59 | self.destroy() 60 | 61 | 62 | if __name__ == '__main__': 63 | mw = tk.Tk() 64 | fw = FindWindow(mw) 65 | mw.mainloop() 66 | -------------------------------------------------------------------------------- /Ch11/tkedit/fontchooser.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | import tkinter.ttk as ttk 3 | from tkinter.font import families 4 | 5 | 6 | class FontChooser(tk.Toplevel): 7 | def __init__(self, master, **kwargs): 8 | super().__init__(**kwargs) 9 | self.master = master 10 | 11 | self.transient(self.master) 12 | self.geometry('500x250') 13 | self.title('Choose font and size') 14 | 15 | self.configure(bg=self.master.background) 16 | 17 | self.font_list = tk.Listbox(self, exportselection=False) 18 | 19 | self.available_fonts = sorted(families()) 20 | 21 | for family in self.available_fonts: 22 | self.font_list.insert(tk.END, family) 23 | 24 | current_selection_index = self.available_fonts.index(self.master.font_family) 25 | if current_selection_index: 26 | self.font_list.select_set(current_selection_index) 27 | self.font_list.see(current_selection_index) 28 | 29 | self.size_input = tk.Spinbox(self, from_=0, to=99, value=self.master.font_size) 30 | 31 | self.save_button = ttk.Button(self, text="Save", style="editor.TButton", command=self.save) 32 | 33 | self.save_button.pack(side=tk.BOTTOM, fill=tk.X, expand=1, padx=40) 34 | self.font_list.pack(side=tk.LEFT, fill=tk.Y, expand=1) 35 | self.size_input.pack(side=tk.BOTTOM, fill=tk.X, expand=1) 36 | 37 | def save(self): 38 | font_family = self.font_list.get(self.font_list.curselection()[0]) 39 | yaml_file_contents = f"family: {font_family}\n" \ 40 | + f"size: {self.size_input.get()}" 41 | 42 | with open(self.master.font_scheme_path, 'w') as file: 43 | file.write(yaml_file_contents) 44 | 45 | self.master.update_font() 46 | 47 | -------------------------------------------------------------------------------- /Ch11/tkedit/highlighter.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | 3 | import yaml 4 | 5 | 6 | class Highlighter: 7 | def __init__(self, text_widget, syntax_file): 8 | self.text_widget = text_widget 9 | self.syntax_file = syntax_file 10 | self.categories = None 11 | self.numbers_colour = "blue" 12 | self.strings_colour = "red" 13 | 14 | self.disallowed_previous_chars = ["_", "-", "."] 15 | 16 | self.parse_syntax_file() 17 | 18 | self.text_widget.bind('', self.on_key_release) 19 | 20 | def on_key_release(self, event=None): 21 | self.highlight() 22 | 23 | def parse_syntax_file(self): 24 | with open(self.syntax_file, 'r') as stream: 25 | try: 26 | config = yaml.load(stream) 27 | except yaml.YAMLError as error: 28 | print(error) 29 | return 30 | 31 | self.categories = config['categories'] 32 | self.numbers_colour = config['numbers']['colour'] 33 | self.strings_colour = config['strings']['colour'] 34 | 35 | self.configure_tags() 36 | 37 | def configure_tags(self): 38 | for category in self.categories.keys(): 39 | colour = self.categories[category]['colour'] 40 | self.text_widget.tag_configure(category, foreground=colour) 41 | 42 | self.text_widget.tag_configure("number", foreground=self.numbers_colour) 43 | self.text_widget.tag_configure("string", foreground=self.strings_colour) 44 | 45 | def highlight(self, event=None): 46 | length = tk.IntVar() 47 | for category in self.categories: 48 | matches = self.categories[category]['matches'] 49 | for keyword in matches: 50 | start = 1.0 51 | keyword = keyword + "[^A-Za-z_-]" 52 | idx = self.text_widget.search(keyword, start, stopindex=tk.END, count=length, regexp=1) 53 | while idx: 54 | char_match_found = int(str(idx).split('.')[1]) 55 | line_match_found = int(str(idx).split('.')[0]) 56 | if char_match_found > 0: 57 | previous_char_index = str(line_match_found) + '.' + str(char_match_found - 1) 58 | previous_char = self.text_widget.get(previous_char_index, previous_char_index + "+1c") 59 | 60 | if previous_char.isalnum() or previous_char in self.disallowed_previous_chars: 61 | end = f"{idx}+{length.get() - 1}c" 62 | start = end 63 | idx = self.text_widget.search(keyword, start, stopindex=tk.END, regexp=1) 64 | else: 65 | end = f"{idx}+{length.get() - 1}c" 66 | self.text_widget.tag_add(category, idx, end) 67 | 68 | start = end 69 | idx = self.text_widget.search(keyword, start, stopindex=tk.END, regexp=1) 70 | else: 71 | end = f"{idx}+{length.get() - 1}c" 72 | self.text_widget.tag_add(category, idx, end) 73 | 74 | start = end 75 | idx = self.text_widget.search(keyword, start, stopindex=tk.END, regexp=1) 76 | 77 | self.highlight_regex(r"(\d)+[.]?(\d)*", "number") 78 | self.highlight_regex(r"[\'][^\']*[\']", "string") 79 | self.highlight_regex(r"[\"][^\']*[\"]", "string") 80 | 81 | def highlight_regex(self, regex, tag): 82 | length = tk.IntVar() 83 | start = 1.0 84 | idx = self.text_widget.search(regex, start, stopindex=tk.END, regexp=1, count=length) 85 | while idx: 86 | end = f"{idx}+{length.get()}c" 87 | self.text_widget.tag_add(tag, idx, end) 88 | 89 | start = end 90 | idx = self.text_widget.search(regex, start, stopindex=tk.END, regexp=1, count=length) 91 | 92 | def force_highlight(self): 93 | self.highlight() 94 | 95 | def clear_highlight(self): 96 | for category in self.categories: 97 | self.text_widget.tag_remove(category, 1.0, tk.END) 98 | 99 | 100 | if __name__ == '__main__': 101 | w = tk.Tk() 102 | h = Highlighter(tk.Text(w), 'languages/python.yaml') 103 | w.mainloop() 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /Ch11/tkedit/languages/python.yaml: -------------------------------------------------------------------------------- 1 | categories: 2 | keywords: 3 | colour: orange 4 | matches: [for, def, while, from, import, as, with, self] 5 | 6 | variables: 7 | colour: red4 8 | matches: ['True', 'False', None] 9 | 10 | conditionals: 11 | colour: green 12 | matches: [try, except, if, else, elif] 13 | 14 | functions: 15 | colour: blue4 16 | matches: [int, str, dict, list, set, float] 17 | 18 | numbers: 19 | colour: purple 20 | 21 | strings: 22 | colour: '#e1218b' 23 | 24 | -------------------------------------------------------------------------------- /Ch11/tkedit/languages/sql.yaml: -------------------------------------------------------------------------------- 1 | categories: 2 | keywords: 3 | colour: orange 4 | matches: [select, where, and, from, order, by, group] 5 | 6 | dangerous: 7 | colour: red4 8 | matches: [set, update, drop] 9 | 10 | 11 | numbers: 12 | colour: purple 13 | 14 | strings: 15 | colour: red -------------------------------------------------------------------------------- /Ch11/tkedit/linenumbers.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | 3 | 4 | class LineNumbers(tk.Text): 5 | def __init__(self, master, text_widget, **kwargs): 6 | super().__init__(master, **kwargs) 7 | 8 | self.text_widget = text_widget 9 | self.text_widget.bind('', self.on_key_press) 10 | 11 | self.insert(1.0, '1') 12 | self.configure(state='disabled') 13 | 14 | def on_key_press(self, event=None): 15 | final_index = str(self.text_widget.index(tk.END)) 16 | num_of_lines = final_index.split('.')[0] 17 | line_numbers_string = "\n".join(str(no + 1) for no in range(int(num_of_lines))) 18 | width = len(str(num_of_lines)) 19 | 20 | self.configure(state='normal', width=width) 21 | self.delete(1.0, tk.END) 22 | self.insert(1.0, line_numbers_string) 23 | self.configure(state='disabled') 24 | 25 | def force_update(self): 26 | self.on_key_press() 27 | 28 | -------------------------------------------------------------------------------- /Ch11/tkedit/requirements.txt: -------------------------------------------------------------------------------- 1 | pyyaml 2 | -------------------------------------------------------------------------------- /Ch11/tkedit/schemes/default.yaml: -------------------------------------------------------------------------------- 1 | background: '#dddddd' 2 | foreground: '#000000' 3 | text_background: '#ffffff' 4 | text_foreground: '#000000' 5 | -------------------------------------------------------------------------------- /Ch11/tkedit/schemes/font.yaml: -------------------------------------------------------------------------------- 1 | family: Ubuntu Mono 2 | size: 15 -------------------------------------------------------------------------------- /Ch11/tkedit/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from distutils.core import setup 4 | 5 | setup( 6 | name='tkedit', 7 | version='0.1', 8 | description='This is a python text editor with syntax highlighting', 9 | author='David Love', 10 | py_modules= [ 11 | "colourchooser", 12 | "findwindow", 13 | "fontchooser", 14 | "highlighter", 15 | "linenumbers", 16 | "textarea", 17 | "texteditor", 18 | ], 19 | install_requires = [ 20 | "PyYAML", 21 | ], 22 | entry_points = { 23 | "console_scripts": ["tkedit = texteditor:main"] 24 | } 25 | ) 26 | 27 | -------------------------------------------------------------------------------- /Ch11/tkedit/setup_cx_freeze.py: -------------------------------------------------------------------------------- 1 | from cx_Freeze import setup, Executable 2 | 3 | import sys 4 | base = 'Win32GUI' if sys.platform=='win32' else None 5 | 6 | import os 7 | import os.path 8 | PYTHON_INSTALL_DIR = os.path.dirname(os.path.dirname(os.__file__)) 9 | os.environ['TCL_LIBRARY'] = os.path.join(PYTHON_INSTALL_DIR, 'tcl', 'tcl8.6') 10 | os.environ['TK_LIBRARY'] = os.path.join(PYTHON_INSTALL_DIR, 'tcl', 'tk8.6') 11 | 12 | options = { 13 | 'build_exe': { 14 | 'include_files':[ 15 | os.path.join(PYTHON_INSTALL_DIR, 'DLLs', 'tk86t.dll'), 16 | os.path.join(PYTHON_INSTALL_DIR, 'DLLs', 'tcl86t.dll'), 17 | ], 18 | }, 19 | } 20 | 21 | executables = [ 22 | Executable('tkedit.py', base=base) 23 | ] 24 | 25 | setup(name='tkedit', 26 | version = '1.0', 27 | description = 'A tkinter text editor', 28 | options = options, 29 | executables = executables) 30 | -------------------------------------------------------------------------------- /Ch11/tkedit/textarea.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | import tkinter.messagebox as msg 3 | 4 | 5 | class TextArea(tk.Text): 6 | def __init__(self, master, **kwargs): 7 | super().__init__(**kwargs) 8 | 9 | self.master = master 10 | 11 | self.config(wrap=tk.WORD) # CHAR NONE 12 | 13 | self.tag_configure('find_match', background="yellow") 14 | self.find_match_index = None 15 | self.find_search_starting_index = 1.0 16 | 17 | self.bind_events() 18 | 19 | def bind_events(self): 20 | self.bind('', self.select_all) 21 | self.bind('', self.copy) 22 | self.bind('', self.paste) 23 | self.bind('', self.cut) 24 | self.bind('', self.redo) 25 | self.bind('', self.undo) 26 | 27 | def cut(self, event=None): 28 | self.event_generate("<>") 29 | 30 | return "break" 31 | 32 | def copy(self, event=None): 33 | self.event_generate("<>") 34 | 35 | return "break" 36 | 37 | def paste(self, event=None): 38 | self.event_generate("<>") 39 | 40 | return "break" 41 | 42 | def undo(self, event=None): 43 | self.event_generate("<>") 44 | 45 | return "break" 46 | 47 | def redo(self, event=None): 48 | self.event_generate("<>") 49 | 50 | return "break" 51 | 52 | def select_all(self, event=None): 53 | self.tag_add("sel", 1.0, tk.END) 54 | 55 | return "break" 56 | 57 | def find(self, text_to_find): 58 | length = tk.IntVar() 59 | idx = self.search(text_to_find, self.find_search_starting_index, stopindex=tk.END, count=length) 60 | 61 | if idx: 62 | self.tag_remove('find_match', 1.0, tk.END) 63 | 64 | end = f'{idx}+{length.get()}c' 65 | self.tag_add('find_match', idx, end) 66 | self.see(idx) 67 | 68 | self.find_search_starting_index = end 69 | self.find_match_index = idx 70 | else: 71 | if self.find_match_index != 1.0: 72 | if msg.askyesno("No more results", "No further matches. Repeat from the beginning?"): 73 | self.find_search_starting_index = 1.0 74 | self.find_match_index = None 75 | return self.find(text_to_find) 76 | else: 77 | msg.showinfo("No Matches", "No matching text found") 78 | 79 | def replace_text(self, target, replacement): 80 | if self.find_match_index: 81 | current_found_index_line = str(self.find_match_index).split('.')[0] 82 | 83 | end = f"{self.find_match_index}+{len(target)}c" 84 | self.replace(self.find_match_index, end, replacement) 85 | 86 | self.find_search_starting_index = current_found_index_line + '.0' 87 | 88 | def cancel_find(self): 89 | self.find_search_starting_index = 1.0 90 | self.find_match_index = None 91 | self.tag_remove('find_match', 1.0, tk.END) 92 | 93 | def display_file_contents(self, filepath): 94 | with open(filepath, 'r') as file: 95 | self.delete(1.0, tk.END) 96 | self.insert(1.0, file.read()) 97 | 98 | -------------------------------------------------------------------------------- /Ch11/tkedit/tkedit.py: -------------------------------------------------------------------------------- 1 | from texteditor import main 2 | 3 | main() 4 | -------------------------------------------------------------------------------- /Ch11/widgets/label_frame.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | import tkinter.ttk as ttk 3 | 4 | win = tk.Tk() 5 | name_frame = ttk.Frame(win) 6 | address_frame = ttk.Frame(win) 7 | 8 | name_label_frame = ttk.LabelFrame(name_frame, text="Name") 9 | address_label = ttk.Label(win, text="Address") 10 | address_label_frame = ttk.LabelFrame(address_frame, labelwidget=address_label) 11 | 12 | first_name = ttk.Entry(name_label_frame) 13 | last_name = ttk.Entry(name_label_frame) 14 | 15 | first_name.pack(side=tk.TOP) 16 | last_name.pack(side=tk.BOTTOM) 17 | 18 | name_label_frame.pack(fill=tk.BOTH, expand=1) 19 | name_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=1) 20 | 21 | address_1 = ttk.Entry(address_label_frame) 22 | address_2 = ttk.Entry(address_label_frame) 23 | address_3 = ttk.Entry(address_label_frame) 24 | 25 | address_1.pack(side=tk.TOP) 26 | address_2.pack(side=tk.TOP) 27 | address_3.pack(side=tk.TOP) 28 | 29 | address_label_frame.pack(fill=tk.BOTH, expand=1) 30 | address_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=1) 31 | 32 | win.mainloop() -------------------------------------------------------------------------------- /Ch2/Ch2-class-2.py: -------------------------------------------------------------------------------- 1 | class Dog: 2 | def __init__(self, name): 3 | self.name = name 4 | 5 | def speak(self): 6 | print("Woof! My name is", self.name) 7 | 8 | 9 | class Greyhound(Dog): 10 | def __init__(self, name): 11 | super().__init__(name) 12 | 13 | def speak(self): 14 | print("Zoom! My name is", self.name) 15 | 16 | def race(self, opponent): 17 | print(self.name, "is running faster than", opponent.name) 18 | 19 | class JackRussell(Dog): 20 | def __init__(self, name, color): 21 | super().__init__(name) 22 | 23 | self.color = color 24 | 25 | def get_color(self): 26 | print(self.name, "is", self.color) 27 | 28 | 29 | greyhound = Greyhound("Tessa") 30 | jack_russell = JackRussell("Jack", "brown") 31 | dog = Dog("Boris") 32 | 33 | greyhound.speak() 34 | jack_russell.speak() 35 | dog.speak() 36 | 37 | greyhound.race(jack_russell) 38 | greyhound.race(dog) 39 | 40 | jack_russell.get_color() 41 | 42 | -------------------------------------------------------------------------------- /Ch2/Ch2-class.py: -------------------------------------------------------------------------------- 1 | class Dog: 2 | def __init__(self, name): 3 | self.name = name 4 | 5 | def speak(self): 6 | print("Woof! My name is", self.name) 7 | 8 | 9 | dog_one = Dog('Rover') 10 | dog_two = Dog('Rex') 11 | 12 | dog_one.speak() 13 | dog_two.speak() 14 | -------------------------------------------------------------------------------- /Ch2/ch2.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | 4 | class Card: 5 | def __init__(self, suit, value): 6 | self.suit = suit 7 | self.value = value 8 | 9 | def __repr__(self): 10 | return " of ".join((self.value, self.suit)) 11 | 12 | 13 | class Deck: 14 | def __init__(self): 15 | self.cards = [Card(s, v) for s in ["Spades", "Clubs", "Hearts", "Diamonds"] for v in 16 | ["A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"]] 17 | 18 | def shuffle(self): 19 | if len(self.cards) > 1: 20 | random.shuffle(self.cards) 21 | 22 | def deal(self): 23 | if len(self.cards) > 1: 24 | return self.cards.pop(0) 25 | 26 | 27 | class Hand: 28 | def __init__(self, dealer=False): 29 | self.dealer = dealer 30 | self.cards = [] 31 | self.value = 0 32 | 33 | def add_card(self, card): 34 | self.cards.append(card) 35 | 36 | def calculate_value(self): 37 | self.value = 0 38 | has_ace = False 39 | for card in self.cards: 40 | if card.value.isnumeric(): 41 | self.value += int(card.value) 42 | else: 43 | if card.value == "A": 44 | has_ace = True 45 | self.value += 11 46 | else: 47 | self.value += 10 48 | 49 | if has_ace and self.value > 21: 50 | self.value -= 10 51 | 52 | def get_value(self): 53 | self.calculate_value() 54 | return self.value 55 | 56 | def display(self): 57 | if self.dealer: 58 | print("hidden") 59 | print(self.cards[1]) 60 | else: 61 | for card in self.cards: 62 | print(card) 63 | print("Value:", self.get_value()) 64 | 65 | 66 | class Game: 67 | def __init__(self): 68 | playing = True 69 | 70 | while playing: 71 | self.deck = Deck() 72 | self.deck.shuffle() 73 | 74 | self.player_hand = Hand() 75 | self.dealer_hand = Hand(dealer=True) 76 | 77 | for i in range(2): 78 | self.player_hand.add_card(self.deck.deal()) 79 | self.dealer_hand.add_card(self.deck.deal()) 80 | 81 | print("Your hand is:") 82 | self.player_hand.display() 83 | print() 84 | print("Dealer's hand is:") 85 | self.dealer_hand.display() 86 | 87 | game_over = False 88 | 89 | while not game_over: 90 | player_has_blackjack, dealer_has_blackjack = self.check_for_blackjack() 91 | if player_has_blackjack or dealer_has_blackjack: 92 | game_over = True 93 | self.show_blackjack_results(player_has_blackjack, dealer_has_blackjack) 94 | continue 95 | 96 | choice = input("Please choose [Hit / Stick] ").lower() 97 | while choice not in ["h", "s", "hit", "stick"]: 98 | choice = input("Please enter 'hit' or 'stick' (or H/S) ").lower() 99 | if choice in ['hit', 'h']: 100 | self.player_hand.add_card(self.deck.deal()) 101 | self.player_hand.display() 102 | if self.player_is_over(): 103 | print("You have lost!") 104 | game_over = True 105 | else: 106 | player_hand_value = self.player_hand.get_value() 107 | dealer_hand_value = self.dealer_hand.get_value() 108 | 109 | print("Final Results") 110 | print("Your hand:", player_hand_value) 111 | print("Dealer's hand:", dealer_hand_value) 112 | 113 | if player_hand_value > dealer_hand_value: 114 | print("You Win!") 115 | elif player_hand_value == dealer_hand_value: 116 | print("Tie!") 117 | else: 118 | print("Dealer Wins!") 119 | game_over = True 120 | 121 | again = input("Play Again? [Y/N] ") 122 | while again.lower() not in ["y", "n"]: 123 | again = input("Please enter Y or N ") 124 | if again.lower() == "n": 125 | print("Thanks for playing!") 126 | playing = False 127 | else: 128 | game_over = False 129 | 130 | def player_is_over(self): 131 | return self.player_hand.get_value() > 21 132 | 133 | def check_for_blackjack(self): 134 | player = False 135 | dealer = False 136 | if self.player_hand.get_value() == 21: 137 | player = True 138 | if self.dealer_hand.get_value() == 21: 139 | dealer = True 140 | 141 | return player, dealer 142 | 143 | def show_blackjack_results(self, player_has_blackjack, dealer_has_blackjack): 144 | if player_has_blackjack and dealer_has_blackjack: 145 | print("Both players have blackjack! Draw!") 146 | 147 | elif player_has_blackjack: 148 | print("You have blackjack! You win!") 149 | 150 | elif dealer_has_blackjack: 151 | print("Dealer has blackjack! Dealer wins!") 152 | 153 | 154 | if __name__ == "__main__": 155 | g = Game() 156 | -------------------------------------------------------------------------------- /Ch3/ch3-canvas.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | 3 | window = tk.Tk() 4 | 5 | canvas = tk.Canvas(window, bg="white", width=300, height=300) 6 | 7 | canvas.pack() 8 | 9 | canvas.create_oval((0, 0, 300, 300), fill="yellow") 10 | 11 | canvas.create_arc((50, 100, 100, 150), extent=180, fill="black") 12 | 13 | canvas.create_arc((200, 100, 250, 150), extent=180, fill="black") 14 | 15 | canvas.create_line((50, 200, 110, 240), fill="red", width=5) 16 | canvas.create_line((110, 240, 190, 240), fill="red", width=5) 17 | canvas.create_line((190, 240, 250, 200), fill="red", width=5) 18 | 19 | window.mainloop() 20 | -------------------------------------------------------------------------------- /Ch3/ch3.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | import os 3 | import random 4 | 5 | assets_folder = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'assets/')) 6 | 7 | class Card: 8 | def __init__(self, suit, value): 9 | self.suit = suit 10 | self.value = value 11 | self.img = tk.PhotoImage(file=assets_folder + '/' + self.suit + self.value + ".png") 12 | 13 | def __repr__(self): 14 | return " of ".join((self.value, self.suit)) 15 | 16 | def get_file(self): 17 | return self.img 18 | 19 | @classmethod 20 | def get_back_file(cls): 21 | cls.back = tk.PhotoImage(file=assets_folder + "/back.png") 22 | 23 | return cls.back 24 | 25 | 26 | class Deck: 27 | def __init__(self): 28 | self.cards = [Card(s, v) for s in ["Spades", "Clubs", "Hearts", "Diamonds"] for v in 29 | ["A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"]] 30 | 31 | def shuffle(self): 32 | if len(self.cards) > 1: 33 | random.shuffle(self.cards) 34 | 35 | def deal(self): 36 | if len(self.cards) > 1: 37 | return self.cards.pop(0) 38 | 39 | 40 | class Hand: 41 | def __init__(self, dealer=False): 42 | self.dealer = dealer 43 | self.cards = [] 44 | self.value = 0 45 | 46 | def add_card(self, card): 47 | self.cards.append(card) 48 | 49 | def calculate_value(self): 50 | self.value = 0 51 | has_ace = False 52 | for card in self.cards: 53 | if card.value.isnumeric(): 54 | self.value += int(card.value) 55 | else: 56 | if card.value == "A": 57 | has_ace = True 58 | self.value += 11 59 | else: 60 | self.value += 10 61 | 62 | if has_ace and self.value > 21: 63 | self.value -= 10 64 | 65 | def get_value(self): 66 | self.calculate_value() 67 | return self.value 68 | 69 | 70 | class GameScreen(tk.Tk): 71 | def __init__(self): 72 | super().__init__() 73 | self.title("Blackjack") 74 | self.geometry("800x640") 75 | self.resizable(False, False) 76 | 77 | self.CARD_ORIGINAL_POSITION = 100 78 | self.CARD_WIDTH_OFFSET = 100 79 | 80 | self.PLAYER_CARD_HEIGHT = 300 81 | self.DEALER_CARD_HEIGHT = 100 82 | 83 | self.PLAYER_SCORE_TEXT_COORDS = (400, 450) 84 | self.WINNER_TEXT_COORDS = (400, 250) 85 | 86 | self.game_state = GameState() 87 | 88 | self.game_screen = tk.Canvas(self, bg="white", width=800, height=500) 89 | 90 | self.bottom_frame = tk.Frame(self, width=800, height=140, bg="red") 91 | self.bottom_frame.pack_propagate(0) 92 | 93 | self.hit_button = tk.Button(self.bottom_frame, text="Hit", width=25, command=self.hit) 94 | self.stick_button = tk.Button(self.bottom_frame, text="Stick", width=25, command=self.stick) 95 | 96 | self.play_again_button = tk.Button(self.bottom_frame, text="Play Again", width=25, command=self.play_again) 97 | self.quit_button = tk.Button(self.bottom_frame, text="Quit", width=25, command=self.destroy) 98 | 99 | self.hit_button.pack(side=tk.LEFT, padx=(100, 200)) 100 | self.stick_button.pack(side=tk.LEFT) 101 | 102 | self.bottom_frame.pack(side=tk.BOTTOM, fill=tk.X) 103 | self.game_screen.pack(side=tk.LEFT, anchor=tk.N) 104 | 105 | self.display_table() 106 | 107 | def display_table(self, hide_dealer=True, table_state=None): 108 | if not table_state: 109 | table_state = self.game_state.get_table_state() 110 | 111 | player_card_images = [card.get_file() for card in table_state['player_cards']] 112 | dealer_card_images = [card.get_file() for card in table_state['dealer_cards']] 113 | if hide_dealer and not table_state['blackjack']: 114 | dealer_card_images[0] = Card.get_back_file() 115 | 116 | self.game_screen.delete("all") 117 | self.tabletop_image = tk.PhotoImage(file=assets_folder + "/tabletop.png") 118 | 119 | self.game_screen.create_image((400, 250), image=self.tabletop_image) 120 | 121 | for card_number, card_image in enumerate(player_card_images): 122 | self.game_screen.create_image( 123 | (self.CARD_ORIGINAL_POSITION + self.CARD_WIDTH_OFFSET * card_number, self.PLAYER_CARD_HEIGHT), 124 | image=card_image 125 | ) 126 | 127 | for card_number, card_image in enumerate(dealer_card_images): 128 | self.game_screen.create_image( 129 | (self.CARD_ORIGINAL_POSITION + self.CARD_WIDTH_OFFSET * card_number, self.DEALER_CARD_HEIGHT), 130 | image=card_image 131 | ) 132 | 133 | self.game_screen.create_text(self.PLAYER_SCORE_TEXT_COORDS, text=self.game_state.player_score_as_text(), font=(None, 20)) 134 | 135 | if table_state['has_winner']: 136 | if table_state['has_winner'] == 'p': 137 | self.game_screen.create_text(self.WINNER_TEXT_COORDS, text="YOU WIN!", font=(None, 50)) 138 | elif table_state['has_winner'] == 'dp': 139 | self.game_screen.create_text(self.WINNER_TEXT_COORDS, text="TIE!", font=(None, 50)) 140 | else: 141 | self.game_screen.create_text(self.WINNER_TEXT_COORDS, text="DEALER WINS!", font=(None, 50)) 142 | 143 | self.show_play_again_options() 144 | 145 | def show_play_again_options(self): 146 | self.hit_button.pack_forget() 147 | self.stick_button.pack_forget() 148 | 149 | self.play_again_button.pack(side=tk.LEFT, padx=(100, 200)) 150 | self.quit_button.pack(side=tk.LEFT) 151 | 152 | def show_gameplay_buttons(self): 153 | self.play_again_button.pack_forget() 154 | self.quit_button.pack_forget() 155 | 156 | self.hit_button.pack(side=tk.LEFT, padx=(100, 200)) 157 | self.stick_button.pack(side=tk.LEFT) 158 | 159 | def play_again(self): 160 | self.show_gameplay_buttons() 161 | self.game_state = GameState() 162 | self.display_table() 163 | 164 | def hit(self): 165 | self.game_state.hit() 166 | self.display_table() 167 | 168 | def stick(self): 169 | table_state = self.game_state.calculate_final_state() 170 | self.display_table(False, table_state) 171 | 172 | class GameState: 173 | def __init__(self): 174 | self.deck = Deck() 175 | self.deck.shuffle() 176 | 177 | self.player_hand = Hand() 178 | self.dealer_hand = Hand(dealer=True) 179 | 180 | for i in range(2): 181 | self.player_hand.add_card(self.deck.deal()) 182 | self.dealer_hand.add_card(self.deck.deal()) 183 | 184 | self.has_winner = '' 185 | 186 | def hit(self): 187 | self.player_hand.add_card(self.deck.deal()) 188 | if self.someone_has_blackjack() == 'p': 189 | self.has_winner = 'p' 190 | if self.player_is_over(): 191 | self.has_winner = 'd' 192 | 193 | return self.has_winner 194 | 195 | def get_table_state(self): 196 | blackjack = False 197 | winner = self.has_winner 198 | if not winner: 199 | winner = self.someone_has_blackjack() 200 | if winner: 201 | blackjack = True 202 | table_state = { 203 | 'player_cards': self.player_hand.cards, 204 | 'dealer_cards': self.dealer_hand.cards, 205 | 'has_winner': winner, 206 | 'blackjack': blackjack, 207 | } 208 | 209 | return table_state 210 | 211 | def calculate_final_state(self): 212 | player_hand_value = self.player_hand.get_value() 213 | dealer_hand_value = self.dealer_hand.get_value() 214 | 215 | if player_hand_value == dealer_hand_value: 216 | winner = 'dp' 217 | elif player_hand_value > dealer_hand_value: 218 | winner = 'p' 219 | else: 220 | winner = 'd' 221 | 222 | table_state = { 223 | 'player_cards': self.player_hand.cards, 224 | 'dealer_cards': self.dealer_hand.cards, 225 | 'has_winner': winner, 226 | } 227 | 228 | return table_state 229 | 230 | def player_score_as_text(self): 231 | return "Score: " + str(self.player_hand.get_value()) 232 | 233 | def someone_has_blackjack(self): 234 | player = False 235 | dealer = False 236 | if self.player_hand.get_value() == 21: 237 | player = True 238 | if self.dealer_hand.get_value() == 21: 239 | dealer = True 240 | 241 | if player and dealer: 242 | return 'dp' 243 | elif player: 244 | return 'p' 245 | elif dealer: 246 | return 'd' 247 | 248 | return False 249 | 250 | def player_is_over(self): 251 | return self.player_hand.get_value() > 21 252 | 253 | 254 | if __name__ == "__main__": 255 | gs = GameScreen() 256 | gs.mainloop() 257 | -------------------------------------------------------------------------------- /Ch4/.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | env/ 3 | __pycache__ 4 | -------------------------------------------------------------------------------- /Ch4/casino/__init__.py: -------------------------------------------------------------------------------- 1 | from .card import Card, assets_folder 2 | from .deck import Deck 3 | from .hand import Hand 4 | from .player import Player, Dealer -------------------------------------------------------------------------------- /Ch4/casino/card.py: -------------------------------------------------------------------------------- 1 | import os 2 | from tkinter import PhotoImage 3 | 4 | assets_folder = os.path.abspath(os.path.join(os.path.dirname(__file__), '../..', 'assets/')) 5 | 6 | 7 | class Card: 8 | def __init__(self, suit, value): 9 | self.suit = suit 10 | self.value = value 11 | self.img = PhotoImage(file=assets_folder + '/' + self.suit + self.value + ".png") 12 | 13 | def __repr__(self): 14 | return " of ".join((self.value, self.suit)) 15 | 16 | def get_file(self): 17 | return self.img 18 | 19 | @classmethod 20 | def get_back_file(cls): 21 | cls.back = PhotoImage(file=assets_folder + "/back.png") 22 | 23 | return cls.back -------------------------------------------------------------------------------- /Ch4/casino/deck.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | from .card import Card 4 | 5 | 6 | class Deck: 7 | def __init__(self): 8 | self.cards = [Card(s, v) for s in ["Spades", "Clubs", "Hearts", "Diamonds"] for v in 9 | ["A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"]] 10 | 11 | def shuffle(self): 12 | if len(self.cards) > 1: 13 | random.shuffle(self.cards) 14 | 15 | def deal(self): 16 | if len(self.cards) > 1: 17 | return self.cards.pop(0) -------------------------------------------------------------------------------- /Ch4/casino/hand.py: -------------------------------------------------------------------------------- 1 | class Hand: 2 | def __init__(self): 3 | self.cards = [] 4 | self.value = 0 5 | 6 | def add_card(self, card): 7 | self.cards.append(card) 8 | 9 | def calculate_value(self): 10 | self.value = 0 11 | has_ace = False 12 | for card in self.cards: 13 | if card.value.isnumeric(): 14 | self.value += int(card.value) 15 | else: 16 | if card.value == "A": 17 | has_ace = True 18 | self.value += 11 19 | else: 20 | self.value += 10 21 | 22 | if has_ace and self.value > 21: 23 | self.value -= 10 24 | 25 | def get_value(self): 26 | self.calculate_value() 27 | return self.value -------------------------------------------------------------------------------- /Ch4/casino/player.py: -------------------------------------------------------------------------------- 1 | from .hand import Hand 2 | 3 | 4 | class Player: 5 | def __init__(self): 6 | self.money = 50 7 | self.hand = Hand() 8 | 9 | def add_winnings(self, winnings): 10 | self.money += winnings 11 | 12 | def can_place_bet(self, amount): 13 | return self.money >= amount 14 | 15 | def place_bet(self, amount): 16 | self.money -= amount 17 | 18 | def receive_card(self, card): 19 | self.hand.add_card(card) 20 | 21 | def empty_hand(self): 22 | self.hand.cards = [] 23 | 24 | @property 25 | def score(self): 26 | return self.hand.get_value() 27 | 28 | @property 29 | def is_over(self): 30 | return self.hand.get_value() > 21 31 | 32 | @property 33 | def has_blackjack(self): 34 | return self.hand.get_value() == 21 35 | 36 | @property 37 | def cards(self): 38 | return self.hand.cards 39 | 40 | 41 | class Dealer(Player): 42 | pass -------------------------------------------------------------------------------- /Ch4/casino_sounds/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pygame 4 | from casino import assets_folder 5 | 6 | 7 | class SoundBoard: 8 | def __init__(self): 9 | pygame.init() 10 | self.sound_folder = os.path.join(assets_folder, 'sounds') 11 | self.place_sound = self.load_sound('cardPlace1.wav') 12 | self.shuffle_sound = self.load_sound('cardShuffle.wav') 13 | self.chip_sound = self.load_sound('chipsStack6.wav') 14 | 15 | def load_sound(self, sound): 16 | file_location = os.path.join(self.sound_folder, sound) 17 | if os.path.isfile(file_location): 18 | return pygame.mixer.Sound(file_location) 19 | else: 20 | raise Exception('file ' + file_location + ' could not be found') -------------------------------------------------------------------------------- /Ch5/custom_events.py: -------------------------------------------------------------------------------- 1 | import random 2 | import tkinter as tk 3 | 4 | win = tk.Tk() 5 | sv = tk.StringVar() 6 | sv.set('You are walking around with an open wallet...') 7 | lab = tk.Label(win, textvar=sv) 8 | 9 | 10 | def user_found_money(event=None): 11 | amount = event.x 12 | sv.set('You found £' + str(amount)) 13 | 14 | 15 | def user_lost_money(event=None): 16 | amount = event.x 17 | sv.set('You dropped £' + str(amount)) 18 | 19 | 20 | def emit_custom_event(): 21 | choices = ['find', 'lose'] 22 | choice = random.choice(choices) 23 | 24 | if choice == 'find': 25 | win.event_generate("<>", x=random.randint(0, 50)) 26 | else: 27 | win.event_generate("<>", x=random.randint(0, 50)) 28 | 29 | win.after(2000, emit_custom_event) 30 | 31 | 32 | lab.pack(padx=50, pady=50) 33 | 34 | win.bind("<>", user_found_money) 35 | win.bind("<>", user_lost_money) 36 | 37 | win.after(2000, emit_custom_event) 38 | 39 | win.mainloop() 40 | 41 | 42 | -------------------------------------------------------------------------------- /Ch5/events.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | 3 | win = tk.Tk() 4 | 5 | can = tk.Canvas(win, width=300, height=300) 6 | strvar = tk.StringVar() 7 | lab = tk.Label(win, textvar=strvar) 8 | strvar.set("Press a key") 9 | 10 | 11 | def on_click(event=None): 12 | can.create_oval((event.x - 5, event.y - 5, event.x + 5, event.y + 5), fill="red") 13 | 14 | 15 | def on_key_down(event=None): 16 | strvar.set(event.keysym) 17 | 18 | 19 | def on_ctrl_d(event=None): 20 | top = tk.Toplevel(win) 21 | top.geometry("200x200") 22 | sv = tk.StringVar() 23 | sv.set("Hover the mouse over me") 24 | label = tk.Label(top, textvar=sv) 25 | label.bind("", lambda e, sv=sv: sv.set("Hello mouse!")) 26 | label.bind("", lambda e, sv=sv: sv.set("Goodbye mouse!")) 27 | label.pack(expand=1, fill=tk.BOTH) 28 | 29 | 30 | can.bind('', on_click) 31 | win.bind('', on_ctrl_d) 32 | win.bind('', on_key_down) 33 | win.bind('', lambda e, s=lab: lab.configure(font=(None, 18))) 34 | 35 | can.pack() 36 | lab.pack(side=tk.BOTTOM) 37 | 38 | win.mainloop() 39 | -------------------------------------------------------------------------------- /Ch5/findwindow.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | import tkinter.ttk as ttk 3 | 4 | 5 | class FindWindow(tk.Toplevel): 6 | def __init__(self, master, **kwargs): 7 | super().__init__(**kwargs ) 8 | 9 | self.geometry('350x100') 10 | self.title('Find and Replace') 11 | 12 | self.text_to_find = tk.StringVar() 13 | self.text_to_replace_with = tk.StringVar() 14 | 15 | top_frame = tk.Frame(self) 16 | middle_frame = tk.Frame(self) 17 | bottom_frame = tk.Frame(self) 18 | 19 | find_entry_label = tk.Label(top_frame, text="Find: ") 20 | self.find_entry = ttk.Entry(top_frame, textvar=self.text_to_find) 21 | 22 | replace_entry_label = tk.Label(middle_frame, text="Replace: ") 23 | self.replace_entry = ttk.Entry(middle_frame, textvar=self.text_to_replace_with) 24 | 25 | self.find_button = ttk.Button(bottom_frame, text="Find", command=self.on_find) 26 | self.replace = ttk.Button(bottom_frame, text="Replace", command=self.on_replace) 27 | self.cancel_button = ttk.Button(bottom_frame, text="Cancel", command=self.destroy) 28 | 29 | find_entry_label.pack(side=tk.LEFT, padx=(20, 0)) 30 | self.find_entry.pack(side=tk.LEFT, fill=tk.X, expand=1) 31 | 32 | replace_entry_label.pack(side=tk.LEFT) 33 | self.replace_entry.pack(side=tk.LEFT, fill=tk.X, expand=1) 34 | 35 | self.find_button.pack(side=tk.LEFT, padx=(85, 0)) 36 | self.cancel_button.pack(side=tk.RIGHT, padx=(0, 30)) 37 | 38 | top_frame.pack(side=tk.TOP, expand=1, fill=tk.X, padx=30) 39 | middle_frame.pack(side=tk.TOP, expand=1, fill=tk.X, padx=30) 40 | bottom_frame.pack(side=tk.TOP, expand=1, fill=tk.X) 41 | 42 | def on_find(self): 43 | self.master.find(self.text_to_find.get()) 44 | 45 | def on_replace(self): 46 | self.master.replace(self.text_to_find.get(), self.text_to_replace_with.get()) 47 | 48 | 49 | if __name__ == '__main__': 50 | mw = tk.Tk() 51 | fw = FindWindow(mw) 52 | mw.mainloop() 53 | -------------------------------------------------------------------------------- /Ch5/textarea.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | 3 | 4 | class TextArea(tk.Text): 5 | def __init__(self, master, **kwargs): 6 | super().__init__(**kwargs) 7 | 8 | self.master = master 9 | 10 | self.config(wrap=tk.WORD) # CHAR NONE 11 | 12 | self.bind_events() 13 | 14 | def bind_events(self): 15 | self.bind('', self.select_all) 16 | self.bind('', self.copy) 17 | self.bind('', self.paste) 18 | self.bind('', self.cut) 19 | self.bind('', self.redo) 20 | self.bind('', self.undo) 21 | 22 | def cut(self, event=None): 23 | self.event_generate("<>") 24 | 25 | def copy(self, event=None): 26 | self.event_generate("<>") 27 | 28 | def paste(self, event=None): 29 | self.event_generate("<>") 30 | 31 | def undo(self, event=None): 32 | self.event_generate("<>") 33 | 34 | return "break" 35 | 36 | def redo(self, event=None): 37 | self.event_generate("<>") 38 | 39 | return "break" 40 | 41 | def select_all(self, event=None): 42 | self.tag_add("sel", 1.0, tk.END) 43 | 44 | return "break" 45 | -------------------------------------------------------------------------------- /Ch5/texteditor.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | import tkinter.ttk as ttk 3 | 4 | from textarea import TextArea 5 | 6 | 7 | class MainWindow(tk.Tk): 8 | def __init__(self): 9 | super().__init__() 10 | 11 | self.text_area = TextArea(self, bg="white", fg="black", undo=True) 12 | 13 | self.scrollbar = ttk.Scrollbar(orient="vertical", command=self.scroll_text) 14 | self.text_area.configure(yscrollcommand=self.scrollbar.set) 15 | 16 | self.line_numbers = tk.Text(self, bg="grey", fg="white") 17 | first_100_numbers = [str(n+1) for n in range(100)] 18 | 19 | self.line_numbers.insert(1.0, "\n".join(first_100_numbers)) 20 | self.line_numbers.configure(state="disabled", width=3) 21 | 22 | self.scrollbar.pack(side=tk.RIGHT, fill=tk.Y) 23 | self.line_numbers.pack(side=tk.LEFT, fill=tk.Y) 24 | self.text_area.pack(side=tk.LEFT, fill=tk.BOTH, expand=1) 25 | 26 | self.bind_events() 27 | 28 | def bind_events(self): 29 | self.text_area.bind("", self.scroll_text) 30 | self.text_area.bind("", self.scroll_text) 31 | self.text_area.bind("", self.scroll_text) 32 | 33 | self.line_numbers.bind("", lambda e: "break") 34 | self.line_numbers.bind("", lambda e: "break") 35 | self.line_numbers.bind("", lambda e: "break") 36 | 37 | def scroll_text(self, *args): 38 | if len(args) > 1: 39 | self.text_area.yview_moveto(args[1]) 40 | self.line_numbers.yview_moveto(args[1]) 41 | else: 42 | event = args[0] 43 | if event.delta: 44 | move = -1 * (event.delta / 120) 45 | else: 46 | if event.num == 5: 47 | move = 1 48 | else: 49 | move = -1 50 | 51 | self.text_area.yview_scroll(int(move), "units") 52 | self.line_numbers.yview_scroll(int(move) * 3, "units") 53 | 54 | 55 | if __name__ == '__main__': 56 | mw = MainWindow() 57 | mw.mainloop() 58 | 59 | -------------------------------------------------------------------------------- /Ch5/tk_Style_example.py: -------------------------------------------------------------------------------- 1 | import itertools 2 | import tkinter as tk 3 | 4 | style_1 = {'fg': 'red', 'bg': 'black', 'activebackground': 'gold', 'activeforeground': 'dim gray'} 5 | style_2 = {'fg': 'yellow', 'bg': 'grey', 'activebackground': 'chocolate', 'activeforeground': 'blue4'} 6 | style_cycle = itertools.cycle([style_1, style_2]) 7 | 8 | 9 | def switch_style(): 10 | style = next(style_cycle) 11 | button_1.configure(**style) 12 | 13 | 14 | win = tk.Tk() 15 | 16 | button_1 = tk.Button(win, text="style switch", command=switch_style) 17 | button_1.pack(padx=50, pady=50) 18 | 19 | win.mainloop() 20 | -------------------------------------------------------------------------------- /Ch5/ttk_Example.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | import tkinter.ttk as ttk 3 | 4 | win = tk.Tk() 5 | button_tk = tk.Button(win, text="tk") 6 | button_ttk = ttk.Button(win, text="ttk") 7 | 8 | button_tk.pack(padx=10, pady=10) 9 | button_ttk.pack(padx=10, pady=10) 10 | 11 | win.mainloop() 12 | -------------------------------------------------------------------------------- /Ch5/ttk_inheritance.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | import tkinter.ttk as ttk 3 | 4 | win = tk.Tk() 5 | 6 | regular_button = ttk.Button(win, text="regular button") 7 | small_button = ttk.Button(win, text="small button", style="small.TButton") 8 | big_button = ttk.Button(win, text="big button", style="big.TButton") 9 | big_dangerous_button = ttk.Button(win, text="big dangerous", style="danger.big.TButton") 10 | small_dangerous_button = ttk.Button(win, text="small dangerous", style="danger.small.TButton") 11 | 12 | style = ttk.Style() 13 | 14 | style.configure('TButton', foreground="blue4") 15 | style.configure('small.TButton', font=(None, 7)) 16 | style.configure('big.TButton', font=(None, 20)) 17 | style.configure('danger.small.TButton', foreground="red") 18 | style.configure('danger.big.TButton', foreground="dark red") 19 | 20 | regular_button.pack(padx=50, pady=50) 21 | small_button.pack(padx=50, pady=50) 22 | big_button.pack(padx=50, pady=50) 23 | big_dangerous_button.pack(padx=50, pady=50) 24 | small_dangerous_button.pack(padx=50, pady=50) 25 | 26 | win.mainloop() 27 | -------------------------------------------------------------------------------- /Ch5/ttk_style_example.py: -------------------------------------------------------------------------------- 1 | import itertools 2 | import tkinter as tk 3 | import tkinter.ttk as ttk 4 | 5 | win = tk.Tk() 6 | style = ttk.Style() 7 | 8 | style_1 = {'foreground': 'red', 'background': 'black'} 9 | style_2 = {'foreground': 'yellow', 'background': 'grey'} 10 | 11 | mapping_1 = {'background': [('pressed', 'gold'), ('active', 'magenta')]} 12 | mapping_2 = {'background': [('pressed', 'chocolate'), ('active', 'blue4')]} 13 | 14 | style_cycle = itertools.cycle([style_1, style_2]) 15 | mapping_cycle = itertools.cycle([mapping_1, mapping_2]) 16 | 17 | def switch_style(): 18 | style_choice = next(style_cycle) 19 | mapping_choice = next(mapping_cycle) 20 | style.configure('TButton', **style_choice) 21 | style.map('TButton', **mapping_choice) 22 | # button.configure(style="TButton") 23 | 24 | button = ttk.Button(win, text="style switch", command=switch_style) 25 | button.pack(padx=50, pady=50) 26 | 27 | win.mainloop() 28 | -------------------------------------------------------------------------------- /Ch6/demo/indexing.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | 3 | win = tk.Tk() 4 | current_index = tk.StringVar() 5 | text = tk.Text(win, bg="white", fg="black") 6 | lab = tk.Label(win, textvar=current_index) 7 | 8 | 9 | def update_index(event=None): 10 | cursor_position = text.index(tk.INSERT) 11 | cursor_position_pieces = str(cursor_position).split('.') 12 | 13 | cursor_line = cursor_position_pieces[0] 14 | cursor_char = cursor_position_pieces[1] 15 | 16 | current_index.set('line: ' + cursor_line + ' char: ' + cursor_char + ' index: ' + str(cursor_position)) 17 | 18 | 19 | text.pack(side=tk.TOP, fill=tk.BOTH, expand=1) 20 | lab.pack(side=tk.BOTTOM, fill=tk.X, expand=1) 21 | 22 | text.bind('', update_index) 23 | 24 | win.mainloop() 25 | -------------------------------------------------------------------------------- /Ch6/demo/indexing2.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | 3 | win = tk.Tk() 4 | current_index = tk.StringVar() 5 | text = tk.Text(win, bg="white", fg="black") 6 | lab = tk.Label(win, textvar=current_index) 7 | 8 | 9 | def update_index(event=None): 10 | cursor_position = text.index(tk.INSERT) 11 | cursor_position_pieces = str(cursor_position).split('.') 12 | 13 | cursor_line = cursor_position_pieces[0] 14 | cursor_char = cursor_position_pieces[1] 15 | 16 | current_index.set('line: ' + cursor_line + ' char: ' + cursor_char + ' index: ' + str(cursor_position)) 17 | 18 | 19 | def highlight_line(event=None): 20 | start = str(text.index(tk.INSERT)) + " linestart" 21 | end = str(text.index(tk.INSERT)) + " lineend" 22 | text.tag_add("sel", start, end) 23 | 24 | return "break" 25 | 26 | 27 | def highlight_word(event=None): 28 | word_pos = str(text.index(tk.INSERT)) 29 | start = word_pos + " wordstart" 30 | end = word_pos + " wordend" 31 | text.tag_add("sel", start, end) 32 | 33 | return "break" 34 | 35 | 36 | def down_three_lines(event=None): 37 | current_cursor_index = str(text.index(tk.INSERT)) 38 | new_position = current_cursor_index + "+3l" 39 | text.mark_set(tk.INSERT, new_position) 40 | 41 | return "break" 42 | 43 | 44 | def back_four_chars(event=None): 45 | current_cursor_index = str(text.index(tk.INSERT)) 46 | new_position = current_cursor_index + "-4c" 47 | text.mark_set(tk.INSERT, new_position) 48 | 49 | return "break" 50 | 51 | 52 | text.pack(side=tk.TOP, fill=tk.BOTH, expand=1) 53 | lab.pack(side=tk.BOTTOM, fill=tk.X, expand=1) 54 | 55 | text.bind('', update_index) 56 | text.bind('', highlight_line) 57 | text.bind('', highlight_word) 58 | text.bind('', down_three_lines) 59 | text.bind('', back_four_chars) 60 | 61 | win.mainloop() 62 | -------------------------------------------------------------------------------- /Ch6/demo/searching.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | 3 | win = tk.Tk() 4 | current_index = tk.StringVar() 5 | text = tk.Text(win, bg="white", fg="black") 6 | lab = tk.Label(win, textvar=current_index) 7 | 8 | 9 | def update_index(event=None): 10 | cursor_position = text.index(tk.INSERT) 11 | cursor_position_pieces = str(cursor_position).split('.') 12 | 13 | cursor_line = cursor_position_pieces[0] 14 | cursor_char = cursor_position_pieces[1] 15 | 16 | current_index.set('line: ' + cursor_line + ' char: ' + cursor_char + ' index: ' + str(cursor_position)) 17 | 18 | 19 | def highlight_line(event=None): 20 | start = str(text.index(tk.INSERT)) + " linestart" 21 | end = str(text.index(tk.INSERT)) + " lineend" 22 | text.tag_add("sel", start, end) 23 | 24 | return "break" 25 | 26 | 27 | def highlight_word(event=None): 28 | word_pos = str(text.index(tk.INSERT)) 29 | start = word_pos + " wordstart" 30 | end = word_pos + " wordend" 31 | text.tag_add("sel", start, end) 32 | 33 | return "break" 34 | 35 | 36 | def down_three_lines(event=None): 37 | current_cursor_index = str(text.index(tk.INSERT)) 38 | new_position = current_cursor_index + "+3l" 39 | text.mark_set(tk.INSERT, new_position) 40 | 41 | return "break" 42 | 43 | 44 | def back_four_chars(event=None): 45 | current_cursor_index = str(text.index(tk.INSERT)) 46 | new_position = current_cursor_index + "-4c" 47 | text.mark_set(tk.INSERT, new_position) 48 | 49 | return "break" 50 | 51 | 52 | def tag_alternating(event=None): 53 | for i in range(0, 27, 2): 54 | index = '1.' + str(i) 55 | end = index + '+1c' 56 | text.tag_add('odd', index, end) 57 | 58 | text.tag_configure('odd', foreground='orange', underline=1) 59 | 60 | return "break" 61 | 62 | 63 | def raise_selected(event=None): 64 | text.tag_configure('raise', offset=5) 65 | selection = text.tag_ranges("sel") 66 | text.tag_add('raise', selection[0], selection[1]) 67 | 68 | return "break" 69 | 70 | 71 | def underline_selected(event=None): 72 | text.tag_configure('underline', underline=1) 73 | selection = text.tag_ranges("sel") 74 | text.tag_add('underline', selection[0], selection[1]) 75 | 76 | return "break" 77 | 78 | 79 | def tag_python(event=None): 80 | text.tag_configure('python', foreground="green") 81 | start = 1.0 82 | idx = text.search('python', start, stopindex=tk.END) 83 | while idx: 84 | tag_begin = idx 85 | tag_end = f"{idx}+6c" 86 | text.tag_add('python', tag_begin, tag_end) 87 | 88 | start = tag_end 89 | idx = text.search('python', start, stopindex=tk.END) 90 | 91 | return "break" 92 | 93 | 94 | text.pack(side=tk.TOP, fill=tk.BOTH, expand=1) 95 | lab.pack(side=tk.BOTTOM, fill=tk.X, expand=1) 96 | 97 | text.bind('', update_index) 98 | text.bind('', highlight_line) 99 | text.bind('', highlight_word) 100 | text.bind('', down_three_lines) 101 | text.bind('', back_four_chars) 102 | 103 | text.bind('', tag_python) 104 | text.bind('', raise_selected) 105 | text.bind('', underline_selected) 106 | 107 | win.mainloop() 108 | -------------------------------------------------------------------------------- /Ch6/demo/tagging.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | 3 | win = tk.Tk() 4 | current_index = tk.StringVar() 5 | text = tk.Text(win, bg="white", fg="black") 6 | lab = tk.Label(win, textvar=current_index) 7 | 8 | 9 | def update_index(event=None): 10 | cursor_position = text.index(tk.INSERT) 11 | cursor_position_pieces = str(cursor_position).split('.') 12 | 13 | cursor_line = cursor_position_pieces[0] 14 | cursor_char = cursor_position_pieces[1] 15 | 16 | current_index.set('line: ' + cursor_line + ' char: ' + cursor_char + ' index: ' + str(cursor_position)) 17 | 18 | 19 | def highlight_line(event=None): 20 | start = str(text.index(tk.INSERT)) + " linestart" 21 | end = str(text.index(tk.INSERT)) + " lineend" 22 | text.tag_add("sel", start, end) 23 | 24 | return "break" 25 | 26 | 27 | def highlight_word(event=None): 28 | word_pos = str(text.index(tk.INSERT)) 29 | start = word_pos + " wordstart" 30 | end = word_pos + " wordend" 31 | text.tag_add("sel", start, end) 32 | 33 | return "break" 34 | 35 | 36 | def down_three_lines(event=None): 37 | current_cursor_index = str(text.index(tk.INSERT)) 38 | new_position = current_cursor_index + "+3l" 39 | text.mark_set(tk.INSERT, new_position) 40 | 41 | return "break" 42 | 43 | 44 | def back_four_chars(event=None): 45 | current_cursor_index = str(text.index(tk.INSERT)) 46 | new_position = current_cursor_index + "-4c" 47 | text.mark_set(tk.INSERT, new_position) 48 | 49 | return "break" 50 | 51 | 52 | def tag_alternating(event=None): 53 | for i in range(0, 27, 2): 54 | index = '1.' + str(i) 55 | end = index + '+1c' 56 | text.tag_add('odd', index, end) 57 | 58 | text.tag_configure('odd', foreground='orange') 59 | 60 | return "break" 61 | 62 | 63 | def raise_selected(event=None): 64 | text.tag_configure('raise', offset=5) 65 | selection = text.tag_ranges("sel") 66 | text.tag_add('raise', selection[0], selection[1]) 67 | 68 | return "break" 69 | 70 | 71 | def underline_selected(event=None): 72 | text.tag_configure('underline', underline=1) 73 | selection = text.tag_ranges("sel") 74 | text.tag_add('underline', selection[0], selection[1]) 75 | 76 | return "break" 77 | 78 | 79 | text.pack(side=tk.TOP, fill=tk.BOTH, expand=1) 80 | lab.pack(side=tk.BOTTOM, fill=tk.X, expand=1) 81 | 82 | text.bind('', update_index) 83 | text.bind('', highlight_line) 84 | text.bind('', highlight_word) 85 | text.bind('', down_three_lines) 86 | text.bind('', back_four_chars) 87 | 88 | text.bind('', tag_alternating) 89 | text.bind('', raise_selected) 90 | text.bind('', underline_selected) 91 | 92 | win.mainloop() 93 | -------------------------------------------------------------------------------- /Ch6/findwindow.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | import tkinter.ttk as ttk 3 | 4 | 5 | class FindWindow(tk.Toplevel): 6 | def __init__(self, master, **kwargs): 7 | super().__init__(**kwargs) 8 | 9 | self.master = master 10 | 11 | self.geometry('350x100') 12 | self.title('Find and Replace') 13 | self.transient(self.master) 14 | 15 | self.text_to_find = tk.StringVar() 16 | self.text_to_replace_with = tk.StringVar() 17 | 18 | top_frame = tk.Frame(self) 19 | middle_frame = tk.Frame(self) 20 | bottom_frame = tk.Frame(self) 21 | 22 | find_entry_label = tk.Label(top_frame, text="Find: ") 23 | self.find_entry = ttk.Entry(top_frame, textvar=self.text_to_find) 24 | 25 | replace_entry_label = tk.Label(middle_frame, text="Replace: ") 26 | self.replace_entry = ttk.Entry(middle_frame, textvar=self.text_to_replace_with) 27 | 28 | self.find_button = ttk.Button(bottom_frame, text="Find", command=self.on_find) 29 | self.replace_button = ttk.Button(bottom_frame, text="Replace", command=self.on_replace) 30 | self.cancel_button = ttk.Button(bottom_frame, text="Cancel", command=self.on_cancel) 31 | 32 | find_entry_label.pack(side=tk.LEFT, padx=(20, 0)) 33 | self.find_entry.pack(side=tk.LEFT, fill=tk.X, expand=1) 34 | 35 | replace_entry_label.pack(side=tk.LEFT) 36 | self.replace_entry.pack(side=tk.LEFT, fill=tk.X, expand=1) 37 | 38 | self.find_button.pack(side=tk.LEFT, padx=(85, 0)) 39 | self.replace_button.pack(side=tk.LEFT, padx=(20, 20)) 40 | self.cancel_button.pack(side=tk.RIGHT, padx=(0, 30)) 41 | 42 | top_frame.pack(side=tk.TOP, expand=1, fill=tk.X, padx=30) 43 | middle_frame.pack(side=tk.TOP, expand=1, fill=tk.X, padx=30) 44 | bottom_frame.pack(side=tk.TOP, expand=1, fill=tk.X) 45 | 46 | self.find_entry.focus_force() 47 | 48 | self.protocol("WM_DELETE_WINDOW", self.on_cancel) 49 | 50 | def on_find(self): 51 | self.master.find(self.text_to_find.get()) 52 | 53 | def on_replace(self): 54 | self.master.replace_text(self.text_to_find.get(), self.text_to_replace_with.get()) 55 | 56 | def on_cancel(self): 57 | self.master.cancel_find() 58 | self.destroy() 59 | 60 | 61 | if __name__ == '__main__': 62 | mw = tk.Tk() 63 | fw = FindWindow(mw) 64 | mw.mainloop() 65 | -------------------------------------------------------------------------------- /Ch6/highlighter.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | 3 | import yaml 4 | 5 | 6 | class Highlighter: 7 | def __init__(self, text_widget, syntax_file): 8 | self.text_widget = text_widget 9 | self.syntax_file = syntax_file 10 | self.categories = None 11 | self.numbers_color = "blue" 12 | 13 | self.disallowed_previous_chars = ["_", "-", "."] 14 | 15 | self.parse_syntax_file() 16 | 17 | self.text_widget.bind('', self.on_key_release) 18 | 19 | def on_key_release(self, event=None): 20 | self.highlight() 21 | 22 | def parse_syntax_file(self): 23 | with open(self.syntax_file, 'r') as stream: 24 | try: 25 | config = yaml.load(stream) 26 | except yaml.YAMLError as error: 27 | print(error) 28 | return 29 | 30 | self.categories = config['categories'] 31 | self.numbers_color = config['numbers']['color'] 32 | self.strings_color = config['strings']['color'] 33 | 34 | self.configure_tags() 35 | 36 | def configure_tags(self): 37 | for category in self.categories.keys(): 38 | color = self.categories[category]['color'] 39 | self.text_widget.tag_configure(category, foreground=color) 40 | 41 | self.text_widget.tag_configure("number", foreground=self.numbers_color) 42 | self.text_widget.tag_configure("string", foreground=self.strings_color) 43 | 44 | def highlight(self, event=None): 45 | length = tk.IntVar() 46 | for category in self.categories: 47 | matches = self.categories[category]['matches'] 48 | for keyword in matches: 49 | start = 1.0 50 | keyword = keyword + "[^A-Za-z_-]" 51 | idx = self.text_widget.search(keyword, start, stopindex=tk.END, count=length, regexp=1) 52 | while idx: 53 | char_match_found = int(str(idx).split('.')[1]) 54 | line_match_found = int(str(idx).split('.')[0]) 55 | if char_match_found > 0: 56 | previous_char_index = str(line_match_found) + '.' + str(char_match_found - 1) 57 | previous_char = self.text_widget.get(previous_char_index, previous_char_index + "+1c") 58 | 59 | if previous_char.isalnum() or previous_char in self.disallowed_previous_chars: 60 | end = f"{idx}+{length.get() - 1}c" 61 | start = end 62 | idx = self.text_widget.search(keyword, start, stopindex=tk.END, regexp=1) 63 | else: 64 | end = f"{idx}+{length.get() - 1}c" 65 | self.text_widget.tag_add(category, idx, end) 66 | 67 | start = end 68 | idx = self.text_widget.search(keyword, start, stopindex=tk.END, regexp=1) 69 | else: 70 | end = f"{idx}+{length.get() - 1}c" 71 | self.text_widget.tag_add(category, idx, end) 72 | 73 | start = end 74 | idx = self.text_widget.search(keyword, start, stopindex=tk.END, regexp=1) 75 | 76 | self.highlight_regex(r"(\d)+[.]?(\d)*", "number") 77 | self.highlight_regex(r"[\'][^\']*[\']", "string") 78 | self.highlight_regex(r"[\"][^\']*[\"]", "string") 79 | 80 | def highlight_regex(self, regex, tag): 81 | length = tk.IntVar() 82 | start = 1.0 83 | idx = self.text_widget.search(regex, start, stopindex=tk.END, regexp=1, count=length) 84 | while idx: 85 | end = f"{idx}+{length.get()}c" 86 | self.text_widget.tag_add(tag, idx, end) 87 | 88 | start = end 89 | idx = self.text_widget.search(regex, start, stopindex=tk.END, regexp=1, count=length) 90 | 91 | 92 | if __name__ == '__main__': 93 | w = tk.Tk() 94 | h = Highlighter(tk.Text(w), 'languages/python.yaml') 95 | w.mainloop() 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /Ch6/languages/python.yaml: -------------------------------------------------------------------------------- 1 | categories: 2 | keywords: 3 | color: orange 4 | matches: [for, def, while, from, import, as, with, self] 5 | 6 | variables: 7 | color: red4 8 | matches: ['True', 'False', None] 9 | 10 | conditionals: 11 | color: green 12 | matches: [try, except, if, else, elif] 13 | 14 | functions: 15 | color: blue4 16 | matches: [int, str, dict, list, set, float] 17 | 18 | numbers: 19 | color: purple 20 | 21 | strings: 22 | color: red -------------------------------------------------------------------------------- /Ch6/linenumbers.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | 3 | 4 | class LineNumbers(tk.Text): 5 | def __init__(self, master, text_widget, **kwargs): 6 | super().__init__(master, **kwargs) 7 | 8 | self.text_widget = text_widget 9 | self.text_widget.bind('', self.on_key_press) 10 | 11 | self.insert(1.0, '1') 12 | self.configure(state='disabled') 13 | 14 | def on_key_press(self, event=None): 15 | final_index = str(self.text_widget.index(tk.END)) 16 | num_of_lines = final_index.split('.')[0] 17 | line_numbers_string = "\n".join(str(no + 1) for no in range(int(num_of_lines))) 18 | width = len(str(num_of_lines)) 19 | 20 | self.configure(state='normal', width=width) 21 | self.delete(1.0, tk.END) 22 | self.insert(1.0, line_numbers_string) 23 | self.configure(state='disabled') 24 | 25 | 26 | if __name__ == '__main__': 27 | w = tk.Tk() 28 | t = tk.Text(w) 29 | l = LineNumbers(w, t, width=1) 30 | l.pack(side=tk.LEFT) 31 | t.pack(side=tk.LEFT, expand=1) 32 | w.mainloop() -------------------------------------------------------------------------------- /Ch6/requirements.txt: -------------------------------------------------------------------------------- 1 | pyyaml 2 | -------------------------------------------------------------------------------- /Ch6/textarea.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | import tkinter.messagebox as msg 3 | 4 | 5 | class TextArea(tk.Text): 6 | def __init__(self, master, **kwargs): 7 | super().__init__(**kwargs) 8 | 9 | self.master = master 10 | 11 | self.config(wrap=tk.WORD) # CHAR NONE 12 | 13 | self.tag_configure('find_match', background="yellow") 14 | self.find_match_index = None 15 | self.find_search_starting_index = 1.0 16 | 17 | self.bind_events() 18 | 19 | def bind_events(self): 20 | self.bind('', self.select_all) 21 | self.bind('', self.copy) 22 | self.bind('', self.paste) 23 | self.bind('', self.cut) 24 | self.bind('', self.redo) 25 | self.bind('', self.undo) 26 | 27 | def cut(self, event=None): 28 | self.event_generate("<>") 29 | 30 | def copy(self, event=None): 31 | self.event_generate("<>") 32 | 33 | def paste(self, event=None): 34 | self.event_generate("<>") 35 | 36 | def undo(self, event=None): 37 | self.event_generate("<>") 38 | 39 | return "break" 40 | 41 | def redo(self, event=None): 42 | self.event_generate("<>") 43 | 44 | return "break" 45 | 46 | def select_all(self, event=None): 47 | self.tag_add("sel", 1.0, tk.END) 48 | 49 | return "break" 50 | 51 | def find(self, text_to_find): 52 | length = tk.IntVar() 53 | idx = self.search(text_to_find, self.find_search_starting_index, stopindex=tk.END, count=length) 54 | 55 | if idx: 56 | self.tag_remove('find_match', 1.0, tk.END) 57 | 58 | end = f'{idx}+{length.get()}c' 59 | self.tag_add('find_match', idx, end) 60 | self.see(idx) 61 | 62 | self.find_search_starting_index = end 63 | self.find_match_index = idx 64 | else: 65 | if self.find_match_index != 1.0: 66 | if msg.askyesno("No more results", "No further matches. Repeat from the beginning?"): 67 | self.find_search_starting_index = 1.0 68 | self.find_match_index = None 69 | return self.find(text_to_find) 70 | else: 71 | msg.showinfo("No Matches", "No matching text found") 72 | 73 | def replace_text(self, target, replacement): 74 | if self.find_match_index: 75 | 76 | end = f"{self.find_match_index}+{len(target)}c" 77 | self.replace(self.find_match_index, end, replacement) 78 | 79 | self.find_search_starting_index = f"{self.find_match_index} linestart" 80 | self.find_match_index = None 81 | 82 | def cancel_find(self): 83 | self.find_search_starting_index = 1.0 84 | self.find_match_index = None 85 | self.tag_remove('find_match', 1.0, tk.END) 86 | -------------------------------------------------------------------------------- /Ch6/texteditor.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | import tkinter.ttk as ttk 3 | 4 | from textarea import TextArea 5 | from linenumbers import LineNumbers 6 | from highlighter import Highlighter 7 | from findwindow import FindWindow 8 | 9 | 10 | class MainWindow(tk.Tk): 11 | def __init__(self): 12 | super().__init__() 13 | 14 | self.text_area = TextArea(self, bg="white", fg="black", undo=True) 15 | 16 | self.scrollbar = ttk.Scrollbar(orient="vertical", command=self.scroll_text) 17 | self.text_area.configure(yscrollcommand=self.scrollbar.set) 18 | 19 | self.line_numbers = LineNumbers(self, self.text_area, bg="grey", fg="white", width=1) 20 | self.highlighter = Highlighter(self.text_area, 'languages/python.yaml') 21 | 22 | self.scrollbar.pack(side=tk.RIGHT, fill=tk.Y) 23 | self.line_numbers.pack(side=tk.LEFT, fill=tk.Y) 24 | self.text_area.pack(side=tk.LEFT, fill=tk.BOTH, expand=1) 25 | 26 | self.bind_events() 27 | 28 | def bind_events(self): 29 | self.text_area.bind("", self.scroll_text) 30 | self.text_area.bind("", self.scroll_text) 31 | self.text_area.bind("", self.scroll_text) 32 | 33 | self.bind('', self.show_find_window) 34 | 35 | self.line_numbers.bind("", lambda e: "break") 36 | self.line_numbers.bind("", lambda e: "break") 37 | self.line_numbers.bind("", lambda e: "break") 38 | 39 | def scroll_text(self, *args): 40 | if len(args) > 1: 41 | self.text_area.yview_moveto(args[1]) 42 | self.line_numbers.yview_moveto(args[1]) 43 | else: 44 | event = args[0] 45 | if event.delta: 46 | move = -1 * (event.delta / 120) 47 | else: 48 | if event.num == 5: 49 | move = 1 50 | else: 51 | move = -1 52 | 53 | self.text_area.yview_scroll(int(move), "units") 54 | self.line_numbers.yview_scroll(int(move) * 3, "units") 55 | 56 | def show_find_window(self, event=None): 57 | FindWindow(self.text_area) 58 | 59 | 60 | if __name__ == '__main__': 61 | mw = MainWindow() 62 | mw.mainloop() 63 | 64 | -------------------------------------------------------------------------------- /Ch7/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | /__pycache/ 3 | *.pyc 4 | -------------------------------------------------------------------------------- /Ch7/colourchooser.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | import tkinter.ttk as ttk 3 | from tkinter.colorchooser import askcolor 4 | 5 | 6 | class colorChooser(tk.Toplevel): 7 | def __init__(self, master, **kwargs): 8 | super().__init__(**kwargs) 9 | self.master = master 10 | 11 | self.transient(self.master) 12 | self.geometry('400x300') 13 | self.title('color Scheme') 14 | self.configure(bg=self.master.background) 15 | 16 | self.chosen_background_color = tk.StringVar() 17 | self.chosen_foreground_color = tk.StringVar() 18 | self.chosen_text_background_color = tk.StringVar() 19 | self.chosen_text_foreground_color = tk.StringVar() 20 | 21 | self.chosen_background_color.set(self.master.background) 22 | self.chosen_foreground_color.set(self.master.foreground) 23 | self.chosen_text_background_color.set(self.master.text_background) 24 | self.chosen_text_foreground_color.set(self.master.text_foreground) 25 | 26 | window_frame = tk.Frame(self, bg=self.master.background) 27 | window_foreground_frame = tk.Frame(window_frame, bg=self.master.background) 28 | window_background_frame = tk.Frame(window_frame, bg=self.master.background) 29 | 30 | text_frame = tk.Frame(self, bg=self.master.background) 31 | text_foreground_frame = tk.Frame(text_frame, bg=self.master.background) 32 | text_background_frame = tk.Frame(text_frame, bg=self.master.background) 33 | 34 | self.all_frames = [window_frame, window_foreground_frame, window_background_frame, 35 | text_frame, text_foreground_frame, text_background_frame] 36 | 37 | window_label = ttk.Label(window_frame, text="Window:", anchor=tk.W, style="editor.TLabel") 38 | foreground_label = ttk.Label(window_foreground_frame, text="Foreground:", anchor=tk.E, style="editor.TLabel") 39 | background_label = ttk.Label(window_background_frame, text="Background:", anchor=tk.E, style="editor.TLabel") 40 | 41 | text_label = ttk.Label(text_frame, text="Editor:", anchor=tk.W, style="editor.TLabel") 42 | text_foreground_label = ttk.Label(text_foreground_frame, text="Foreground:", anchor=tk.E, style="editor.TLabel") 43 | text_background_label = ttk.Label(text_background_frame, text="Background:", anchor=tk.E, style="editor.TLabel") 44 | 45 | foreground_color_chooser = ttk.Button(window_foreground_frame, text="Change Foreground color", width=26, style="editor.TButton", 46 | command=lambda sv=self.chosen_foreground_color: self.set_color(sv)) 47 | background_color_chooser = ttk.Button(window_background_frame, text="Change Background color", width=26, 48 | style="editor.TButton", 49 | command=lambda sv=self.chosen_background_color: self.set_color(sv)) 50 | text_foreground_color_chooser = ttk.Button(text_foreground_frame, text="Change Text Foreground color", 51 | width=26, style="editor.TButton", 52 | command=lambda sv=self.chosen_text_foreground_color: self.set_color(sv)) 53 | text_background_color_chooser = ttk.Button(text_background_frame, text="Change Text Background color", 54 | width=26, style="editor.TButton", 55 | command=lambda sv=self.chosen_text_background_color: self.set_color(sv)) 56 | 57 | foreground_color_preview = ttk.Label(window_foreground_frame, textvar=self.chosen_foreground_color, 58 | style="editor.TLabel") 59 | background_color_preview = ttk.Label(window_background_frame, textvar=self.chosen_background_color, 60 | style="editor.TLabel") 61 | text_foreground_color_preview = ttk.Label(text_foreground_frame, textvar=self.chosen_text_foreground_color, 62 | style="editor.TLabel") 63 | text_background_color_preview = ttk.Label(text_background_frame, textvar=self.chosen_text_background_color, 64 | style="editor.TLabel") 65 | 66 | 67 | window_frame.pack(side=tk.TOP, fill=tk.X, expand=1) 68 | window_label.pack(side=tk.TOP, fill=tk.X) 69 | 70 | window_foreground_frame.pack(side=tk.TOP, fill=tk.X, expand=1) 71 | window_background_frame.pack(side=tk.TOP, fill=tk.X, expand=1) 72 | 73 | foreground_label.pack(side=tk.LEFT, padx=30, pady=10) 74 | foreground_color_chooser.pack(side=tk.LEFT) 75 | foreground_color_preview.pack(side=tk.LEFT, expand=1, fill=tk.X, padx=(15, 0)) 76 | 77 | background_label.pack(side=tk.LEFT, fill=tk.X, padx=(30, 27)) 78 | background_color_chooser.pack(side=tk.LEFT) 79 | background_color_preview.pack(side=tk.LEFT, expand=1, fill=tk.X, padx=(15, 0)) 80 | 81 | text_frame.pack(side=tk.TOP, fill=tk.X, expand=1) 82 | text_label.pack(side=tk.TOP, fill=tk.X) 83 | 84 | text_foreground_frame.pack(side=tk.TOP, fill=tk.X, expand=1) 85 | text_background_frame.pack(side=tk.TOP, fill=tk.X, expand=1) 86 | 87 | text_foreground_label.pack(side=tk.LEFT, padx=30, pady=10) 88 | text_foreground_color_chooser.pack(side=tk.LEFT) 89 | text_foreground_color_preview.pack(side=tk.LEFT, expand=1, fill=tk.X, padx=(15, 0)) 90 | 91 | text_background_label.pack(side=tk.LEFT, fill=tk.X, padx=(30, 27)) 92 | text_background_color_chooser.pack(side=tk.LEFT) 93 | text_background_color_preview.pack(side=tk.LEFT, expand=1, fill=tk.X, padx=(15, 0)) 94 | 95 | save_button = ttk.Button(self, text="save", command=self.save, style="editor.TButton") 96 | save_button.pack(side=tk.BOTTOM, pady=(0, 20)) 97 | 98 | 99 | def set_color(self, sv): 100 | choice = askcolor()[1] 101 | sv.set(choice) 102 | 103 | def save(self): 104 | yaml_file_contents = f"background: '{self.chosen_background_color.get()}'\n" \ 105 | + f"foreground: '{self.chosen_foreground_color.get()}'\n" \ 106 | + f"text_background: '{self.chosen_text_background_color.get()}'\n" \ 107 | + f"text_foreground: '{self.chosen_text_foreground_color.get()}'\n" 108 | 109 | with open("schemes/default.yaml", "w") as yaml_file: 110 | yaml_file.write(yaml_file_contents) 111 | 112 | self.master.apply_color_scheme(self.chosen_foreground_color.get(), self.chosen_background_color.get(), 113 | self.chosen_text_foreground_color.get(), self.chosen_text_background_color.get()) 114 | for frame in self.all_frames: 115 | frame.configure(bg=self.chosen_background_color.get()) 116 | 117 | self.configure(bg=self.chosen_background_color.get()) 118 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /Ch7/demo/menu.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | 3 | win = tk.Tk() 4 | win.geometry('400x300') 5 | 6 | lab = tk.Label(win, text="Demo application") 7 | 8 | menu = tk.Menu(win) 9 | cascade = tk.Menu(win) 10 | 11 | cascade.add_command(label='Change Label color', command=lambda: lab.configure(fg="blue4")) 12 | cascade.add_command(label='Change Label Highlight', command=lambda: lab.configure(bg="yellow")) 13 | 14 | menu.add_cascade(label="Label colors", menu=cascade) 15 | 16 | menu.add_command(label='Change Label Text', command=lambda: lab.configure(text='Menu Item Clicked')) 17 | menu.add_command(label='Change Window Size', command=lambda: win.geometry('600x600')) 18 | 19 | win.configure(menu=menu) 20 | 21 | context_menu = tk.Menu(win) 22 | context_menu.add_command(label='close', command=win.destroy) 23 | 24 | def on_right_click(event): 25 | x = win.winfo_x() + event.x 26 | y = win.winfo_y() + event.y 27 | 28 | context_menu.post(x, y) 29 | 30 | win.bind('', on_right_click) 31 | 32 | lab.pack(padx=50, pady=50) 33 | 34 | win.mainloop() -------------------------------------------------------------------------------- /Ch7/findwindow.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | import tkinter.ttk as ttk 3 | 4 | 5 | class FindWindow(tk.Toplevel): 6 | def __init__(self, master, **kwargs): 7 | super().__init__(**kwargs) 8 | 9 | self.master = master 10 | 11 | self.geometry('350x100') 12 | self.title('Find and Replace') 13 | self.transient(self.master) 14 | self.configure(bg=self.master.master.background) 15 | 16 | self.text_to_find = tk.StringVar() 17 | self.text_to_replace_with = tk.StringVar() 18 | 19 | top_frame = tk.Frame(self, bg=self.master.master.background) 20 | middle_frame = tk.Frame(self, bg=self.master.master.background) 21 | bottom_frame = tk.Frame(self, bg=self.master.master.background) 22 | 23 | find_entry_label = ttk.Label(top_frame, text="Find: ", style="editor.TLabel") 24 | self.find_entry = ttk.Entry(top_frame, textvar=self.text_to_find) 25 | 26 | replace_entry_label = ttk.Label(middle_frame, text="Replace: ", style="editor.TLabel") 27 | self.replace_entry = ttk.Entry(middle_frame, textvar=self.text_to_replace_with) 28 | 29 | self.find_button = ttk.Button(bottom_frame, text="Find", command=self.on_find, style="editor.TButton") 30 | self.replace_button = ttk.Button(bottom_frame, text="Replace", command=self.on_replace, style="editor.TButton") 31 | self.cancel_button = ttk.Button(bottom_frame, text="Cancel", command=self.on_cancel, style="editor.TButton") 32 | 33 | find_entry_label.pack(side=tk.LEFT, padx=(20, 0)) 34 | self.find_entry.pack(side=tk.LEFT, fill=tk.X, expand=1) 35 | 36 | replace_entry_label.pack(side=tk.LEFT) 37 | self.replace_entry.pack(side=tk.LEFT, fill=tk.X, expand=1) 38 | 39 | self.find_button.pack(side=tk.LEFT, padx=(85, 0)) 40 | self.replace_button.pack(side=tk.LEFT, padx=(20, 20)) 41 | self.cancel_button.pack(side=tk.RIGHT, padx=(0, 30)) 42 | 43 | top_frame.pack(side=tk.TOP, expand=1, fill=tk.X, padx=30) 44 | middle_frame.pack(side=tk.TOP, expand=1, fill=tk.X, padx=30) 45 | bottom_frame.pack(side=tk.TOP, expand=1, fill=tk.X) 46 | 47 | self.find_entry.focus_force() 48 | 49 | self.protocol("WM_DELETE_WINDOW", self.on_cancel) 50 | 51 | def on_find(self): 52 | self.master.find(self.text_to_find.get()) 53 | 54 | def on_replace(self): 55 | self.master.replace_text(self.text_to_find.get(), self.text_to_replace_with.get()) 56 | 57 | def on_cancel(self): 58 | self.master.cancel_find() 59 | self.destroy() 60 | 61 | 62 | if __name__ == '__main__': 63 | mw = tk.Tk() 64 | fw = FindWindow(mw) 65 | mw.mainloop() 66 | -------------------------------------------------------------------------------- /Ch7/fontchooser.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | import tkinter.ttk as ttk 3 | from tkinter.font import families 4 | 5 | 6 | class FontChooser(tk.Toplevel): 7 | def __init__(self, master, **kwargs): 8 | super().__init__(**kwargs) 9 | self.master = master 10 | 11 | self.transient(self.master) 12 | self.geometry('500x250') 13 | self.title('Choose font and size') 14 | 15 | self.configure(bg=self.master.background) 16 | 17 | self.font_list = tk.Listbox(self, exportselection=False) 18 | 19 | self.available_fonts = sorted(families()) 20 | 21 | for family in self.available_fonts: 22 | self.font_list.insert(tk.END, family) 23 | 24 | current_selection_index = self.available_fonts.index(self.master.font_family) 25 | if current_selection_index: 26 | self.font_list.select_set(current_selection_index) 27 | self.font_list.see(current_selection_index) 28 | 29 | self.size_input = tk.Spinbox(self, from_=0, to=99, value=self.master.font_size) 30 | 31 | self.save_button = ttk.Button(self, text="Save", style="editor.TButton", command=self.save) 32 | 33 | self.save_button.pack(side=tk.BOTTOM, fill=tk.X, expand=1, padx=40) 34 | self.font_list.pack(side=tk.LEFT, fill=tk.Y, expand=1) 35 | self.size_input.pack(side=tk.BOTTOM, fill=tk.X, expand=1) 36 | 37 | def save(self): 38 | font_family = self.font_list.get(self.font_list.curselection()[0]) 39 | yaml_file_contents = f"family: {font_family}\n" \ 40 | + f"size: {self.size_input.get()}" 41 | 42 | with open(self.master.font_file, 'w') as file: 43 | file.write(yaml_file_contents) 44 | 45 | self.master.update_font() 46 | 47 | -------------------------------------------------------------------------------- /Ch7/highlighter.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | 3 | import yaml 4 | 5 | 6 | class Highlighter: 7 | def __init__(self, text_widget, syntax_file): 8 | self.text_widget = text_widget 9 | self.syntax_file = syntax_file 10 | self.categories = None 11 | self.numbers_color = "blue" 12 | self.strings_color = "red" 13 | 14 | self.disallowed_previous_chars = ["_", "-", "."] 15 | 16 | self.parse_syntax_file() 17 | 18 | self.text_widget.bind('', self.on_key_release) 19 | 20 | def on_key_release(self, event=None): 21 | self.highlight() 22 | 23 | def parse_syntax_file(self): 24 | with open(self.syntax_file, 'r') as stream: 25 | try: 26 | config = yaml.load(stream) 27 | except yaml.YAMLError as error: 28 | print(error) 29 | return 30 | 31 | self.categories = config['categories'] 32 | self.numbers_color = config['numbers']['color'] 33 | self.strings_color = config['strings']['color'] 34 | 35 | self.configure_tags() 36 | 37 | def configure_tags(self): 38 | for category in self.categories.keys(): 39 | color = self.categories[category]['color'] 40 | self.text_widget.tag_configure(category, foreground=color) 41 | 42 | self.text_widget.tag_configure("number", foreground=self.numbers_color) 43 | self.text_widget.tag_configure("string", foreground=self.strings_color) 44 | 45 | def highlight(self, event=None): 46 | length = tk.IntVar() 47 | for category in self.categories: 48 | matches = self.categories[category]['matches'] 49 | for keyword in matches: 50 | start = 1.0 51 | keyword = keyword + "[^A-Za-z_-]" 52 | idx = self.text_widget.search(keyword, start, stopindex=tk.END, count=length, regexp=1) 53 | while idx: 54 | char_match_found = int(str(idx).split('.')[1]) 55 | line_match_found = int(str(idx).split('.')[0]) 56 | if char_match_found > 0: 57 | previous_char_index = str(line_match_found) + '.' + str(char_match_found - 1) 58 | previous_char = self.text_widget.get(previous_char_index, previous_char_index + "+1c") 59 | 60 | if previous_char.isalnum() or previous_char in self.disallowed_previous_chars: 61 | end = f"{idx}+{length.get() - 1}c" 62 | start = end 63 | idx = self.text_widget.search(keyword, start, stopindex=tk.END, regexp=1) 64 | else: 65 | end = f"{idx}+{length.get() - 1}c" 66 | self.text_widget.tag_add(category, idx, end) 67 | 68 | start = end 69 | idx = self.text_widget.search(keyword, start, stopindex=tk.END, regexp=1) 70 | else: 71 | end = f"{idx}+{length.get() - 1}c" 72 | self.text_widget.tag_add(category, idx, end) 73 | 74 | start = end 75 | idx = self.text_widget.search(keyword, start, stopindex=tk.END, regexp=1) 76 | 77 | self.highlight_regex(r"(\d)+[.]?(\d)*", "number") 78 | self.highlight_regex(r"[\'][^\']*[\']", "string") 79 | self.highlight_regex(r"[\"][^\']*[\"]", "string") 80 | 81 | def highlight_regex(self, regex, tag): 82 | length = tk.IntVar() 83 | start = 1.0 84 | idx = self.text_widget.search(regex, start, stopindex=tk.END, regexp=1, count=length) 85 | while idx: 86 | end = f"{idx}+{length.get()}c" 87 | self.text_widget.tag_add(tag, idx, end) 88 | 89 | start = end 90 | idx = self.text_widget.search(regex, start, stopindex=tk.END, regexp=1, count=length) 91 | 92 | def force_highlight(self): 93 | self.highlight() 94 | 95 | def clear_highlight(self): 96 | for category in self.categories: 97 | self.text_widget.tag_remove(category, 1.0, tk.END) 98 | 99 | 100 | if __name__ == '__main__': 101 | w = tk.Tk() 102 | h = Highlighter(tk.Text(w), 'languages/python.yaml') 103 | w.mainloop() 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /Ch7/languages/python.yaml: -------------------------------------------------------------------------------- 1 | categories: 2 | keywords: 3 | color: orange 4 | matches: [for, def, while, from, import, as, with, self] 5 | 6 | variables: 7 | color: red4 8 | matches: ['True', 'False', None] 9 | 10 | conditionals: 11 | color: green 12 | matches: [try, except, if, else, elif] 13 | 14 | functions: 15 | color: blue4 16 | matches: [int, str, dict, list, set, float] 17 | 18 | numbers: 19 | color: purple 20 | 21 | strings: 22 | color: '#e1218b' 23 | 24 | -------------------------------------------------------------------------------- /Ch7/languages/sql.yaml: -------------------------------------------------------------------------------- 1 | categories: 2 | keywords: 3 | color: orange 4 | matches: [select, where, and, from, order, by, group] 5 | 6 | dangerous: 7 | color: red4 8 | matches: [set, update, drop] 9 | 10 | 11 | numbers: 12 | color: purple 13 | 14 | strings: 15 | color: red -------------------------------------------------------------------------------- /Ch7/linenumbers.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | 3 | 4 | class LineNumbers(tk.Text): 5 | def __init__(self, master, text_widget, **kwargs): 6 | super().__init__(master, **kwargs) 7 | 8 | self.text_widget = text_widget 9 | self.text_widget.bind('', self.on_key_press) 10 | 11 | self.insert(1.0, '1') 12 | self.configure(state='disabled') 13 | 14 | def on_key_press(self, event=None): 15 | final_index = str(self.text_widget.index(tk.END)) 16 | num_of_lines = final_index.split('.')[0] 17 | line_numbers_string = "\n".join(str(no + 1) for no in range(int(num_of_lines))) 18 | width = len(str(num_of_lines)) 19 | 20 | self.configure(state='normal', width=width) 21 | self.delete(1.0, tk.END) 22 | self.insert(1.0, line_numbers_string) 23 | self.configure(state='disabled') 24 | 25 | def force_update(self): 26 | self.on_key_press() 27 | 28 | -------------------------------------------------------------------------------- /Ch7/requirements.txt: -------------------------------------------------------------------------------- 1 | pyyaml 2 | -------------------------------------------------------------------------------- /Ch7/schemes/default.yaml: -------------------------------------------------------------------------------- 1 | background: '#dddddd' 2 | foreground: '#000000' 3 | text_background: '#ffffff' 4 | text_foreground: '#000000' 5 | -------------------------------------------------------------------------------- /Ch7/schemes/font.yaml: -------------------------------------------------------------------------------- 1 | family: Ubuntu Mono 2 | size: 15 -------------------------------------------------------------------------------- /Ch7/textarea.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | import tkinter.messagebox as msg 3 | 4 | 5 | class TextArea(tk.Text): 6 | def __init__(self, master, **kwargs): 7 | super().__init__(**kwargs) 8 | 9 | self.master = master 10 | 11 | self.config(wrap=tk.WORD) # CHAR NONE 12 | 13 | self.tag_configure('find_match', background="yellow") 14 | self.find_match_index = None 15 | self.find_search_starting_index = 1.0 16 | 17 | self.bind_events() 18 | 19 | def bind_events(self): 20 | self.bind('', self.select_all) 21 | self.bind('', self.copy) 22 | self.bind('', self.paste) 23 | self.bind('', self.cut) 24 | self.bind('', self.redo) 25 | self.bind('', self.undo) 26 | 27 | def cut(self, event=None): 28 | self.event_generate("<>") 29 | 30 | return "break" 31 | 32 | def copy(self, event=None): 33 | self.event_generate("<>") 34 | 35 | return "break" 36 | 37 | def paste(self, event=None): 38 | self.event_generate("<>") 39 | 40 | return "break" 41 | 42 | def undo(self, event=None): 43 | self.event_generate("<>") 44 | 45 | return "break" 46 | 47 | def redo(self, event=None): 48 | self.event_generate("<>") 49 | 50 | return "break" 51 | 52 | def select_all(self, event=None): 53 | self.tag_add("sel", 1.0, tk.END) 54 | 55 | return "break" 56 | 57 | def find(self, text_to_find): 58 | length = tk.IntVar() 59 | idx = self.search(text_to_find, self.find_search_starting_index, stopindex=tk.END, count=length) 60 | 61 | if idx: 62 | self.tag_remove('find_match', 1.0, tk.END) 63 | 64 | end = f'{idx}+{length.get()}c' 65 | self.tag_add('find_match', idx, end) 66 | self.see(idx) 67 | 68 | self.find_search_starting_index = end 69 | self.find_match_index = idx 70 | else: 71 | if self.find_match_index != 1.0: 72 | if msg.askyesno("No more results", "No further matches. Repeat from the beginning?"): 73 | self.find_search_starting_index = 1.0 74 | self.find_match_index = None 75 | return self.find(text_to_find) 76 | else: 77 | msg.showinfo("No Matches", "No matching text found") 78 | 79 | def replace_text(self, target, replacement): 80 | if self.find_match_index: 81 | current_found_index_line = str(self.find_match_index).split('.')[0] 82 | 83 | end = f"{self.find_match_index}+{len(target)}c" 84 | self.replace(self.find_match_index, end, replacement) 85 | 86 | self.find_search_starting_index = current_found_index_line + '.0' 87 | 88 | def cancel_find(self): 89 | self.find_search_starting_index = 1.0 90 | self.find_match_index = None 91 | self.tag_remove('find_match', 1.0, tk.END) 92 | 93 | def display_file_contents(self, filepath): 94 | with open(filepath, 'r') as file: 95 | self.delete(1.0, tk.END) 96 | self.insert(1.0, file.read()) 97 | 98 | -------------------------------------------------------------------------------- /Ch7/texteditor.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | import tkinter.ttk as ttk 3 | import tkinter.messagebox as msg 4 | 5 | import yaml 6 | 7 | from tkinter import filedialog 8 | 9 | from textarea import TextArea 10 | from linenumbers import LineNumbers 11 | from highlighter import Highlighter 12 | from findwindow import FindWindow 13 | from colorchooser import colorChooser 14 | from fontchooser import FontChooser 15 | 16 | 17 | class MainWindow(tk.Tk): 18 | def __init__(self): 19 | super().__init__() 20 | 21 | self.title('Python Text Editor v3') 22 | self.geometry('800x600') 23 | 24 | self.foreground = 'black' 25 | self.background = 'lightgrey' 26 | self.text_foreground = 'black' 27 | self.text_background='white' 28 | 29 | self.load_scheme_file('schemes/default.yaml') 30 | self.configure_ttk_elements() 31 | 32 | self.font_size = 15 33 | self.font_family = "Ubuntu Mono" 34 | self.load_font_file('schemes/font.yaml') 35 | 36 | self.text_area = TextArea(self, bg=self.text_background, fg=self.text_foreground, undo=True, 37 | font=(self.font_family, self.font_size)) 38 | 39 | self.scrollbar = ttk.Scrollbar(orient="vertical", command=self.scroll_text) 40 | self.text_area.configure(yscrollcommand=self.scrollbar.set) 41 | 42 | self.line_numbers = LineNumbers(self, self.text_area, bg="grey", fg="white", width=1) 43 | self.highlighter = Highlighter(self.text_area, 'languages/python.yaml') 44 | 45 | self.menu = tk.Menu(self, bg=self.background, fg=self.foreground) 46 | self.all_menus = [self.menu] 47 | 48 | sub_menu_items = ["file", "edit", "tools", "help"] 49 | self.generate_sub_menus(sub_menu_items) 50 | self.configure(menu=self.menu) 51 | 52 | self.right_click_menu = tk.Menu(self, bg=self.background, fg=self.foreground, tearoff=0) 53 | self.right_click_menu.add_command(label='Cut', command=self.edit_cut) 54 | self.right_click_menu.add_command(label='Copy', command=self.edit_copy) 55 | self.right_click_menu.add_command(label='Paste', command=self.edit_paste) 56 | self.all_menus.append(self.right_click_menu) 57 | 58 | self.scrollbar.pack(side=tk.RIGHT, fill=tk.Y) 59 | self.line_numbers.pack(side=tk.LEFT, fill=tk.Y) 60 | self.text_area.pack(side=tk.LEFT, fill=tk.BOTH, expand=1) 61 | 62 | self.bind_events() 63 | 64 | self.open_file = '' 65 | 66 | def bind_events(self): 67 | self.text_area.bind("", self.scroll_text) 68 | self.text_area.bind("", self.scroll_text) 69 | self.text_area.bind("", self.scroll_text) 70 | self.text_area.bind("", self.show_right_click_menu) 71 | 72 | self.bind('', self.show_find_window) 73 | 74 | self.bind('', self.file_new) 75 | self.bind('', self.file_open) 76 | self.bind('', self.file_save) 77 | 78 | self.bind('', self.help_about) 79 | 80 | self.bind('', self.tools_change_syntax_highlighting) 81 | self.bind('', self.tools_change_color_scheme) 82 | self.bind('', self.tools_change_font) 83 | 84 | self.line_numbers.bind("", lambda e: "break") 85 | self.line_numbers.bind("", lambda e: "break") 86 | self.line_numbers.bind("", lambda e: "break") 87 | 88 | def scroll_text(self, *args): 89 | if len(args) > 1: 90 | self.text_area.yview_moveto(args[1]) 91 | self.line_numbers.yview_moveto(args[1]) 92 | else: 93 | event = args[0] 94 | if event.delta: 95 | move = -1 * (event.delta / 120) 96 | else: 97 | if event.num == 5: 98 | move = 1 99 | else: 100 | move = -1 101 | 102 | self.text_area.yview_scroll(int(move), "units") 103 | self.line_numbers.yview_scroll(int(move) * 3, "units") 104 | 105 | def show_find_window(self, event=None): 106 | FindWindow(self.text_area) 107 | 108 | def show_right_click_menu(self, event): 109 | x = self.winfo_x() + self.text_area.winfo_x() + event.x 110 | y = self.winfo_y() + self.text_area.winfo_y() + event.y 111 | self.right_click_menu.post(x, y) 112 | 113 | def generate_sub_menus(self, sub_menu_items): 114 | window_methods = [method_name for method_name in dir(self) 115 | if callable(getattr(self, method_name))] 116 | tkinter_methods = [method_name for method_name in dir(tk.Tk) 117 | if callable(getattr(tk.Tk, method_name))] 118 | 119 | my_methods = [method for method in set(window_methods) - set(tkinter_methods)] 120 | my_methods = sorted(my_methods) 121 | 122 | for item in sub_menu_items: 123 | sub_menu = tk.Menu(self.menu, tearoff=0, bg=self.background, fg=self.foreground) 124 | matching_methods = [] 125 | for method in my_methods: 126 | if method.startswith(item): 127 | matching_methods.append(method) 128 | 129 | for match in matching_methods: 130 | actual_method = getattr(self, match) 131 | method_shortcut = actual_method.__doc__.strip() 132 | friendly_name = ' '.join(match.split('_')[1:]) 133 | sub_menu.add_command(label=friendly_name.title(), command=actual_method, accelerator=method_shortcut) 134 | 135 | self.menu.add_cascade(label=item.title(), menu=sub_menu) 136 | self.all_menus.append(sub_menu) 137 | 138 | def show_about_page(self): 139 | msg.showinfo("About", "My text editor, version 2, written in Python3.6 using tkinter!") 140 | 141 | def load_syntax_highlighting_file(self): 142 | syntax_file = filedialog.askopenfilename(filetypes=[("YAML file", ("*.yaml", "*.yml"))]) 143 | if syntax_file: 144 | self.highlighter.clear_highlight() 145 | self.highlighter = Highlighter(self.text_area, syntax_file) 146 | self.highlighter.force_highlight() 147 | 148 | def load_scheme_file(self, scheme): 149 | with open(scheme, 'r') as stream: 150 | try: 151 | config = yaml.load(stream) 152 | except yaml.YAMLError as error: 153 | print(error) 154 | return 155 | 156 | self.foreground = config['foreground'] 157 | self.background = config['background'] 158 | self.text_foreground = config['text_foreground'] 159 | self.text_background = config['text_background'] 160 | 161 | def load_font_file(self, file_path): 162 | with open(file_path, 'r') as stream: 163 | try: 164 | config = yaml.load(stream) 165 | except yaml.YAMLError as error: 166 | print(error) 167 | return 168 | 169 | self.font_family = config['family'] 170 | self.font_size = config['size'] 171 | 172 | def change_color_scheme(self): 173 | colorChooser(self) 174 | 175 | def apply_color_scheme(self, foreground, background, text_foreground, text_background): 176 | self.text_area.configure(fg=text_foreground, bg=text_background) 177 | self.background = background 178 | self.foreground = foreground 179 | for menu in self.all_menus: 180 | menu.configure(bg=self.background, fg=self.foreground) 181 | self.configure_ttk_elements() 182 | 183 | def configure_ttk_elements(self): 184 | style = ttk.Style() 185 | style.configure('editor.TLabel', foreground=self.foreground, background=self.background) 186 | style.configure('editor.TButton', foreground=self.foreground, background=self.background) 187 | 188 | def change_font(self): 189 | FontChooser(self) 190 | 191 | def update_font(self): 192 | self.load_font_file('schemes/font.yaml') 193 | self.text_area.configure(font=(self.font_family, self.font_size)) 194 | 195 | # =========== Menu Functions ============== 196 | 197 | def file_new(self, event=None): 198 | """ 199 | Ctrl+N 200 | """ 201 | self.text_area.delete(1.0, tk.END) 202 | self.open_file = None 203 | self.line_numbers.force_update() 204 | 205 | def file_open(self, event=None): 206 | """ 207 | Ctrl+O 208 | """ 209 | file_to_open = filedialog.askopenfilename() 210 | if file_to_open: 211 | self.open_file = file_to_open 212 | 213 | self.text_area.display_file_contents(file_to_open) 214 | self.highlighter.force_highlight() 215 | self.line_numbers.force_update() 216 | 217 | def file_save(self, event=None): 218 | """ 219 | Ctrl+S 220 | """ 221 | current_file = self.open_file if self.open_file else None 222 | if not current_file: 223 | current_file = filedialog.asksaveasfilename() 224 | 225 | if current_file: 226 | contents = self.text_area.get(1.0, tk.END) 227 | with open(current_file, 'w') as file: 228 | file.write(contents) 229 | 230 | def edit_cut(self, event=None): 231 | """ 232 | Ctrl+X 233 | """ 234 | self.text_area.event_generate("") 235 | self.line_numbers.force_update() 236 | 237 | def edit_paste(self, event=None): 238 | """ 239 | Ctrl+V 240 | """ 241 | self.text_area.event_generate("") 242 | self.line_numbers.force_update() 243 | self.highlighter.force_highlight() 244 | 245 | def edit_copy(self, event=None): 246 | """ 247 | Ctrl+C 248 | """ 249 | self.text_area.event_generate("") 250 | 251 | def edit_select_all(self, event=None): 252 | """ 253 | Ctrl+A 254 | """ 255 | self.text_area.event_generate("") 256 | 257 | def edit_find_and_replace(self, event=None): 258 | """ 259 | Ctrl+F 260 | """ 261 | self.show_find_window() 262 | 263 | def help_about(self, event=None): 264 | """ 265 | Ctrl+H 266 | """ 267 | self.show_about_page() 268 | 269 | def tools_change_syntax_highlighting(self, event=None): 270 | """ 271 | Ctrl+M 272 | """ 273 | self.load_syntax_highlighting_file() 274 | 275 | def tools_change_color_scheme(self, event=None): 276 | """ 277 | Ctrl+G 278 | """ 279 | self.change_color_scheme() 280 | 281 | def tools_change_font(self, event=None): 282 | """ 283 | Ctrl+L 284 | """ 285 | self.change_font() 286 | 287 | 288 | 289 | if __name__ == '__main__': 290 | mw = MainWindow() 291 | mw.mainloop() 292 | 293 | -------------------------------------------------------------------------------- /Ch8/chatwindow.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import tkinter as tk 4 | import tkinter.ttk as ttk 5 | 6 | from smilieselect import SmilieSelect 7 | 8 | 9 | class ChatWindow(tk.Toplevel): 10 | def __init__(self, master, friend_name, friend_avatar, **kwargs): 11 | super().__init__(**kwargs) 12 | self.master = master 13 | self.title(friend_name) 14 | self.geometry('540x640') 15 | self.minsize(540, 640) 16 | 17 | self.right_frame = tk.Frame(self) 18 | self.left_frame = tk.Frame(self) 19 | self.bottom_frame = tk.Frame(self.left_frame) 20 | 21 | self.messages_area = tk.Text(self.left_frame, bg="white", fg="black", wrap=tk.WORD, width=30) 22 | self.scrollbar = ttk.Scrollbar(self.left_frame, orient='vertical', command=self.messages_area.yview) 23 | self.messages_area.configure(yscrollcommand=self.scrollbar.set) 24 | 25 | self.text_area = tk.Text(self.bottom_frame, bg="white", fg="black", height=3, width=30) 26 | self.text_area.smilies = [] 27 | self.send_button = ttk.Button(self.bottom_frame, text="Send", command=self.send_message, style="send.TButton") 28 | 29 | self.smilies_image = tk.PhotoImage(file="smilies/mikulka-smile-cool.png") 30 | self.smilie_button = ttk.Button(self.bottom_frame, image=self.smilies_image, command=self.smilie_chooser, style="smilie.TButton") 31 | 32 | self.profile_picture = tk.PhotoImage(file="images/avatar.png") 33 | self.friend_profile_picture = tk.PhotoImage(file=friend_avatar) 34 | 35 | self.profile_picture_area = tk.Label(self.right_frame, image=self.profile_picture, relief=tk.RIDGE) 36 | self.friend_profile_picture_area = tk.Label(self.right_frame, image=self.friend_profile_picture, relief=tk.RIDGE) 37 | 38 | self.left_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=1) 39 | self.scrollbar.pack(side=tk.LEFT, fill=tk.Y) 40 | self.messages_area.pack(side=tk.TOP, fill=tk.BOTH, expand=1) 41 | self.messages_area.configure(state='disabled') 42 | 43 | self.right_frame.pack(side=tk.LEFT, fill=tk.Y) 44 | self.profile_picture_area.pack(side=tk.BOTTOM) 45 | self.friend_profile_picture_area.pack(side=tk.TOP) 46 | 47 | self.bottom_frame.pack(side=tk.BOTTOM, fill=tk.X) 48 | self.smilie_button.pack(side=tk.LEFT, pady=5) 49 | self.text_area.pack(side=tk.LEFT, fill=tk.X, expand=1, pady=5) 50 | self.send_button.pack(side=tk.RIGHT, pady=5) 51 | 52 | self.configure_styles() 53 | self.bind_events() 54 | 55 | def bind_events(self): 56 | self.bind("", self.send_message) 57 | self.text_area.bind("", self.send_message) 58 | 59 | self.text_area.bind('', self.smilie_chooser) 60 | 61 | def send_message(self, event=None): 62 | message = self.text_area.get(1.0, tk.END) 63 | 64 | if message.strip() or len(self.text_area.smilies): 65 | message = "Me: " + message 66 | self.messages_area.configure(state='normal') 67 | self.messages_area.insert(tk.END, message) 68 | 69 | if len(self.text_area.smilies): 70 | last_line_no = self.messages_area.index(tk.END) 71 | last_line_no = str(last_line_no).split('.')[0] 72 | last_line_no = str(int(last_line_no) - 2) 73 | 74 | for index, file in self.text_area.smilies: 75 | char_index = str(index).split('.')[1] 76 | char_index = str(int(char_index) + 4) 77 | smilile_index = last_line_no + '.' + char_index 78 | self.messages_area.image_create(smilile_index, image=file) 79 | 80 | self.text_area.smilies = [] 81 | 82 | self.messages_area.configure(state='disabled') 83 | 84 | self.text_area.delete(1.0, tk.END) 85 | 86 | return "break" 87 | 88 | def smilie_chooser(self, event=None): 89 | SmilieSelect(self) 90 | 91 | def add_smilie(self, smilie): 92 | smilie_index = self.text_area.index(self.text_area.image_create(tk.END, image=smilie)) 93 | self.text_area.smilies.append((smilie_index, smilie)) 94 | 95 | def receive_message(self, message, smilies): 96 | """ 97 | Writes message into messages_area 98 | :param message: message text 99 | :param smilies: list of tuples of (char_index, smilie_file), where char_index is the x index of the smilie's location 100 | and smilie_file is the file name only (no path) 101 | :return: None 102 | """ 103 | self.messages_area.configure(state='normal') 104 | self.messages_area.insert(tk.END, message) 105 | 106 | if len(smilies): 107 | last_line_no = self.messages_area.index(tk.END) 108 | last_line_no = str(last_line_no).split('.')[0] 109 | last_line_no = str(int(last_line_no) - 2) 110 | 111 | for index, file in smilies: 112 | smilie_path = os.path.join(SmilieSelect.smilies_dir, file) 113 | image = tk.PhotoImage(file=smilie_path) 114 | smilie_index = last_line_no + '.' + index 115 | self.messages_area.image_create(smilie_index, image=image) 116 | 117 | self.messages_area.configure(state='disabled') 118 | 119 | def configure_styles(self): 120 | style = ttk.Style() 121 | style.configure("send.TButton", background='#dddddd', foreground="black", padding=16) 122 | 123 | 124 | if __name__ == '__main__': 125 | w = tk.Tk() 126 | c = ChatWindow(w, 'friend', 'images/avatar.png') 127 | c.protocol("WM_DELETE_WINDOW", w.destroy) 128 | w.mainloop() 129 | -------------------------------------------------------------------------------- /Ch8/demo/demo.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | 3 | win = tk.Tk() 4 | canvas = tk.Canvas(win) 5 | frame = tk.Frame(canvas) 6 | 7 | scroll = tk.Scrollbar(win, orient='vertical', command=canvas.yview) 8 | scroll.pack(side=tk.RIGHT, fill=tk.Y) 9 | 10 | canvas.configure(yscrollcommand=scroll.set) 11 | canvas.pack(fill=tk.BOTH, expand=1) 12 | canvas.create_window((0, 0), window=frame, anchor='nw') 13 | 14 | 15 | def on_frame_resized(self, event=None): 16 | canvas.configure(scrollregion=canvas.bbox("all")) 17 | 18 | 19 | win.bind('', on_frame_resized) 20 | 21 | for _ in range(30): 22 | tk.Label(frame, text="big label").pack(pady=20) 23 | 24 | win.mainloop() -------------------------------------------------------------------------------- /Ch8/friendslist.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | import tkinter.ttk as ttk 3 | 4 | from chatwindow import ChatWindow 5 | 6 | 7 | class FriendsList(tk.Tk): 8 | def __init__(self, **kwargs): 9 | super().__init__(**kwargs) 10 | 11 | self.title('Tk Chat') 12 | self.geometry('700x500') 13 | 14 | self.menu = tk.Menu(self, bg="lightgrey", fg="black", tearoff=0) 15 | 16 | self.friends_menu = tk.Menu(self.menu, fg="black", bg="lightgrey", tearoff=0) 17 | self.friends_menu.add_command(label="Add Friend", command=self.add_friend) 18 | 19 | self.menu.add_cascade(label="Friends", menu=self.friends_menu) 20 | 21 | self.configure(menu=self.menu) 22 | 23 | self.canvas = tk.Canvas(self, bg="white") 24 | self.canvas_frame = tk.Frame(self.canvas) 25 | 26 | self.scrollbar = ttk.Scrollbar(self, orient="vertical", command=self.canvas.yview) 27 | self.canvas.configure(yscrollcommand=self.scrollbar.set) 28 | 29 | self.scrollbar.pack(side=tk.LEFT, fill=tk.Y) 30 | self.canvas.pack(side=tk.LEFT, expand=1, fill=tk.BOTH) 31 | 32 | self.friends_area = self.canvas.create_window((0, 0), window=self.canvas_frame, anchor="nw") 33 | 34 | self.bind_events() 35 | 36 | self.load_friends() 37 | 38 | def bind_events(self): 39 | self.bind('', self.on_frame_resized) 40 | self.canvas.bind('', self.friends_width) 41 | 42 | def friends_width(self, event): 43 | canvas_width = event.width 44 | self.canvas.itemconfig(self.friends_area, width=canvas_width) 45 | 46 | def on_frame_resized(self, event=None): 47 | self.canvas.configure(scrollregion=self.canvas.bbox("all")) 48 | 49 | def load_friends(self): 50 | friend_frame = ttk.Frame(self.canvas_frame) 51 | 52 | profile_photo = tk.PhotoImage(file="images/avatar.png") 53 | profile_photo_label = ttk.Label(friend_frame, image=profile_photo) 54 | profile_photo_label.image = profile_photo 55 | 56 | friend_name = ttk.Label(friend_frame, text="Jaden Corebyn", anchor=tk.W) 57 | 58 | message_button = ttk.Button(friend_frame, text="Chat", command=self.open_chat_window) 59 | 60 | profile_photo_label.pack(side=tk.LEFT) 61 | friend_name.pack(side=tk.LEFT) 62 | message_button.pack(side=tk.RIGHT) 63 | 64 | friend_frame.pack(fill=tk.X, expand=1) 65 | 66 | def add_friend(self): 67 | self.load_friends() 68 | 69 | def open_chat_window(self): 70 | cw = ChatWindow(self, 'Jaden Corebyn', 'images/avatar.png') 71 | 72 | if __name__ == '__main__': 73 | f = FriendsList() 74 | f.mainloop() 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /Ch8/images/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dvlv/Tkinter-Gui-Programming-By-Example-Packt/6ddac5df32297b9ebaa19d1f08212337724dfed7/Ch8/images/avatar.png -------------------------------------------------------------------------------- /Ch8/smilies/mikulka-smile-cool.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dvlv/Tkinter-Gui-Programming-By-Example-Packt/6ddac5df32297b9ebaa19d1f08212337724dfed7/Ch8/smilies/mikulka-smile-cool.png -------------------------------------------------------------------------------- /Ch8/smilies/mikulka-smile-grin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dvlv/Tkinter-Gui-Programming-By-Example-Packt/6ddac5df32297b9ebaa19d1f08212337724dfed7/Ch8/smilies/mikulka-smile-grin.png -------------------------------------------------------------------------------- /Ch8/smilies/mikulka-smile-license.txt: -------------------------------------------------------------------------------- 1 | 2008 mikulka 2 | 3 | The person or persons who have associated work with this document (the "Dedicator" or "Certifier") hereby either (a) certifies that, to the best of his knowledge, the work of authorship identified is in the public domain of the country from which the work is published, or (b) hereby dedicates whatever copyright the dedicators holds in the work of authorship identified below (the "Work") to the public domain. A certifier, moreover, dedicates any copyright interest he may have in the associated work, and for these purposes, is described as a "dedicator" below. 4 | 5 | A certifier has taken reasonable steps to verify the copyright status of this work. Certifier recognizes that his good faith efforts may not shield him from liability if in fact the work certified is not in the public domain. 6 | 7 | Dedicator makes this dedication for the benefit of the public at large and to the detriment of the Dedicator's heirs and successors. Dedicator intends this dedication to be an overt act of relinquishment in perpetuity of all present and future rights under copyright law, whether vested or contingent, in the Work. Dedicator understands that such relinquishment of all rights includes the relinquishment of all rights to enforce (by lawsuit or otherwise) those copyrights in the Work. 8 | 9 | Dedicator recognizes that, once placed in the public domain, the Work may be freely reproduced, distributed, transmitted, used, modified, built upon, or otherwise exploited by anyone for any purpose, commercial or non-commercial, and in any way, including by methods that have not yet been invented or conceived. 10 | -------------------------------------------------------------------------------- /Ch8/smilies/mikulka-smile-overview.png.ignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dvlv/Tkinter-Gui-Programming-By-Example-Packt/6ddac5df32297b9ebaa19d1f08212337724dfed7/Ch8/smilies/mikulka-smile-overview.png.ignore -------------------------------------------------------------------------------- /Ch8/smilies/mikulka-smile-razz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dvlv/Tkinter-Gui-Programming-By-Example-Packt/6ddac5df32297b9ebaa19d1f08212337724dfed7/Ch8/smilies/mikulka-smile-razz.png -------------------------------------------------------------------------------- /Ch8/smilies/mikulka-smile-sad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dvlv/Tkinter-Gui-Programming-By-Example-Packt/6ddac5df32297b9ebaa19d1f08212337724dfed7/Ch8/smilies/mikulka-smile-sad.png -------------------------------------------------------------------------------- /Ch8/smilies/mikulka-smile-smile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dvlv/Tkinter-Gui-Programming-By-Example-Packt/6ddac5df32297b9ebaa19d1f08212337724dfed7/Ch8/smilies/mikulka-smile-smile.png -------------------------------------------------------------------------------- /Ch8/smilies/mikulka-smile-source.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dvlv/Tkinter-Gui-Programming-By-Example-Packt/6ddac5df32297b9ebaa19d1f08212337724dfed7/Ch8/smilies/mikulka-smile-source.xcf -------------------------------------------------------------------------------- /Ch8/smilies/mikulka-smile-surprised.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dvlv/Tkinter-Gui-Programming-By-Example-Packt/6ddac5df32297b9ebaa19d1f08212337724dfed7/Ch8/smilies/mikulka-smile-surprised.png -------------------------------------------------------------------------------- /Ch8/smilies/mikulka-smile-wink.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dvlv/Tkinter-Gui-Programming-By-Example-Packt/6ddac5df32297b9ebaa19d1f08212337724dfed7/Ch8/smilies/mikulka-smile-wink.png -------------------------------------------------------------------------------- /Ch8/smilieselect.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import tkinter as tk 4 | import tkinter.ttk as ttk 5 | 6 | 7 | class SmilieSelect(tk.Toplevel): 8 | smilies_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), 'smilies/')) 9 | 10 | def __init__(self, master, **kwargs): 11 | super().__init__(**kwargs) 12 | self.master = master 13 | self.transient(master) 14 | self.position_window() 15 | 16 | smilie_files = [file for file in os.listdir(self.smilies_dir) if file.endswith(".png")] 17 | 18 | self.smilie_images = [] 19 | 20 | for file in smilie_files: 21 | full_path = os.path.join(self.smilies_dir, file) 22 | image = tk.PhotoImage(file=full_path) 23 | self.smilie_images.append(image) 24 | 25 | for index, file in enumerate(self.smilie_images): 26 | row, col = divmod(index, 3) 27 | button = ttk.Button(self, image=file, command=lambda s=file: self.insert_smilie(s)) 28 | button.grid(row=row, column=col, sticky='nsew') 29 | 30 | for i in range(3): 31 | tk.Grid.columnconfigure(self, i, weight=1) 32 | tk.Grid.rowconfigure(self, i, weight=1) 33 | 34 | def position_window(self): 35 | master_pos_x = self.master.winfo_x() 36 | master_pos_y = self.master.winfo_y() 37 | 38 | master_width = self.master.winfo_width() 39 | master_height = self.master.winfo_height() 40 | 41 | my_width = 100 42 | my_height = 100 43 | 44 | pos_x = (master_pos_x + (master_width // 2)) - (my_width // 2) 45 | pos_y = (master_pos_y + (master_height // 2)) - (my_height // 2) 46 | 47 | geometry = f"{my_width}x{my_height}+{pos_x}+{pos_y}" 48 | self.geometry(geometry) 49 | 50 | def insert_smilie(self, smilie): 51 | self.master.add_smilie(smilie) 52 | self.destroy() 53 | 54 | 55 | if __name__ == '__main__': 56 | w = tk.Tk() 57 | s = SmilieSelect(w) 58 | w.mainloop() -------------------------------------------------------------------------------- /Ch9/chatwindow.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | 4 | import tkinter as tk 5 | import tkinter.ttk as ttk 6 | 7 | from smilieselect import SmilieSelect 8 | 9 | 10 | class ChatWindow(tk.Toplevel): 11 | def __init__(self, master, friend_name, friend_username, friend_avatar, **kwargs): 12 | super().__init__(**kwargs) 13 | self.master = master 14 | self.title(friend_name) 15 | self.geometry('540x640') 16 | self.minsize(540, 640) 17 | 18 | self.friend_username = friend_username 19 | 20 | self.right_frame = tk.Frame(self) 21 | self.left_frame = tk.Frame(self) 22 | self.bottom_frame = tk.Frame(self.left_frame) 23 | 24 | self.messages_area = tk.Text(self.left_frame, bg="white", fg="black", wrap=tk.WORD, width=30) 25 | self.scrollbar = ttk.Scrollbar(self.left_frame, orient='vertical', command=self.messages_area.yview) 26 | self.messages_area.configure(yscrollcommand=self.scrollbar.set) 27 | 28 | self.text_area = tk.Text(self.bottom_frame, bg="white", fg="black", height=3, width=30) 29 | self.text_area.smilies = [] 30 | self.send_button = ttk.Button(self.bottom_frame, text="Send", command=self.send_message, style="send.TButton") 31 | 32 | self.smilies_image = tk.PhotoImage(file="smilies/mikulka-smile-cool.png") 33 | self.smilie_button = ttk.Button(self.bottom_frame, image=self.smilies_image, command=self.smilie_chooser, style="smilie.TButton") 34 | 35 | self.profile_picture = tk.PhotoImage(file="images/avatar.png") 36 | self.friend_profile_picture = tk.PhotoImage(file=friend_avatar) 37 | 38 | self.profile_picture_area = tk.Label(self.right_frame, image=self.profile_picture, relief=tk.RIDGE) 39 | self.friend_profile_picture_area = tk.Label(self.right_frame, image=self.friend_profile_picture, relief=tk.RIDGE) 40 | 41 | self.left_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=1) 42 | self.scrollbar.pack(side=tk.LEFT, fill=tk.Y) 43 | self.messages_area.pack(side=tk.TOP, fill=tk.BOTH, expand=1) 44 | self.messages_area.configure(state='disabled') 45 | 46 | self.right_frame.pack(side=tk.LEFT, fill=tk.Y) 47 | self.profile_picture_area.pack(side=tk.BOTTOM) 48 | self.friend_profile_picture_area.pack(side=tk.TOP) 49 | 50 | self.bottom_frame.pack(side=tk.BOTTOM, fill=tk.X) 51 | self.smilie_button.pack(side=tk.LEFT, pady=5) 52 | self.text_area.pack(side=tk.LEFT, fill=tk.X, expand=1, pady=5) 53 | self.send_button.pack(side=tk.RIGHT, pady=5) 54 | 55 | self.configure_styles() 56 | self.bind_events() 57 | self.load_history() 58 | 59 | def bind_events(self): 60 | self.bind("", self.send_message) 61 | self.text_area.bind("", self.send_message) 62 | 63 | self.text_area.bind('', self.smilie_chooser) 64 | 65 | def load_history(self): 66 | history = self.master.requester.prepare_conversation(self.master.username, self.friend_username) 67 | 68 | if len(history['history']): 69 | for message in history['history']: 70 | self.receive_message(message['author'], message['message']) 71 | 72 | def send_message(self, event=None): 73 | message = self.text_area.get(1.0, tk.END) 74 | 75 | if message.strip() or len(self.text_area.smilies): 76 | self.master.requester.send_message( 77 | self.master.username, 78 | self.friend_username, 79 | message, 80 | ) 81 | 82 | message = "Me: " + message 83 | self.messages_area.configure(state='normal') 84 | self.messages_area.insert(tk.END, message) 85 | 86 | if len(self.text_area.smilies): 87 | last_line_no = self.messages_area.index(tk.END) 88 | last_line_no = str(last_line_no).split('.')[0] 89 | last_line_no = str(int(last_line_no) - 2) 90 | 91 | for index, file in self.text_area.smilies: 92 | char_index = str(index).split('.')[1] 93 | char_index = str(int(char_index) + 4) 94 | smilile_index = last_line_no + '.' + char_index 95 | self.messages_area.image_create(smilile_index, image=file) 96 | 97 | self.text_area.smilies = [] 98 | 99 | self.messages_area.configure(state='disabled') 100 | 101 | self.text_area.delete(1.0, tk.END) 102 | 103 | return "break" 104 | 105 | def smilie_chooser(self, event=None): 106 | SmilieSelect(self) 107 | 108 | def add_smilie(self, smilie): 109 | smilie_index = self.text_area.index(self.text_area.image_create(tk.END, image=smilie)) 110 | self.text_area.smilies.append((smilie_index, smilie)) 111 | 112 | def receive_message(self, author, message): 113 | self.messages_area.configure(state='normal') 114 | 115 | if author == self.master.username: 116 | author = "Me" 117 | 118 | message_with_author = author + ": " + message 119 | 120 | self.messages_area.insert(tk.END, message_with_author) 121 | 122 | self.messages_area.configure(state='disabled') 123 | 124 | def configure_styles(self): 125 | style = ttk.Style() 126 | style.configure("send.TButton", background='#dddddd', foreground="black", padding=16) 127 | 128 | 129 | if __name__ == '__main__': 130 | w = tk.Tk() 131 | c = ChatWindow(w, 'friend', 'friend', 'images/avatar.png') 132 | c.protocol("WM_DELETE_WINDOW", w.destroy) 133 | w.mainloop() 134 | -------------------------------------------------------------------------------- /Ch9/friendslist.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | import tkinter.messagebox as msg 3 | import tkinter.ttk as ttk 4 | 5 | from functools import partial 6 | 7 | from chatwindow import ChatWindow 8 | from requester import Requester 9 | 10 | 11 | class FriendsList(tk.Tk): 12 | def __init__(self, **kwargs): 13 | super().__init__(**kwargs) 14 | 15 | self.title('Tk Chat') 16 | self.geometry('700x500') 17 | 18 | self.menu = tk.Menu(self, bg="lightgrey", fg="black", tearoff=0) 19 | 20 | self.friends_menu = tk.Menu(self.menu, fg="black", bg="lightgrey", tearoff=0) 21 | self.friends_menu.add_command(label="Add Friend", command=self.add_friend) 22 | 23 | self.menu.add_cascade(label="Friends", menu=self.friends_menu) 24 | 25 | self.requester = Requester() 26 | 27 | self.show_login_screen() 28 | 29 | def show_login_screen(self): 30 | self.login_frame = ttk.Frame(self) 31 | 32 | username_label = ttk.Label(self.login_frame, text="Username") 33 | self.username_entry = ttk.Entry(self.login_frame) 34 | 35 | real_name_label = ttk.Label(self.login_frame, text="Real Name") 36 | self.real_name_entry = ttk.Entry(self.login_frame) 37 | 38 | login_button = ttk.Button(self.login_frame, text="Login", command=self.login) 39 | create_account_button = ttk.Button(self.login_frame, text="Create Account", command=self.create_account) 40 | 41 | username_label.grid(row=0, column=0, sticky='e') 42 | self.username_entry.grid(row=0, column=1) 43 | 44 | real_name_label.grid(row=1, column=0, sticky='e') 45 | self.real_name_entry.grid(row=1, column=1) 46 | 47 | login_button.grid(row=2, column=0, sticky='e') 48 | create_account_button.grid(row=2, column=1) 49 | 50 | for i in range(3): 51 | tk.Grid.rowconfigure(self.login_frame, i, weight=1) 52 | tk.Grid.columnconfigure(self.login_frame, i, weight=1) 53 | 54 | self.login_frame.pack(fill=tk.BOTH, expand=1) 55 | 56 | def login(self): 57 | username = self.username_entry.get() 58 | real_name = self.real_name_entry.get() 59 | 60 | if self.requester.login(username, real_name): 61 | self.username = username 62 | self.real_name = real_name 63 | 64 | self.show_friends() 65 | else: 66 | msg.showerror("Failed", f"Could not log in as {username}") 67 | 68 | def create_account(self): 69 | username = self.username_entry.get() 70 | real_name = self.real_name_entry.get() 71 | 72 | if self.requester.create_account(username, real_name): 73 | self.username = username 74 | self.real_name = real_name 75 | 76 | self.show_friends() 77 | else: 78 | msg.showerror("Failed", "Account already exists!") 79 | 80 | def show_friends(self): 81 | self.configure(menu=self.menu) 82 | self.login_frame.pack_forget() 83 | 84 | self.canvas = tk.Canvas(self, bg="white") 85 | self.canvas_frame = tk.Frame(self.canvas) 86 | 87 | self.scrollbar = ttk.Scrollbar(self, orient="vertical", command=self.canvas.yview) 88 | self.canvas.configure(yscrollcommand=self.scrollbar.set) 89 | 90 | self.scrollbar.pack(side=tk.LEFT, fill=tk.Y) 91 | self.canvas.pack(side=tk.LEFT, expand=1, fill=tk.BOTH) 92 | 93 | self.friends_area = self.canvas.create_window((0, 0), window=self.canvas_frame, anchor="nw") 94 | 95 | self.bind_events() 96 | 97 | self.load_friends() 98 | 99 | def bind_events(self): 100 | self.bind('', self.on_frame_resized) 101 | self.canvas.bind('', self.friends_width) 102 | 103 | def friends_width(self, event): 104 | canvas_width = event.width 105 | self.canvas.itemconfig(self.friends_area, width=canvas_width) 106 | 107 | def on_frame_resized(self, event=None): 108 | self.canvas.configure(scrollregion=self.canvas.bbox("all")) 109 | 110 | def load_friends(self): 111 | all_users = self.requester.get_all_users() 112 | 113 | for user in all_users: 114 | if user['username'] != self.username: 115 | friend_frame = ttk.Frame(self.canvas_frame) 116 | 117 | profile_photo = tk.PhotoImage(file="images/avatar.png") 118 | profile_photo_label = ttk.Label(friend_frame, image=profile_photo) 119 | profile_photo_label.image = profile_photo 120 | 121 | friend_name = ttk.Label(friend_frame, text=user['real_name'], anchor=tk.W) 122 | 123 | message_this_friend = partial(self.open_chat_window, username=user["username"], real_name=user["real_name"]) 124 | 125 | message_button = ttk.Button(friend_frame, text="Chat", command=message_this_friend) 126 | 127 | profile_photo_label.pack(side=tk.LEFT) 128 | friend_name.pack(side=tk.LEFT) 129 | message_button.pack(side=tk.RIGHT) 130 | 131 | friend_frame.pack(fill=tk.X, expand=1) 132 | 133 | def add_friend(self): 134 | self.load_friends() 135 | 136 | def open_chat_window(self, username, real_name): 137 | cw = ChatWindow(self, real_name, username, 'images/avatar.png') 138 | 139 | if __name__ == '__main__': 140 | f = FriendsList() 141 | f.mainloop() 142 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /Ch9/images/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dvlv/Tkinter-Gui-Programming-By-Example-Packt/6ddac5df32297b9ebaa19d1f08212337724dfed7/Ch9/images/avatar.png -------------------------------------------------------------------------------- /Ch9/requester.py: -------------------------------------------------------------------------------- 1 | import json 2 | import requests 3 | 4 | 5 | class Requester: 6 | def __init__(self): 7 | self.url = "http://127.0.0.1:5000" 8 | 9 | def request(self, method, endpoint, params=None): 10 | url = self.url + endpoint 11 | 12 | if method == "GET": 13 | r = requests.get(url, params=params) 14 | 15 | return r.text 16 | else: 17 | r = requests.post(url, data=params) 18 | 19 | return r.json() 20 | 21 | def login(self, username, real_name): 22 | endpoint = "/user_exists" 23 | params = {"username": username, "real_name": real_name} 24 | 25 | user_exists = self.request("POST", endpoint, params) 26 | 27 | if user_exists["exists"]: 28 | return True 29 | 30 | return False 31 | 32 | def create_account(self, username, real_name): 33 | endpoint = "/user_exists" 34 | params = {"username": username} 35 | exists = self.request("POST", endpoint, params) 36 | 37 | if exists["exists"]: 38 | return False 39 | 40 | endpoint = "/add_user" 41 | params = {"username": username, "real_name": real_name} 42 | 43 | self.request("POST", endpoint, params) 44 | 45 | return True 46 | 47 | def get_all_users(self): 48 | endpoint = "/get_all_users" 49 | 50 | users = self.request("GET", endpoint) 51 | print(users) 52 | 53 | return json.loads(users) 54 | 55 | def prepare_conversation(self, user_one, user_two): 56 | endpoint = "/create_conversation_db" 57 | params = {"user_one": user_one, "user_two": user_two} 58 | 59 | self.request("POST", endpoint, params) 60 | 61 | endpoint = "/get_message_history" 62 | history = self.request("POST", endpoint, params) 63 | 64 | return history 65 | 66 | def send_message(self, author, friend_name, message): 67 | endpoint = f"/send_message/{friend_name}" 68 | params = { 69 | "author": author, 70 | "message": message, 71 | } 72 | 73 | self.request("POST", endpoint, params) 74 | 75 | return True 76 | 77 | -------------------------------------------------------------------------------- /Ch9/server/conversation.py: -------------------------------------------------------------------------------- 1 | import arrow 2 | import sqlite3 3 | 4 | 5 | class Conversation: 6 | def __init__(self, database): 7 | self.database = database 8 | 9 | def initialise_table(self): 10 | sql = "CREATE TABLE conversation (author text, message text, date_sent text)" 11 | conn = sqlite3.connect(self.database) 12 | cursor = conn.cursor() 13 | cursor.execute(sql) 14 | conn.commit() 15 | conn.close() 16 | 17 | def get_history(self): 18 | sql = "SELECT * FROM conversation" 19 | conn = sqlite3.connect(self.database) 20 | conn.row_factory = sqlite3.Row 21 | cursor = conn.cursor() 22 | cursor.execute(sql) 23 | results = [dict(row) for row in cursor.fetchall()] 24 | conn.close() 25 | 26 | return results 27 | 28 | def add_message(self, author, message, date_sent): 29 | sql = "INSERT INTO conversation VALUES (?, ?, ?)" 30 | params = (author, message, date_sent) 31 | conn = sqlite3.connect(self.database) 32 | cursor = conn.cursor() 33 | cursor.execute(sql, params) 34 | conn.commit() 35 | conn.close() -------------------------------------------------------------------------------- /Ch9/server/create_database.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | 3 | database = sqlite3.connect("chat.db") 4 | cursor = database.cursor() 5 | 6 | create_users_sql = "CREATE TABLE users (username TEXT, real_name TEXT)" 7 | cursor.execute(create_users_sql) 8 | 9 | database.commit() 10 | database.close() 11 | -------------------------------------------------------------------------------- /Ch9/server/database.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | 3 | 4 | class Database: 5 | def __init__(self): 6 | self.database = "chat.db" 7 | 8 | def perform_insert(self, sql, params): 9 | conn = sqlite3.connect(self.database) 10 | cursor = conn.cursor() 11 | cursor.execute(sql, params) 12 | conn.commit() 13 | conn.close() 14 | 15 | def perform_select(self, sql, params): 16 | conn = sqlite3.connect(self.database) 17 | conn.row_factory = sqlite3.Row 18 | cursor = conn.cursor() 19 | cursor.execute(sql, params) 20 | results = [dict(row) for row in cursor.fetchall()] 21 | conn.close() 22 | 23 | return results 24 | 25 | def add_user(self, username, real_name): 26 | sql = "INSERT INTO users (username, real_name) VALUES (?,?)" 27 | query_params = (username, real_name) 28 | 29 | self.perform_insert(sql, query_params) 30 | 31 | def get_all_users(self): 32 | sql = "SELECT username, real_name FROM users" 33 | params = [] 34 | 35 | return self.perform_select(sql, params) 36 | 37 | def user_exists(self, username): 38 | sql = "SELECT username FROM users WHERE username = ?" 39 | params = (username,) 40 | 41 | results = self.perform_select(sql, params) 42 | 43 | if len(results): 44 | return True 45 | 46 | return False 47 | 48 | def block_contact(self, username, contact_to_block): 49 | sql = "update relationship set blocked=1 where (user_one = ? and user_two = ?) or (user_two = ? and user_one = ?)" 50 | query_params = (username, contact_to_block, username, contact_to_block) 51 | 52 | self.perform_insert(sql, query_params) 53 | 54 | def create_relationship(self, user_one, user_two): 55 | sql = "insert into relationship (user_one, user_two, blocked) values (?,?,0)" 56 | query_params = (user_one, user_two) 57 | 58 | self.perform_insert(sql, query_params) -------------------------------------------------------------------------------- /Ch9/server/database_schema.sql: -------------------------------------------------------------------------------- 1 | create table users (username text, real_name text); 2 | 3 | create table relationship (user_one text, user_two text, blocked integer); 4 | 5 | -- create a messages table per contact 6 | -- message, author, order -> ? sqlite internal id -------------------------------------------------------------------------------- /Ch9/server/requirements.txt: -------------------------------------------------------------------------------- 1 | arrow==0.12.1 2 | certifi==2018.1.18 3 | chardet==3.0.4 4 | click==6.7 5 | Flask==1.0 6 | idna==2.6 7 | itsdangerous==0.24 8 | Jinja2==2.10 9 | MarkupSafe==1.0 10 | Pillow==5.0.0 11 | python-dateutil==2.7.0 12 | requests==2.20.0 13 | six==1.11.0 14 | Werkzeug==0.15.3 15 | urllib3==1.24.2 -------------------------------------------------------------------------------- /Ch9/server/server.py: -------------------------------------------------------------------------------- 1 | import os 2 | import arrow 3 | 4 | from flask import Flask, session, url_for, request, g, jsonify, json 5 | 6 | from database import Database 7 | from conversation import Conversation 8 | 9 | 10 | app = Flask(__name__) 11 | app.config.from_object(__name__) 12 | 13 | app.secret_key = "tkinterguiprogrammingbyexample" 14 | 15 | database = Database() 16 | 17 | conversations_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), 'conversations/')) 18 | 19 | 20 | @app.route("/", methods=["GET"]) 21 | def index(): 22 | data = { 23 | "cats": 5, 24 | "people": 8, 25 | "dogs": 4 26 | } 27 | 28 | return jsonify(data) 29 | 30 | 31 | @app.route("/send_me_data", methods=["POST"]) 32 | def send_me_data(): 33 | data = request.form 34 | for key, value in data.items(): 35 | print("received", key, "with value", value) 36 | 37 | return "Thanks" 38 | 39 | 40 | @app.route("/get_all_users") 41 | def get_all_users(): 42 | all_users = database.get_all_users() 43 | 44 | return jsonify(all_users) 45 | 46 | 47 | @app.route("/add_user", methods=["POST"]) 48 | def add_user(): 49 | data = request.form 50 | username = data["username"] 51 | real_name = data["real_name"] 52 | 53 | database.add_user(username, real_name) 54 | 55 | return jsonify( 56 | "User Created" 57 | ) 58 | 59 | 60 | @app.route("/user_exists", methods=["POST"]) 61 | def user_exists(): 62 | username = request.form.get("username") 63 | exists = database.user_exists(username) 64 | 65 | return jsonify({ 66 | "exists": exists 67 | }) 68 | 69 | 70 | @app.route("/create_conversation_db", methods=["POST"]) 71 | def create_conversation_db(): 72 | conversation_db_path = get_conversation_db_path_for_users(request.form) 73 | print(conversations_dir) 74 | 75 | if not os.path.exists(conversation_db_path): 76 | with open(conversation_db_path, 'w') as f: 77 | pass 78 | conversation = Conversation(conversation_db_path) 79 | conversation.initialise_table() 80 | 81 | return jsonify({ 82 | "success": True, 83 | }) 84 | 85 | 86 | @app.route("/get_message_history", methods=["POST"]) 87 | def get_message_history(): 88 | conversation_db_path = get_conversation_db_path_for_users(request.form) 89 | conversation = Conversation(conversation_db_path) 90 | 91 | history = conversation.get_history() 92 | 93 | return jsonify({ 94 | "history": history 95 | }) 96 | 97 | 98 | @app.route("/send_message/", methods=["POST"]) 99 | def send_message(username): 100 | data = request.form 101 | author = data["author"] 102 | message = data["message"] 103 | date_sent = arrow.now().timestamp 104 | 105 | conversation_db_path = get_conversation_db_path_for_users({"user_one": author, "user_two": username}) 106 | conversation = Conversation(conversation_db_path) 107 | conversation.add_message(author, message, date_sent) 108 | 109 | return jsonify({ 110 | "success": True 111 | }) 112 | 113 | 114 | @app.route("/add_friend", methods=["POST"]) 115 | def add_contact(): 116 | data = request.form 117 | username = data['username'] 118 | 119 | 120 | @app.route("/block_user/") 121 | def block_contact(username): 122 | pass 123 | 124 | 125 | @app.route("/unblock_user/") 126 | def unblock_contact(username): 127 | pass 128 | 129 | 130 | def get_conversation_db_path_for_users(data): 131 | user_one = data["user_one"] 132 | user_two = data["user_two"] 133 | 134 | users_in_order = sorted([user_one, user_two]) 135 | users_in_order = "_".join(users_in_order) 136 | 137 | conversation_db = users_in_order + ".db" 138 | conversation_db_path = os.path.join(conversations_dir, conversation_db) 139 | 140 | return conversation_db_path 141 | 142 | if __name__ == '__main__': 143 | app.run(debug=True) 144 | -------------------------------------------------------------------------------- /Ch9/smilies/mikulka-smile-cool.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dvlv/Tkinter-Gui-Programming-By-Example-Packt/6ddac5df32297b9ebaa19d1f08212337724dfed7/Ch9/smilies/mikulka-smile-cool.png -------------------------------------------------------------------------------- /Ch9/smilies/mikulka-smile-grin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dvlv/Tkinter-Gui-Programming-By-Example-Packt/6ddac5df32297b9ebaa19d1f08212337724dfed7/Ch9/smilies/mikulka-smile-grin.png -------------------------------------------------------------------------------- /Ch9/smilies/mikulka-smile-license.txt: -------------------------------------------------------------------------------- 1 | 2008 mikulka 2 | 3 | The person or persons who have associated work with this document (the "Dedicator" or "Certifier") hereby either (a) certifies that, to the best of his knowledge, the work of authorship identified is in the public domain of the country from which the work is published, or (b) hereby dedicates whatever copyright the dedicators holds in the work of authorship identified below (the "Work") to the public domain. A certifier, moreover, dedicates any copyright interest he may have in the associated work, and for these purposes, is described as a "dedicator" below. 4 | 5 | A certifier has taken reasonable steps to verify the copyright status of this work. Certifier recognizes that his good faith efforts may not shield him from liability if in fact the work certified is not in the public domain. 6 | 7 | Dedicator makes this dedication for the benefit of the public at large and to the detriment of the Dedicator's heirs and successors. Dedicator intends this dedication to be an overt act of relinquishment in perpetuity of all present and future rights under copyright law, whether vested or contingent, in the Work. Dedicator understands that such relinquishment of all rights includes the relinquishment of all rights to enforce (by lawsuit or otherwise) those copyrights in the Work. 8 | 9 | Dedicator recognizes that, once placed in the public domain, the Work may be freely reproduced, distributed, transmitted, used, modified, built upon, or otherwise exploited by anyone for any purpose, commercial or non-commercial, and in any way, including by methods that have not yet been invented or conceived. 10 | -------------------------------------------------------------------------------- /Ch9/smilies/mikulka-smile-overview.png.ignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dvlv/Tkinter-Gui-Programming-By-Example-Packt/6ddac5df32297b9ebaa19d1f08212337724dfed7/Ch9/smilies/mikulka-smile-overview.png.ignore -------------------------------------------------------------------------------- /Ch9/smilies/mikulka-smile-razz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dvlv/Tkinter-Gui-Programming-By-Example-Packt/6ddac5df32297b9ebaa19d1f08212337724dfed7/Ch9/smilies/mikulka-smile-razz.png -------------------------------------------------------------------------------- /Ch9/smilies/mikulka-smile-sad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dvlv/Tkinter-Gui-Programming-By-Example-Packt/6ddac5df32297b9ebaa19d1f08212337724dfed7/Ch9/smilies/mikulka-smile-sad.png -------------------------------------------------------------------------------- /Ch9/smilies/mikulka-smile-smile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dvlv/Tkinter-Gui-Programming-By-Example-Packt/6ddac5df32297b9ebaa19d1f08212337724dfed7/Ch9/smilies/mikulka-smile-smile.png -------------------------------------------------------------------------------- /Ch9/smilies/mikulka-smile-source.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dvlv/Tkinter-Gui-Programming-By-Example-Packt/6ddac5df32297b9ebaa19d1f08212337724dfed7/Ch9/smilies/mikulka-smile-source.xcf -------------------------------------------------------------------------------- /Ch9/smilies/mikulka-smile-surprised.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dvlv/Tkinter-Gui-Programming-By-Example-Packt/6ddac5df32297b9ebaa19d1f08212337724dfed7/Ch9/smilies/mikulka-smile-surprised.png -------------------------------------------------------------------------------- /Ch9/smilies/mikulka-smile-wink.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dvlv/Tkinter-Gui-Programming-By-Example-Packt/6ddac5df32297b9ebaa19d1f08212337724dfed7/Ch9/smilies/mikulka-smile-wink.png -------------------------------------------------------------------------------- /Ch9/smilieselect.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import tkinter as tk 4 | import tkinter.ttk as ttk 5 | 6 | 7 | class SmilieSelect(tk.Toplevel): 8 | smilies_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), 'smilies/')) 9 | 10 | def __init__(self, master, **kwargs): 11 | super().__init__(**kwargs) 12 | self.master = master 13 | self.transient(master) 14 | self.position_window() 15 | 16 | smilie_files = [file for file in os.listdir(self.smilies_dir) if file.endswith(".png")] 17 | 18 | self.smilie_images = [] 19 | 20 | for file in smilie_files: 21 | full_path = os.path.join(self.smilies_dir, file) 22 | image = tk.PhotoImage(file=full_path) 23 | self.smilie_images.append(image) 24 | 25 | for index, file in enumerate(self.smilie_images): 26 | row, col = divmod(index, 3) 27 | button = ttk.Button(self, image=file, command=lambda s=file: self.insert_smilie(s)) 28 | button.grid(row=row, column=col, sticky='nsew') 29 | 30 | for i in range(3): 31 | tk.Grid.columnconfigure(self, i, weight=1) 32 | tk.Grid.rowconfigure(self, i, weight=1) 33 | 34 | def position_window(self): 35 | master_pos_x = self.master.winfo_x() 36 | master_pos_y = self.master.winfo_y() 37 | 38 | master_width = self.master.winfo_width() 39 | master_height = self.master.winfo_height() 40 | 41 | my_width = 100 42 | my_height = 100 43 | 44 | pos_x = (master_pos_x + (master_width // 2)) - (my_width // 2) 45 | pos_y = (master_pos_y + (master_height // 2)) - (my_height // 2) 46 | 47 | geometry = f"{my_width}x{my_height}+{pos_x}+{pos_y}" 48 | self.geometry(geometry) 49 | 50 | def insert_smilie(self, smilie): 51 | self.master.add_smilie(smilie) 52 | self.destroy() 53 | 54 | 55 | if __name__ == '__main__': 56 | w = tk.Tk() 57 | s = SmilieSelect(w) 58 | w.mainloop() -------------------------------------------------------------------------------- /assets/README.md: -------------------------------------------------------------------------------- 1 | This is the assets folder for chapters 3 and 4. 2 | 3 | To obtain the assets used by the book's author, download the images from this URL: 4 | 5 | https://opengameart.org/sites/default/files/Free-Game-Assets-08-Playing-Cards.zip 6 | 7 | Inside are three sets of cards. Choose which set you like more. Screenshots from the book are using "Modern". 8 | 9 | Copy the cards from this folder back into your "assets" folder. 10 | 11 | Now the images need to be resized and renamed. 12 | 13 | This can be done from a command line using a program named "ImageMagick". 14 | 15 | http://www.imagemagick.org/script/index.php 16 | 17 | This python script should do it for you: 18 | 19 | ```python 20 | import os 21 | 22 | no = ["tabletop.png", "resize.py", "README.md", "sounds"] 23 | 24 | filename_map = { 25 | "h": "Hearts", 26 | "d": "Diamonds", 27 | "c": "Clubs", 28 | "s": "Spades", 29 | } 30 | 31 | value_map = { 32 | 1: "A", 33 | 11: "J", 34 | 12: "Q", 35 | 13: "K", 36 | } 37 | 38 | images = os.listdir() 39 | 40 | for i in images: 41 | if i.startswith('.'): 42 | continue 43 | 44 | if i not in no: 45 | value = int(i[1:-4]) 46 | if value == 1 or value > 10: 47 | value = value_map[value] 48 | filename = filename_map[i[0:1]] + str(value) + ".png" 49 | os.system(f"convert {i} -resize 80x111\> {filename}") 50 | 51 | 52 | ``` 53 | 54 | This script can be found in this folder, named `resize.py`. 55 | 56 | -------------------------------------------------------------------------------- /assets/resize.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | no = ["tabletop.png", "back.png"] 4 | 5 | for i in os.listdir(): 6 | if i not in no: 7 | os.system(f"convert {i} -resize 80x111\> {i}") 8 | -------------------------------------------------------------------------------- /assets/sounds/cardPlace1.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dvlv/Tkinter-Gui-Programming-By-Example-Packt/6ddac5df32297b9ebaa19d1f08212337724dfed7/assets/sounds/cardPlace1.wav -------------------------------------------------------------------------------- /assets/sounds/cardShuffle.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dvlv/Tkinter-Gui-Programming-By-Example-Packt/6ddac5df32297b9ebaa19d1f08212337724dfed7/assets/sounds/cardShuffle.wav -------------------------------------------------------------------------------- /assets/sounds/chipsStack6.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dvlv/Tkinter-Gui-Programming-By-Example-Packt/6ddac5df32297b9ebaa19d1f08212337724dfed7/assets/sounds/chipsStack6.wav -------------------------------------------------------------------------------- /assets/sounds/readme.txt: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | Casino sounds by Kenney Vleugels (www.kenney.nl) 4 | 5 | You may use these graphics in personal and commercial projects. 6 | Credit (www.kenney.nl) would be nice but is not mandatory. 7 | 8 | -- 9 | 10 | https://opengameart.org/content/54-casino-sound-effects-cards-dice-chips 11 | -------------------------------------------------------------------------------- /assets/tabletop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dvlv/Tkinter-Gui-Programming-By-Example-Packt/6ddac5df32297b9ebaa19d1f08212337724dfed7/assets/tabletop.png -------------------------------------------------------------------------------- /ch4-mod/counter/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dvlv/Tkinter-Gui-Programming-By-Example-Packt/6ddac5df32297b9ebaa19d1f08212337724dfed7/ch4-mod/counter/__init__.py -------------------------------------------------------------------------------- /ch4-mod/counter/countdown.py: -------------------------------------------------------------------------------- 1 | def count_down(max): 2 | numbers = [i for i in range(max)] 3 | for num in numbers[::-1]: 4 | print(num, end=', ') 5 | -------------------------------------------------------------------------------- /ch4-mod/counter/countup.py: -------------------------------------------------------------------------------- 1 | def count_up(max): 2 | for i in range(max): 3 | print(i, end=', ') 4 | -------------------------------------------------------------------------------- /ch4-mod/mymod.py: -------------------------------------------------------------------------------- 1 | myvariable = ['important', 'list'] 2 | 3 | def do_a_thing(): 4 | print('mymod is doing something') 5 | 6 | def do_another_thing(): 7 | print('mymod is doing something else, and myvariable is', myvariable) 8 | --------------------------------------------------------------------------------