From c0bf6f9dd056669c04c02ac514b5a61adc780a32 Mon Sep 17 00:00:00 2001 From: 0xf0f <0x0meta@gmail.com> Date: Mon, 4 Mar 2019 04:14:15 +1300 Subject: [PATCH] Updated CloppyWindow and EditorWindow, removed testing files - Added a time limit option to CloppyWindow - Added a convenience function to CloppyWindow for creating Yes/No dialogs. --- project/functionality/constants.py | 7 +- project/testing/__init__.py | 0 project/testing/test_cloppy_window.py | 90 ------------------ project/testing/test_editor_window.py | 73 --------------- project/windows/cloppy_window.py | 69 +++++++++++++- project/windows/editor_window.py | 129 ++++++++++++-------------- 6 files changed, 131 insertions(+), 237 deletions(-) delete mode 100644 project/testing/__init__.py delete mode 100644 project/testing/test_cloppy_window.py delete mode 100644 project/testing/test_editor_window.py diff --git a/project/functionality/constants.py b/project/functionality/constants.py index 0f232948..5f41883d 100644 --- a/project/functionality/constants.py +++ b/project/functionality/constants.py @@ -1,5 +1,6 @@ from pathlib import Path + class Constants: """ This class is intended to house constant values used throughout the @@ -10,7 +11,7 @@ class Constants: resources_path = Path(__file__).parents[1]/'resources' # The name of the program. - program_name = '[Placeholder Name] Editor' + program_name = 'SMH Editor' # These values are used to determine whether a key press should count as # text input or not in EditorWindow.on_key_press. They're used to check @@ -24,7 +25,9 @@ class Constants: cloppy_picture_path = str(resources_path/'cloppy.png') # The path to the audio of Cloppy used in CloppyWindow - cloppy_sound_path = str(resources_path/'321947__n-audioman__horseneigh02-03.wav') + cloppy_sound_path = str( + resources_path/'321947__n-audioman__horseneigh02-03.wav' + ) # Cloppy's greeting to the user shown in CloppyWindow cloppy_greeting = ( diff --git a/project/testing/__init__.py b/project/testing/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/project/testing/test_cloppy_window.py b/project/testing/test_cloppy_window.py deleted file mode 100644 index 70b302c2..00000000 --- a/project/testing/test_cloppy_window.py +++ /dev/null @@ -1,90 +0,0 @@ -# This file is used to test the editor window in isolation. -# It will be removed during release. - -import tkinter as tk -from project.windows.editor_window import EditorWindow -from project.windows.cloppy_window import CloppyButtonWindow, CloppyTextInputWindow -from project.windows.editor_window_events import NewWordEventData -from project.windows.cloppy_window_events import CloppyChoiceMadeEventData - - -class TestEditorWindow(EditorWindow): - """ - A version of the editor window specifically made for testing. - """ - - def __init__(self, master): - super().__init__(master) - - # self.withdraw() - - self.text_box.tag_config('Underlined', underline=1) - self.previous_word: NewWordEventData = None - - def underline_new_word(event_data: NewWordEventData): - if self.previous_word: - self.text_box.tag_remove( - 'Underlined', - self.previous_word.start, self.previous_word.end - ) - - self.text_box.tag_add( - 'Underlined', event_data.start, event_data.end - ) - - self.previous_word = event_data - - self.new_word.add_callback(underline_new_word) - - self.text_box.bind('', self.on_left_click) - - def show_cloppy(event): - # print('test') - # new_window = CloppyButtonWindow(self) - # new_window.set_message('test') - # new_window.add_choice('test_choice') - # new_window.add_choice('test_choice 2') - # new_window.add_choice('test_choice 3') - # new_window.add_choice('test_choice 4') - - from project.functionality.constants import Constants - - new_window = CloppyTextInputWindow(self) - # new_window.set_message(Constants.cloppy_greeting) - - def show_choice(data: CloppyChoiceMadeEventData): - print(data.message, data.choice) - new_window.choice_made.add_callback(show_choice) - - new_window.show() - - # self.bind('', show_cloppy) - - def on_left_click(self, event: tk.Event): - start, end, word = self.get_word_under_mouse() - if word: - if self.previous_word: - self.text_box.tag_remove( - 'Underlined', - self.previous_word.start, self.previous_word.end - ) - - self.text_box.tag_add( - 'Underlined', start, end - ) - - self.previous_word = NewWordEventData(start, end, word) - - -if __name__ == '__main__': - root = tk.Tk() - - # Hide root window. - root.withdraw() - - editor_window = TestEditorWindow(root) - - # This will close the hidden root window when the editor window is closed. - editor_window.protocol('WM_DELETE_WINDOW', root.destroy) - - root.mainloop() diff --git a/project/testing/test_editor_window.py b/project/testing/test_editor_window.py deleted file mode 100644 index 6f9d6529..00000000 --- a/project/testing/test_editor_window.py +++ /dev/null @@ -1,73 +0,0 @@ -# This file is used to test the editor window in isolation. -# It will be removed during release. - -import tkinter as tk -from project.windows.editor_window import EditorWindow -from project.windows.editor_window_events import NewWordEventData - - -class TestEditorWindow(EditorWindow): - """ - A version of the editor window specifically made for testing. - """ - - def __init__(self, master): - super().__init__(master) - - self.text_box.tag_config('Underlined', underline=1) - self.previous_word: NewWordEventData = None - - def underline_new_word(event_data: NewWordEventData): - if self.previous_word: - self.text_box.tag_remove( - 'Underlined', - self.previous_word.start, self.previous_word.end - ) - - self.text_box.tag_add( - 'Underlined', event_data.start, event_data.end - ) - - self.previous_word = event_data - - self.new_word.add_callback(underline_new_word) - - self.bind('', self.on_left_click) - - def on_left_click(self, event: tk.Event): - start, end, word = self.get_word_under_mouse() - if word: - if self.previous_word: - self.text_box.tag_remove( - 'Underlined', - self.previous_word.start, self.previous_word.end - ) - - self.text_box.tag_add( - 'Underlined', start, end - ) - - # testing out showing menus at the location of a word. - x, y, width, height = self.text_box.bbox(start) - test_menu = tk.Menu(self, tearoff=False) - test_menu.add_command(label='test') - test_menu.tk_popup( - self.text_box.winfo_rootx()+x, - self.text_box.winfo_rooty()+y+height - ) - - self.previous_word = NewWordEventData(start, end, word) - - -if __name__ == '__main__': - root = tk.Tk() - - # Hide root window. - root.withdraw() - - editor_window = TestEditorWindow(root) - - # This will close the hidden root window when the editor window is closed. - editor_window.protocol('WM_DELETE_WINDOW', root.destroy) - - root.mainloop() diff --git a/project/windows/cloppy_window.py b/project/windows/cloppy_window.py index a034895f..e8642773 100644 --- a/project/windows/cloppy_window.py +++ b/project/windows/cloppy_window.py @@ -32,10 +32,6 @@ def __init__(self, master): self.resizable(False, False) # Configure grid properties so the elements inside stretch out. - tk.Grid.rowconfigure(self, index=0, weight=1) - tk.Grid.rowconfigure(self, index=1, weight=1) - tk.Grid.rowconfigure(self, index=2, weight=1) - tk.Grid.rowconfigure(self, index=3, weight=1) tk.Grid.columnconfigure(self, index=0, weight=1) # The message to be displayed to the user. @@ -78,6 +74,10 @@ def __init__(self, master): self.input_frame.grid(row=3, column=0, sticky=tk.NSEW) tk.Grid.columnconfigure(self.input_frame, index=0, weight=1) + # Setting up the optional time limit value. + self.time_remaining = None + self.time_remaining_label: tk.Label = None + def set_message(self, message: str): """ Set the message to be displayed to the user in this dialog. @@ -87,6 +87,42 @@ def set_message(self, message: str): self.message = message self.message_label.config(text=message) + def update_time_remaining(self): + """ + Called to update the text inside the time limit label. + """ + self.time_remaining_label.config( + text=f'Time remaining: {self.time_remaining} seconds.' + ) + + def countdown_cycle(self): + """ + Called for each cycle of the time limit countdown. + """ + if self.time_remaining > 0: + self.time_remaining -= 1 + self.update_time_remaining() + self.after(1000, self.countdown_cycle) + else: + self.time_out() + + def set_time_limit(self, time_limit): + """ + Adds a time limit to this dialog. + :param time_limit: Time limit in seconds. + """ + if not self.time_remaining: + self.time_remaining = time_limit + self.time_remaining_label = tk.Label(self) + self.time_remaining_label.grid(row=4, column=0, sticky=tk.NSEW) + self.update_time_remaining() + + def time_out(self): + """ + Called when the user doesn't make a choice and the time limit runs out. + """ + self.destroy() + def make_choice(self, choice): """ Called in order to register that a choice has been made in this dialog. @@ -133,6 +169,9 @@ def show(self): playsound(Constants.cloppy_sound_path, block=False) + if self.time_remaining: + self.after(1000, self.countdown_cycle) + class CloppyButtonWindow(CloppyWindow): """ @@ -270,3 +309,25 @@ def show(self): # Direct focus to the input box. self.input_box.focus_set() + + +def cloppy_yesno(master, message, callback) -> CloppyButtonWindow: + """ + Convenience function for constructing a basic yes/no Cloppy dialog. + + :param master: The master widget of the dialog. + :param message: Message to display in the dialog. + :param callback: The callback for when a choice is made in this dialog. + """ + + dialog = CloppyButtonWindow(master) + dialog.set_message( + f'{message}\n' + "So it behooves me to ask:\n" + "Are you sure you want to do that?" + ) + dialog.add_choice('Yes') + dialog.add_choice('No') + dialog.choice_made.add_callback(callback) + + return dialog diff --git a/project/windows/editor_window.py b/project/windows/editor_window.py index a0bb4266..684e2f18 100644 --- a/project/windows/editor_window.py +++ b/project/windows/editor_window.py @@ -3,14 +3,13 @@ import tkinter as tk from tkinter import filedialog -from tkinter import messagebox from project.functionality.constants import Constants from project.functionality.utility import pairwise from project.windows.editor_window_events import NewWordEvent -from project.windows.cloppy_window import CloppyButtonWindow +from project.windows.cloppy_window import cloppy_yesno, CloppyButtonWindow from project.windows.cloppy_window_events import CloppyChoiceMadeEventData @@ -338,22 +337,17 @@ def on_backspace(self, event): :return: 'break' in order to interrupt the normal event handling of the backspace key. """ - dialog = CloppyButtonWindow(self) - dialog.set_message( - "It looks like you're trying to erase some text.\n" - "The text you're erasing could be very important.\n" - "So it behooves me to ask:\n" - "Are you sure you want to do that?" - ) - dialog.add_choice('Yes') - dialog.add_choice('No') def delete_text(choice_data: CloppyChoiceMadeEventData): if choice_data.choice == 'Yes': self.delete_selected_text() - dialog.choice_made.add_callback(delete_text) - dialog.show() + cloppy_yesno( + self, + "It looks like you're trying to erase some text.\n" + "The text you're erasing could be very important.", + delete_text + ).show() return 'break' @@ -367,15 +361,6 @@ def on_control_x(self, event=None): :return: 'break' in order to interrupt the normal event handling of the backspace key. """ - dialog = CloppyButtonWindow(self) - dialog.set_message( - "It looks like you're trying to cut some text.\n" - "The text you're cutting could be very important.\n" - "So it behooves me to ask:\n" - "Are you sure you want to do that?" - ) - dialog.add_choice('Yes') - dialog.add_choice('No') def cut_text(choice_data: CloppyChoiceMadeEventData): if choice_data.choice == 'Yes': @@ -386,8 +371,12 @@ def cut_text(choice_data: CloppyChoiceMadeEventData): ) self.set_selected_text('') - dialog.choice_made.add_callback(cut_text) - dialog.show() + cloppy_yesno( + self, + "It looks like you're trying to cut some text.\n" + "The text you're erasing could be very important.", + cut_text + ).show() return 'break' @@ -401,9 +390,20 @@ def on_control_c(self, event=None): :return: 'break' in order to interrupt the normal event handling of the backspace key. """ - root: tk.Tk = self.master - root.clipboard_clear() - root.clipboard_append(self.get_selected_text()) + + def copy_text(choice_data: CloppyChoiceMadeEventData): + if choice_data.choice == 'Yes': + root: tk.Tk = self.master + root.clipboard_clear() + root.clipboard_append(self.get_selected_text()) + + cloppy_yesno( + self, + "It looks like you're trying to copy some text.\n" + "You might not have highlighted the correct section to copy.\n" + "Perhaps it'd be a good idea to double check now.", + copy_text + ).show() return 'break' @@ -417,15 +417,25 @@ def on_control_v(self, event=None): :return: 'break' in order to interrupt the normal event handling of ctrl+v. """ - root: tk.Tk = self.master - try: - self.set_selected_text( - root.clipboard_get(), - set_selected=False - ) - except tk.TclError: - pass + def paste_text(choice_data: CloppyChoiceMadeEventData): + if choice_data.choice == 'Yes': + root: tk.Tk = self.master + try: + self.set_selected_text( + root.clipboard_get(), + set_selected=False + ) + + except tk.TclError: + pass + + cloppy_yesno( + self, + "It looks like you're trying to paste some text.\n" + "The text you're pasting could be replacing other important text.", + paste_text + ).show() return 'break' @@ -467,33 +477,28 @@ def on_control_o(self, event=None): """ # Cloppy asks the user whether they want to open a file. - dialog = CloppyButtonWindow(self) - dialog.set_message( - "It looks like you're trying to open a file.\n" - "If you do you'll lose any unsaved work in the current file.\n" - "So it behooves me to ask:\n" - "Are you sure you want to do that?" - ) - dialog.add_choice('Yes') - dialog.add_choice('No') def open_file(choice_data: CloppyChoiceMadeEventData): if choice_data.choice == 'Yes': - # Brings up a dialog asking the user to select a location for saving - # the file. + # Brings up a dialog asking the user to select a location for + # saving the file. file = filedialog.askopenfile( filetypes=(('Text Files', '*.txt'), ('All Files', '*.*')) ) # Check to see if the user cancelled the dialog or not. if file: - # 'with' is used so that the file is automatically flushed/closed - # after our work is done with it. + # 'with' is used so that the file is automatically flushed + # /closed after our work is done with it. with file: self.set_text(file.read()) - dialog.choice_made.add_callback(open_file) - dialog.show() + cloppy_yesno( + self, + "It looks like you're trying to open a file.\n" + "If you do you'll lose any unsaved work in the current file.", + open_file + ).show() return 'break' @@ -509,22 +514,16 @@ def on_control_n(self, event=None): """ # Cloppy asks the user whether they want to create a new file. - dialog = CloppyButtonWindow(self) - dialog.set_message( - "It looks like you're trying to create a new file.\n" - "If you do you'll lose any unsaved work in the current file.\n" - "So it behooves me to ask:\n" - "Are you sure you want to do that?" - ) - dialog.add_choice('Yes') - dialog.add_choice('No') - def new_file(choice_data: CloppyChoiceMadeEventData): if choice_data.choice == 'Yes': self.set_text('') - dialog.choice_made.add_callback(new_file) - dialog.show() + cloppy_yesno( + self, + "It looks like you're trying to create a new file.\n" + "If you do you'll lose any unsaved work in the current file.", + new_file + ).show() return 'break' @@ -708,12 +707,6 @@ def on_about(self): Called when the 'About' action is selected from the Help menu. """ - # messagebox.showinfo( - # 'About', - # f'{Constants.program_name}\n' - # f'By LargeKnome, Hanyuone, and Meta\n' - # ) - dialog = CloppyButtonWindow(self.master.editor_window) dialog.set_message( f'This program was made by:\n'