class MainFrame(tk.Frame): """ Implements the widget interface and logic. Displays a Key object and a Phrase object with their own internal logic, and four optional buttons. Implements user-input logic not specific to the Key or Phrase object. Handles widget keyboard and mouse events. Args: master (tk.Tk): Root object inheriting from tk.Tk Methods: create_key_label() -> tk.Label create_phrase_label() -> tk.Label create_copy_button() -> tk.Button create_save_button() -> tk.Button create_up_button() -> tk.Button create_down_button() -> tk.Button configure_gui() copy_phrase() save_entry() handle_key_tab(tk.Event) -> str handle_phrase_tab(tk.Event) -> str handle_key_backspace(tk.Event) -> str handle_phrase_backspace(tk.Event) -> str handle_key_release(tk.Event) handle_phrase_input(tk.Event) - NOT IMPLEMENTED bind_event_handlers() """ def __init__(self, master): self.master = master tk.Frame.__init__( self, master=master, bg='#EEEEEE' #Background color ) self.db = config.active_objects['db'] self.key_label = self.create_key_label() self.key = Key(self) self.phrase_label = self.create_phrase_label() self.phrase = Phrase(self) self.widget1 = self.key #To allow same call as TranslationMainFrame self.widget2 = self.phrase #To allow same call as TranslationMainFrame if config.get_show_buttons(): print('creating buttons') self.left_button = self.create_left_button() self.right_button = self.create_right_button() self.up_button = self.create_up_button() self.down_button = self.create_down_button() self.activate_get_mode() self.configure_gui() self.bind_event_handlers() if config.get_show_tutorial(): self.load_tutorial() def create_key_label(self): """ Creates a label for the key entry widget | None -> tk.Label """ language_dict = config.get_language_dict() #Active language name dict label = tk.Label( master=self, text=language_dict['key'], bg='#EEEEEE' ) return label def create_phrase_label(self): """ Creates a label for the phrase text widget | None -> tk.Label """ language_dict = config.get_language_dict() #Active language name dict label = tk.Label( master=self, text=language_dict['phrase'], bg='#EEEEEE' ) return label def create_left_button(self): """ Creates button on left of app window| None -> tk.Button """ language_dict = config.get_language_dict() #Active language name dict button = tk.Button( master=self, command=config.change_mode, text=language_dict['new'], #Changes with config.change_mode relief=tk.RIDGE, borderwidth=2, fg='BLACK', bg='#DDDDDD', padx=10, pady=5 ) return button def create_right_button(self): """ Creates button on right of app window | None -> tk.Button """ language_dict = config.get_language_dict() #Active language name dict button = tk.Button( master=self, command=self.copy_phrase, #Changes with config.change_mode text=language_dict['copy'], #Changes with config.change_mode relief=tk.RIDGE, borderwidth=2, fg='BLACK', bg='#DDDDDD', padx=10, pady=5 ) return button def activate_get_mode(self): """ Sets app to get entry from database | None -> None """ if config.get_mode() == 'get': return language_dict = config.get_language_dict() for item in [self, self.key_label, self.phrase_label]: item.config(bg='#EEEEEE') self.phrase.config( bg='#F5F5F5', borderwidth=3 ) if config.get_show_buttons(): self.left_button.config( text=language_dict['new'], ) self.right_button.config( text=language_dict['copy'], command=self.copy_phrase ) self.key.focus() self.key.full_clear() self.phrase.full_clear() config.set_mode('get') def activate_put_mode(self): """ Sets app to save entry to database | None -> None """ if config.get_mode() == 'put': return language_dict = config.get_language_dict() for item in [self, self.key_label, self.phrase_label]: item.config(bg='#F5FCFF') self.phrase.config( bg='#FFFFFF', borderwidth=2 ) if config.get_show_buttons(): self.left_button.config( text=language_dict['cancel'], ) self.right_button.config( text=language_dict['save'], command=self.save_entry ) self.phrase.focus() self.key.ignore_suggestion() self.phrase.full_clear() config.set_mode('put') def create_up_button(self): """ Creates previous phrase button | None -> tk.Button """ icon = Image.open('icons/noun_chevron up_730241.png') icon = icon.resize((20,20), Image.ANTIALIAS) icon = ImageTk.PhotoImage(icon) self.up_icon = icon button = tk.Button( master=self, command=self.phrase.previous, #Displays previous phrase image=icon, relief=tk.RIDGE, borderwidth=2, bg='#CCCCCC', ) return button def create_down_button(self): """ Creates next phrase button | None -> tk.Button """ icon = Image.open('icons/noun_chevron down_730206.png') icon = icon.resize((20,20), Image.ANTIALIAS) icon = ImageTk.PhotoImage(icon) self.down_icon = icon button = tk.Button( master=self, command=self.phrase.next, #Displays next phrase image=icon, relief=tk.RIDGE, borderwidth=2, bg='#CCCCCC', ) return button def set_text(self, language_dict): """ Sets text of all widgets to config settings | None -> None """ self.key_label.config(text=language_dict['key']) self.phrase_label.config(text=language_dict['phrase']) if config.get_mode() == 'get': self.left_button.config(text=language_dict['new']) self.right_button.config(text=language_dict['copy']) else: self.left_button.config(text=language_dict['cancel']) self.right_button.config(text=language_dict['save']) def configure_gui(self): """ Configures tkinter GUI | None -> None """ #Define rows and columns self.columnconfigure(0, weight=1, pad=10) self.columnconfigure(1, weight=100, pad=10) self.rowconfigure(0, weight=0, pad=10) self.rowconfigure(1, weight=100, pad=10) self.master.rowconfigure(0, weight=1) self.master.columnconfigure(0, weight=1) #Place widgets self.key_label.grid( row=0, column=0, pady=(10,0) ) self.key.grid( row=0, column=1, sticky='ew', #Stretches horizontally with window padx=(0,100), pady=(10,0) ) self.phrase_label.grid( row=1, column=0 ) self.phrase.grid( row=1, column=1, sticky='nsew', #Stretches with window padx=(0,20), pady=(5,10) ) if config.get_show_buttons(): self.left_button.grid( row=2, column=1, sticky='w', padx=(20,0), pady=(0,15) ) self.right_button.grid( row=2, column=1, sticky='e', padx=(0,40), pady=(0,15) ) self.up_button.grid( row=1, column=2, sticky='n', padx=(0,15), pady=(15,0) ) self.down_button.grid( row=1, column=2, sticky='s', padx=(0,15), pady=(0,15) ) #Place self in Root object self.grid( row=0, column=0, sticky='nsew' #Stretches with window ) self.key.focus() #Focus on key widget def copy_phrase(self): """ Copy phrase text widget contents to clipboard | None -> None """ phrase = self.phrase.get_contents() self.master.clipboard_clear() self.master.clipboard_append(phrase) def save_entry(self): """ Save active key/phrase combination to db | None -> None """ #Get key and phrase key_list = self.key.get_display_key_list() print(key_list) phrase = self.phrase.get_contents() print(phrase) #Save combination self.db.prepare_undo() self.db.save_entry(key_list, phrase) print(self.db) #Clear widgets after save self.phrase.full_clear() self.key.full_clear() def block_key_new_line(self, event): """ Prevent new lines in key text widget | None -> str """ print('blocking new line') return 'break' #Interrupt standard tkinter event processing def handle_key_tab(self, event): """ Handle tab keypress in Key text widget | None -> str """ if config.get_mode() == 'put': if not self.key.suggestion_text: self.phrase.focus() return 'break' self.key.handle_tab(event) return 'break' #Interrupt standard tkinter event processing def handle_phrase_tab(self, event): """ Handle tab keypress in Phrase text widget | None -> str """ if config.get_mode() == 'put': if not self.phrase.suggestion_text: self.key.focus() return 'break' self.phrase.handle_tab(event) return 'break' #Interrupt standard tkinter event processing def handle_key_backspace(self, event): """ Handles Backspace + modifier in Key widget | tk.Event -> str """ return self.key.handle_backspace(event) #Returns None or 'break' def handle_phrase_backspace(self, event): """ Handles Backspace + modifier in Phrase widget | tk.Event -> str """ return self.phrase.handle_backspace(event) #Returns None or 'break' def handle_key_button_release(self, event): """ Button release manager for key widget | tk.Event -> None When in put mode, switch to get mode if both fields are empty. When in put mode, confirm suggestion before switching focus to key """ self.clear_hints() #Clear hints if active and activate get mode key_contents = self.key.get_contents() phrase_contents = self.phrase.get_contents() if config.get_mode() == 'put': self.phrase.confirm_suggestion() if key_contents == '' == phrase_contents: self.activate_get_mode() #Switch to get mode self.key.handle_button_release(event) def handle_phrase_button_release(self, event): self.clear_hints() #Clear hints if active and activate get mode if self.phrase.get_selection(): #If user selected text in phrase box self.key.confirm_suggestion() return elif not config.get_mode() == 'put': self.activate_put_mode() return else: self.phrase.handle_button_release(event) def debug(self, number): if not config.get_debug(): return print('') print(number) print(f'current_text = {self.key.current_text}') print(f'current_cursor = {self.key.current_cursor}') print(f'display_text = {self.key.get_contents()}') print(f'display_cursor = {self.key.get_cursor()}') print(f'suggestion_text = {self.key.suggestion_text}') print(f'suggestion_list = {self.key.suggestion_list}') def handle_key_key_release(self, event): """ Key release manager for key widget | tk.Event -> None """ #No autocomplete for key widget in put mode if not config.get_mode() == 'put': self.key_autocomplete(event) else: self.key.current_text = self.key.get_contents() def handle_phrase_key_release(self, event): """ Autocomplete phrase and display related keys | tk.Event -> None If key field is not empty, do not autocomplete to avoid deleting user input """ if not self.key.current_text: #If no user input in key field self.phrase_autocomplete(event) def key_autocomplete(self, event): """ Autocomplete key and suggest phrase | tk.Event -> None """ self.key.autocomplete(event) self.suggest_phrase() def phrase_autocomplete(self, event): """ Autocomplete phrase and get saved keys | tk.Event -> None """ self.phrase.autocomplete(event) saved_keys = self.phrase.get_saved_keys() self.key.set_contents(saved_keys) if saved_keys: print('saved keys:' , saved_keys) self.phrase.ignore_suggestion() else: print('no saved keys') self.key.full_clear() self.phrase.autocomplete(event) def suggest_phrase(self): key_list = self.key.get_display_key_list() #Includes suggestion if any success = self.phrase.display_phrase(key_list) #Display top valid phrase if not success: #If no valid phrase with autocompleted Key input: self.phrase.full_clear() #Clear phrase display def display_hint(self, hint_list): """ Display first hint in hint list | lst(str) -> None """ config.set_mode('hint') self.focus() self.phrase.active_list = hint_list self.phrase_active_list_index = 0 self.phrase_display_current() def load_tutorial(self): """ Display tutorial in hint mode | None -> None """ config.set_mode('hint') config.decrement_tutorial() self.focus() language_dict = config.get_language_dict() with open(language_dict['tutorial'], 'r') as source: tutorial = source.read().split('***\n') self.phrase.active_list = tutorial self.phrase.active_list_index = 0 self.phrase.display_current() def clear_hints(self): """ Clear hints and switch to get mode | None -> None """ print(f'clear hints if {config.get_mode() == "hint"}') if config.get_mode() == 'hint': self.activate_get_mode() def print_tracker_variables(self): print('Key variables:') print(f'Key - current_text: {self.key.current_text}') print(f'Key - current_text: {self.key.suggestion_text}') print(f'Key - current_text: {self.key.suggestion_list}') print(f'Key - current_text: {self.key.current_cursor}') print(f'Phrase - current_text: {self.phrase.current_text}') print(f'Phrase - current_text: {self.phrase.suggestion_text}') print(f'Phrase - current_text: {self.phrase.suggestion_list}') print(f'Phrase - current_text: {self.phrase.current_cursor}') def bind_event_handlers(self): """ Binds all event handlers for all widgets | None -> None """ #Copy and save bindings - active in all focus states self.master.bind('<Control-c>', lambda event: self.copy_phrase()) self.master.bind('<Command-c>', lambda event: self.copy_phrase()) self.master.bind('<Control-s>', lambda event: self.save_entry()) self.master.bind('<Command-s>', lambda event: self.save_entry()) self.master.bind('<Command-z>', lambda event: self.db.undo()) self.master.bind('<Control-z>', lambda event: self.db.undo()) self.master.bind('<Control-y>', lambda event: self.db.redo()) self.master.bind('<Command-y>', lambda event: self.db.redo()) self.master.bind('<Control-Shift-z>', lambda event: self.db.redo()) self.master.bind('<Command-Shift-z>', lambda event: self.db.redo()) self.master.bind('<Escape>', lambda event: config.change_mode()) self.master.bind('<Up>', lambda event: self.phrase.previous()) self.master.bind('<Down>', lambda event: self.phrase.next()) #Key bindings - active when focus on Key entry widget self.key.bind('<Return>', self.block_key_new_line) self.key.bind('<Tab>', self.handle_key_tab) self.key.bind('<BackSpace>', self.handle_key_backspace) self.key.bind('<KeyRelease>', self.handle_key_key_release) self.key.bind('<ButtonRelease>', self.handle_key_button_release) #Phrase bindings - active when focus on Phrase text widget self.phrase.bind('<Tab>', self.handle_phrase_tab) self.phrase.bind('<BackSpace>', self.handle_phrase_backspace) self.phrase.bind('<ButtonRelease>', self.handle_phrase_button_release) self.phrase.bind('<KeyRelease>', self.handle_phrase_key_release)
class MainFrame(tk.Frame): """ Implements the widget interface and logic. Displays two text widget with their own internal logic, and four optional buttons. Implements user-input logic not specific to the text widgets. Handles keyboard and mouse events. Args: master (tk.Tk): Root object inheriting from tk.Tk Methods: create_label_1() -> tk.Label create_label_2() -> tk.Label create_copy_button() -> tk.Button create_save_button() -> tk.Button create_up_button() -> tk.Button create_down_button() -> tk.Button configure_gui() copy() save_entry() handle_key_tab(tk.Event) -> str handle_phrase_tab(tk.Event) -> str handle_key_backspace(tk.Event) -> str handle_phrase_backspace(tk.Event) -> str handle_key_release(tk.Event) handle_phrase_input(tk.Event) - NOT IMPLEMENTED bind_event_handlers() """ def __init__(self, master): self.master = master tk.Frame.__init__( self, master=master, bg='#EEEEEE' #Background color ) self.db = config.active_objects['db'] self.db_type = config.get_db_type() self.label_1 = self.create_label_1() self.label_2 = self.create_label_2() if self.db_type == 'standard': self.box_1 = Key(self) self.box_2 = Phrase(self) else: self.box_1 = Language1(self) self.box_2 = Language2(self) if config.get_show_buttons(): print('creating buttons') self.save_button = self.create_save_button() self.copy_button = self.create_copy_button() self.up_button = self.create_up_button() self.down_button = self.create_down_button() self.configure_gui() self.bind_event_handlers() def get_label_1_text(self): """ Gets correct text for label 1 None -> str """ if self.db_type == 'standard': text = config.get_language_dict()['key'] else: text = config.get_lang1() return text def get_label_2_text(self): """ Gets correct text for label 1 None -> str """ if self.db_type == 'standard': text = config.get_language_dict()['phrase'] else: text = config.get_lang2() return text def create_label_1(self): """ Creates a label for the top text widget | None -> tk.Label """ label = tk.Label(master=self, text=self.get_label_1_text(), bg='#EEEEEE') return label def create_label_2(self): """ Creates a label for the bottom text widget | None -> tk.Label """ label = tk.Label(master=self, text=self.get_label_2_text(), bg='#EEEEEE') return label def create_save_button(self): """ Creates save button on left of app window| None -> tk.Button """ language_dict = config.get_language_dict() #Active language name dict button = tk.Button(master=self, command=self.save_entry, text=language_dict['save'], relief=tk.RIDGE, borderwidth=2, fg='BLACK', bg='#DDDDDD', padx=10, pady=5) return button def create_copy_button(self): """ Creates copy button on right of app window | None -> tk.Button """ language_dict = config.get_language_dict() #Active language name dict button = tk.Button(master=self, command=self.copy, text=language_dict['copy'], relief=tk.RIDGE, borderwidth=2, fg='BLACK', bg='#DDDDDD', padx=10, pady=5) return button def create_up_button(self): """ Creates previous suggestion button | None -> tk.Button """ icon = Image.open('icons/noun_chevron up_730241.png') icon = icon.resize((20, 20), Image.ANTIALIAS) icon = ImageTk.PhotoImage(icon) self.up_icon = icon button = tk.Button( master=self, command=self.previous_match, #Displays previous phrase image=icon, relief=tk.RIDGE, borderwidth=2, bg='#CCCCCC', ) return button def create_down_button(self): """ Creates next suggestion button | None -> tk.Button """ icon = Image.open('icons/noun_chevron down_730206.png') icon = icon.resize((20, 20), Image.ANTIALIAS) icon = ImageTk.PhotoImage(icon) self.down_icon = icon button = tk.Button( master=self, command=self.next_match, #Displays next phrase image=icon, relief=tk.RIDGE, borderwidth=2, bg='#CCCCCC', ) return button def set_text(self, language_dict): """ Sets text of all widgets to config settings | None -> None """ self.lang1_label.config(text=self.get_label_1_text()) self.lang2_label.config(text=self.get_label_2_text()) self.left_button.config(text=language_dict['save']) self.right_button.config(text=language_dict['copy']) def configure_gui(self): """ Configures tkinter GUI | None -> None """ #Define rows and columns self.columnconfigure(0, weight=1, pad=10) self.columnconfigure(1, weight=100, pad=10) self.rowconfigure(0, weight=0, pad=10) self.rowconfigure(1, weight=100, pad=10) self.master.rowconfigure(0, weight=1) self.master.columnconfigure(0, weight=1) #Place widgets self.label_1.grid(row=0, column=0, pady=(10, 0)) self.box_1.grid( row=0, column=1, sticky='ew', #Stretches horizontally with window padx=(0, 100), pady=(10, 0)) self.label_2.grid(row=1, column=0) self.box_2.grid( row=1, column=1, sticky='nsew', #Stretches with window padx=(0, 20), pady=(5, 10)) if config.get_show_buttons(): self.save_button.grid(row=2, column=1, sticky='w', padx=(20, 0), pady=(0, 15)) self.copy_button.grid(row=2, column=1, sticky='e', padx=(0, 40), pady=(0, 15)) self.up_button.grid(row=1, column=2, sticky='n', padx=(0, 15), pady=(15, 0)) self.down_button.grid(row=1, column=2, sticky='s', padx=(0, 15), pady=(0, 15)) #Place self in Root object self.grid( row=0, column=0, sticky='nsew' #Stretches with window ) self.box_1.focus() #Focus on top text widget def get_active_box(self): if self.box_2.active_list: print('box_2 active') return self.box_2 elif self.box_1.active_list: print('box_1 active') return self.box_1 return None def next_match(self): active_box = self.get_active_box() if active_box: active_box.next() return 'break' def previous_match(self): active_box = self.get_active_box() if active_box: active_box.previous() return 'break' def copy(self): """ Copy active suggestion to clipboard | None -> None """ active_box = self.get_active_box() if active_lang: suggestion = active_box.get_contents() else: suggestion = None if suggestion: self.master.clipboard_clear() self.master.clipboard_append(suggestion) def save_entry(self): """ Save combination to database | None -> None """ error_message = 'save_entry must be overridden by subclass' raise NotImplementedError(error_message) def delete_entry(self): """ Delete combination from database | None -> None """ error_message = 'delete_entry must be overridden by subclass' raise NotImplementedError(error_message) def block_new_line(self, event): """ Prevent new lines in text box | None -> str """ print('blocking new line') return 'break' #Interrupt standard tkinter event processing def handle_box_1_tab(self, event): """ Handle tab keypress in top text box | None -> str """ if self.box_1.suggestion_text: self.box_1.handle_tab(event) return 'break' self.box_2.focus() return 'break' #Interrupt standard tkinter event processing def handle_box_2_tab(self, event): """ Handle tab keypress in bottom text box | None -> str """ if self.box_2.suggestion_text: self.box_2.handle_tab(event) return 'break' self.box_1.focus() return 'break' #Interrupt standard tkinter event processing def handle_box_1_backspace(self, event): """ Handles Backspace + modifier in top text box | tk.Event -> str """ return self.box_1.handle_backspace(event) #Returns None or 'break' def handle_box_2_backspace(self, event): """ Handles Backspace + modifier in bottom text box | tk.Event -> str """ return self.box_2.handle_backspace(event) #Returns None or 'break' def handle_box_1_button_release(self, event): """ Button release manager for top text box | tk.Event -> None """ if self.box_1.get_selection(): #If user selected text in lang1 box self.box_2.confirm_suggestion() return self.box_1.handle_button_release(event) def handle_box_2_button_release(self, event): """ Button release manager for bottom text box | tk.Event -> None """ if self.box_2.get_selection(): #If user selected text in lang2 box self.box_1.confirm_suggestion() return self.box_2.handle_button_release(event) def handle_box_1_key_release(self, event): """ Key release manager for top text box | tk.Event -> None """ if event.keysym in ('Up', 'Down'): return 'break' self.box_1_autocomplete(event) def handle_box_2_key_release(self, event): """ Key release manager for bottom text box | tk.Event -> None """ if event.keysym in ('Up', 'Down'): return 'break' self.box_2_autocomplete(event) def box_1_autocomplete(self, event): """ Autocomplete box_1 and suggest box_2 match | tk.Event -> None """ if not self.box_2.current_text: self.box_1.autocomplete(event) self.suggest_box_2() def box_2_autocomplete(self, event): """ Autocomplete box_2 and suggest box_1 match | tk.Event -> None """ if not self.box_1.current_text: self.box_2.autocomplete(event) self.suggest_box_1() def suggest_box_1(self): error_message = 'suggest_box_1 must be overridden by subclass' raise NotImplementedError(error_message) def suggest_box_2(self): error_message = 'suggest_box_2 must be overridden by subclass' raise NotImplementedError(error_message) def load_tutorial(self): """ NOT IMPLEMENTED """ pass def print_tracker_variables(self): print('Key variables:') print(f'Box_1 - current_text: {self.key.current_text}') print(f'Box_1 - suggestion_text: {self.key.suggestion_text}') print(f'Box_1 - suggestion_list: {self.key.suggestion_list}') print(f'Box_1 - current_cursor: {self.key.current_cursor}') print(f'Box_2 - current_text: {self.phrase.current_text}') print(f'Box_2 - suggestion_text: {self.phrase.suggestion_text}') print(f'Box_2 - suggestion_list: {self.phrase.suggestion_list}') print(f'Box_2 - current_cursor: {self.phrase.current_cursor}') def bind_event_handlers(self): """ Binds all event handlers for all widgets | None -> None """ #Copy and save bindings - active in all focus states self.master.bind('<Control-c>', lambda event: self.copy()) self.master.bind('<Command-c>', lambda event: self.copy()) self.master.bind('<Control-s>', lambda event: self.save_entry()) self.master.bind('<Command-s>', lambda event: self.save_entry()) self.master.bind('<Control-d>', lambda event: self.delete_entry()) self.master.bind('<Command-d>', lambda event: self.delete_entry()) self.master.bind('<Command-z>', lambda event: self.db.undo()) self.master.bind('<Control-z>', lambda event: self.db.undo()) self.master.bind('<Control-y>', lambda event: self.db.redo()) self.master.bind('<Command-y>', lambda event: self.db.redo()) self.master.bind('<Control-Shift-z>', lambda event: self.db.redo()) self.master.bind('<Command-Shift-z>', lambda event: self.db.redo()) self.master.bind('<Up>', lambda event: self.next_match()) self.master.bind('<Down>', lambda event: self.previous_match()) #Box_1 bindings - active when focus on Key entry widget self.box_1.bind('<Return>', self.block_new_line) self.box_1.bind('<Tab>', self.handle_box_1_tab) self.box_1.bind('<BackSpace>', self.handle_box_1_backspace) self.box_1.bind('<KeyRelease>', self.handle_box_1_key_release) self.box_1.bind('<ButtonRelease>', self.handle_box_1_button_release) #Box_2 bindings - active when focus on Phrase text widget if self.db_type == 'translation': self.box_2.bind('<Return>', self.block_new_line) self.box_2.bind('<Tab>', self.handle_box_2_tab) self.box_2.bind('<BackSpace>', self.handle_box_2_backspace) self.box_2.bind('<ButtonRelease>', self.handle_box_2_button_release) self.box_2.bind('<KeyRelease>', self.handle_box_2_key_release)