diff --git a/.gitignore b/.gitignore index 6ddeb29..d727651 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,5 @@ gui/__pycache__/ pyspim/\.DS_Store \.DS_Store + +pyspim/__pycache__/ diff --git a/README.md b/README.md index fbb924c..9dd353c 100644 --- a/README.md +++ b/README.md @@ -2,4 +2,19 @@ We are working to create an educational program that will be used to help students learn how to program in MIPS code. We are using the pyspim code base to run spim through python. -ZenHub: https://app.zenhub.com/workspaces/smip-5c5dd5323547567638dbb48a/boards?repos=169785349 \ No newline at end of file +# OS REQUIREMENTS: +-Linux +-MacOS +(Windows unsupported at this time). + +# IDE REQUIREMENTS: +Pycharm Community 2018 (Highly Recommended unless serious CLI experience): +https://www.jetbrains.com/pycharm/ +Python Interpreter 3.5+ + +# INSTRUCTIONS: +- Open forked repo in IDE. +- Run Main.py found in /gui/Main.py. + +# ASSOCIATED SCRUMBOARD: +ZenHub: https://app.zenhub.com/workspaces/smip-5c5dd5323547567638dbb48a/boards?repos=169785349 diff --git a/gui/Drawer.py b/gui/Drawer.py index b49ae68..6eee136 100644 --- a/gui/Drawer.py +++ b/gui/Drawer.py @@ -1,15 +1,23 @@ import tkinter as tk -from tkinter import font -from tkinter import messagebox +from tkinter import font, messagebox, Menu +from lessons.Lesson_Transition import get_next_lesson, get_previous_lesson, append_new_lesson, set_current_lesson_index from gui.ReferenceWindow import draw_reference -from gui.LessonPage import submit_code -from gui.Utilities import transfer_to +from gui.LessonPage import submit_code, get_text +from gui.Utilities import transfer_to, get_relative_file_path, get_path +from lessons.Lesson_Workbook import initialize_workbook +from lessons.Lesson_Transition import get_current_lesson +from .ReferenceWindow import Reference +from lessons.Lesson_Transition import lessons +from string import digits SIDEBAR_COLUMN_WIDTH = 5 registers = [] -def draw_menu(root, ttk, next_lesson): +def draw_menu(root, ttk): + # Resize in case window has been adjusted + if root.winfo_width() > 700: + root.minsize(700, root.winfo_screenheight()) # Set fonts for the menu widgets. # print(font.families()) to print available font families. menuLabel_font = font.Font(family="Loma", size=24, weight="bold") @@ -29,28 +37,47 @@ def draw_menu(root, ttk, next_lesson): label_banner = ttk.Label(main_frame, text='\tWelcome to SMIP.\n Your Best Friend for Learning MIPS ', style='green/black.TLabel', width=700, anchor="center") label_plug = ttk.Label(main_frame, style='textBox.TLabel', text=' Our repo: https://github.com/coreyrop/SMIP\n\t' - '-Last Updated: 02/20/2019-') + '-Last Updated: 03/26/2019-') label_banner.pack(pady=10) label_plug.pack(side="bottom", pady=5) + def lesson_validation(): + if lessons: + transfer_to( + lambda: draw_lesson(root, ttk, get_current_lesson()), + main_frame) + pass + button1 = ttk.Button(main_frame, text='Start', style='green/black.TButton', - command=lambda: transfer_to( - lambda: draw_lesson(root, ttk, next_lesson, submit_code, messagebox.showinfo), main_frame)) - button2 = ttk.Button(main_frame, text='Select Lesson', style='green/black.TButton') - button3 = ttk.Button(main_frame, text='Practice', style='green/black.TButton') - button4 = ttk.Button(main_frame, text='Reference', style='green/black.TButton', command=draw_reference) + command=lesson_validation) + + button2 = ttk.Button(main_frame, text='Select Lesson', style='green/black.TButton', + command=lambda: transfer_to(lambda: draw_lesson_select(root, ttk), main_frame)) + button3 = ttk.Button(main_frame, text='Practice', style='green/black.TButton', command=lambda: transfer_to( + lambda: draw_practice(root, ttk), main_frame)) + button4 = ttk.Button(main_frame, text='Reference', style='green/black.TButton', + command=lambda: draw_reference(Reference.local_file.value, + '/References/MIPS_Green_Sheet.pdf')) + create_lesson_button = ttk.Button(main_frame, text='Create Lesson', style='green/black.TButton', + command=lambda: transfer_to(lambda: draw_create_lessons_form(root, ttk), + main_frame)) button5 = ttk.Button(main_frame, text='Exit', style='green/black.TButton', command=quit) button1.pack(pady=30) button2.pack(pady=30) button3.pack(pady=30) button4.pack(pady=30) + create_lesson_button.pack(pady=30) button5.pack(pady=30) pass -def draw_lesson(root, ttk, lesson, submit_function, hint_function): +def draw_lesson(root, ttk, lesson): + # Resize page. + if root.winfo_width() < 875: + root.minsize(875, root.winfo_screenheight()) + # Set fonts for the menu widgets. # print(font.families()) to print available font families. menuLabel_font = font.Font(family="Loma", size=22, weight="bold") @@ -61,42 +88,72 @@ def draw_lesson(root, ttk, lesson, submit_function, hint_function): lesson_header = tk.Frame(master=root, bg="medium blue") center_frame = tk.Frame(master=root, bg="medium blue") - bottom_frame_top = tk.Frame(master=root, bg="medium blue") - bottom_frame_bottom = tk.Frame(master=root, bg="medium blue") - register_frame = tk.Frame(root, width=200, bg='white', height=500, relief='sunken', borderwidth=2) + register_frame = tk.Frame(root, width=200, bg='medium blue', height=500, relief='sunken', borderwidth=2) + + # TODO Need to properly gridify this page. + # This is a temp fix for functionality. + tk.Grid.columnconfigure(lesson_header, 0, weight=1) + tk.Grid.rowconfigure(lesson_header, 0, weight=1) + tk.Grid.columnconfigure(center_frame, 0, weight=1) + tk.Grid.rowconfigure(center_frame, 1, weight=1) registers = [] draw_sidebar(register_frame, registers) # Pack lesson_header Frame over the top of the center_frame. - register_frame.pack(expand=True, fill='both', side='left') - lesson_header.pack(fill="x") - center_frame.pack(expand=True, fill="both") - bottom_frame_top.pack(expand=True, fill="both") - bottom_frame_bottom.pack(expand=True, fill="both", side="bottom") + register_frame.grid(row=0, column=1, columnspan=1, sticky='w') + lesson_header.grid(row=0, column=0) + center_frame.grid(row=1, column=0, columnspan=2) - label_instruction = ttk.Label(center_frame, text=lesson.lesson_prompt, style='B_DO1.TLabel') - lesson_input = ttk.Entry(master=center_frame, font=menuLabel_font) - lesson_input = tk.Text(center_frame, height=30, width=100) - lesson_input.insert(tk.END, lesson.code_base) + label_instruction = ttk.Label(lesson_header, text=lesson.lesson_prompt, style='B_DO1.TLabel') + lesson_input = tk.Text(lesson_header, height=30, width=100) + lesson_input.insert(tk.END, get_text(lesson)) - label_instruction.pack(side="top", pady=5) - lesson_input.pack(pady=20, padx=10) + label_instruction.grid(pady=5, sticky='n') + lesson_input.grid(pady=20, padx=10) - menu_escape = ttk.Button(bottom_frame_top, text='Main Menu', style='B_DO1.TButton', cursor="target", - command=lambda: transfer_to(lambda: draw_menu(root, ttk, lesson), center_frame, - bottom_frame_top, bottom_frame_bottom, register_frame)) - hint_button = ttk.Button(bottom_frame_bottom, text='Hint', style='B_DO1.TButton', - cursor="target", command=lambda: hint_function("Hint", lesson.lesson_hint)) - reference_button = ttk.Button(bottom_frame_bottom, text='Reference', style='B_DO1.TButton', - cursor="target", command=draw_reference) - submit_button = ttk.Button(bottom_frame_top, text='Submit Code', style='B_DO1.TButton', - cursor="target", command=lambda: submit_function(lesson_input, registers, lesson)) + hints = ''.join([str(i + 1) + '. ' + lesson.lesson_hint[i] + '\n\n' for i in range(len(lesson.lesson_hint))]) - menu_escape.pack(side='left', padx=10) - submit_button.pack(side='right', padx=10) - hint_button.pack(side='left', padx=10) - reference_button.pack(side='right', padx=10) + menu_escape = ttk.Button(center_frame, text='Main Menu', style='B_DO1.TButton', cursor="target", + command=lambda: transfer_to(lambda: draw_menu(root, ttk), center_frame, + register_frame)) + hint_button = ttk.Button(center_frame, text='Hint', style='B_DO1.TButton', + cursor="target", command=lambda: messagebox.showinfo("Hint", hints)) + reference_button = ttk.Button(center_frame, text='Reference', style='B_DO1.TButton', + cursor="target") + + popup_reference = Menu(root, tearoff=0, bg='#f27446', font=20) + + for reference in lesson.lesson_reference: + popup_reference.add_command(label=reference['Name'], + command=lambda r=reference: draw_reference(r['Type'], r['Path'])) + + def do_popup_ref(event): + # display the popup menu + popup_reference.tk_popup(event.x_root, event.y_root, 0) + + reference_button.bind("", do_popup_ref) + + submit_button = ttk.Button(center_frame, text='Submit Code', style='B_DO1.TButton', + cursor="target", command=lambda: submit_code(lesson_input, registers, lesson)) + previous_lesson_button = ttk.Button(center_frame, text='Previous Lesson', style='B_DO1.TButton', + cursor='target', command=lambda: transfer_to(lambda: draw_lesson(root, ttk, + get_previous_lesson()), + center_frame, + register_frame)) + + next_lesson_button = ttk.Button(center_frame, text='Next Lesson', style='B_DO1.TButton', + cursor='target', command=lambda: transfer_to(lambda: draw_lesson(root, ttk, + get_next_lesson()), + center_frame, + register_frame)) + + menu_escape.grid(row=4, column=0, padx=20, pady=20) + submit_button.grid(row=4, column=2, padx=20) + previous_lesson_button.grid(row=5, column=0, padx=20, pady=10) + next_lesson_button.grid(row=5, column=2, padx=20) + hint_button.grid(row=4, column=1, padx=20, pady=20) + reference_button.grid(row=5, column=1, padx=20) pass @@ -106,7 +163,7 @@ def draw_sidebar(sidebar, registers): tk.Label(sidebar, text="VALUE", width=SIDEBAR_COLUMN_WIDTH).grid(row=0, column=2) for i in range(32): - label = tk.Label(sidebar, text="0", width="5") + label = tk.Label(sidebar, text="undef", width="5") label.grid(row=i + 1, column=2) registers.append(label) # global arr so labels can be updated @@ -145,3 +202,426 @@ def draw_sidebar(sidebar, registers): for i in range(32): tk.Label(sidebar, text=i, width=SIDEBAR_COLUMN_WIDTH).grid(row=i + 1, column=1) pass + + +def draw_create_lessons_form(root, ttk): + # Need extra room because we have 3 rows of info. + root.minsize(900, root.winfo_screenheight()) + # Cover the whole screen with the frame. + main_frame = tk.Frame(root, bg='medium blue', width=root.winfo_width(), height=root.winfo_height()) + # Fill the frame with the background. + main_frame.pack(expand=True, fill="both") + # Include separate font choices for human readable text. + menuButton_font = font.Font(family="Loma", size=22, weight="bold") + create_field_font = font.Font(family="Loma", size=20, weight="normal") + create_button_font = font.Font(family="Loma", size=18, weight="bold") + register_label_font = font.Font(family="Latin Modern Roman", size=14, weight="bold") + register_entry_font = font.Font(family="Latin Modern Roman", size=14, weight="normal") + # Apply style settings. + ttk.Style().configure('B_DO1.TLabel', foreground='black', background='DarkOrange1', width=20, + font=create_field_font, anchor="CENTER") + ttk.Style().configure('B_DO1.TButton', foreground='black', background='DarkOrange1', font=create_button_font, + width=15) + ttk.Style().configure('menu_buttons.TButton', foreground='black', background='DarkOrange1', font=menuButton_font, + width=15, padx=5) + ttk.Style().configure('references.TLabel', foreground='black', background='DarkOrange1', width=12, + font=create_field_font, anchor="CENTER") + ttk.Style().configure('TMenuButton', background='DarkOrange1') + + # Define register fields. + register_fields = {i: {} for i in range(32)} + + references = [] + + # Make an "added reference dropdown" which reflects added references. + str_var = tk.StringVar(root) + included_references = ['None'] + str_var.set('None') + reference_menu = tk.OptionMenu(main_frame, str_var, *included_references) + + lesson_title_label = ttk.Label(main_frame, text='Lesson Title', style='B_DO1.TLabel') + lesson_prompt_label = ttk.Label(main_frame, text='Lesson Prompt', style='B_DO1.TLabel') + lesson_hint_label = ttk.Label(main_frame, text='Lesson Hint', style='B_DO1.TLabel') + lesson_filepath_label = ttk.Label(main_frame, text='Relative File Path', style='B_DO1.TLabel') + reference_menu_label = ttk.Label(main_frame, text='References', style='references.TLabel') + + for i in register_fields.keys(): + register_fields[i]['label'] = ttk.Label(main_frame, text='$r' + str(i) + ' ', font=register_label_font) + + lesson_title_label.grid(row=0, column=0, pady=10, padx=20) + lesson_prompt_label.grid(row=1, column=0, pady=10) + lesson_hint_label.grid(row=2, column=0, pady=10) + lesson_filepath_label.grid(row=3, column=0, pady=20) + reference_menu_label.grid(row=0, column=2) + reference_menu.grid(row=1, column=2) + + for i in register_fields.keys(): + if i < 16: + if i > 9: + new_text = register_fields[i]['label'].cget("text") + register_fields[i]['label'] = ttk.Label(main_frame, text=new_text[:4], font=register_label_font) + register_fields[i]['label'].grid(row=i + 4, column=1, sticky='w', padx=10) + else: + new_text = register_fields[i]['label'].cget("text") + register_fields[i]['label'] = ttk.Label(main_frame, text=new_text[:4], font=register_label_font) + register_fields[i]['label'].grid(row=i - 16 + 4, column=1, stick='e', padx=3) + + lesson_title_entry = ttk.Entry(main_frame, font=create_button_font) + + #TODO MYG + # My Solution for prompt size + MAX_ENTRY_SIZE = 20 + entry_text = tk.StringVar() + lesson_prompt_entry = ttk.Entry(main_frame, font=create_button_font, width=20, textvariable=entry_text) + entry_text.set(lesson_prompt_entry.get()[:MAX_ENTRY_SIZE]) + + # Source of implemented solution: + # https://stackoverflow.com/questions/16373887/how-to-set-the-text-value-content-of-an-entry-widget-using-a-button-in-tkinter + + lesson_hint_entry = ttk.Entry(main_frame, font=create_button_font) + lesson_filepath_current_label = ttk.Label(main_frame, text='None Set', font=create_field_font, width=9, + anchor='center') + for i in register_fields.keys(): + register_fields[i]['entry'] = ttk.Entry(main_frame, font=register_entry_font) + + lesson_title_entry.grid(row=0, column=1) + lesson_prompt_entry.grid(row=1, column=1) + lesson_hint_entry.grid(row=2, column=1) + lesson_filepath_current_label.grid(row=3, column=1) + for i in register_fields.keys(): + if i < 16: + register_fields[i]['entry'].grid(row=i + 4, column=0, sticky='e', padx=3) + else: + register_fields[i]['entry'].grid(row=i - 16 + 4, column=2, padx=3) + + lesson_filepath_button = ttk.Button(main_frame, text='Select', cursor='target', style='B_DO1.TButton', + command=lambda: lesson_filepath_current_label.config( + text=get_relative_file_path([('MIPS code files', '*.s')]))) + lesson_filepath_button.grid(row=3, column=2) + + main_menu_button = ttk.Button(main_frame, text='Main Menu', cursor='target', style='menu_buttons.TButton', + command=lambda: transfer_to(lambda: draw_menu(root, ttk), main_frame)) + + def submit_confirmation(): + + #TODO: MYG + # Truncate entry + # truncate_entry_size(lesson_prompt_entry, 50) + + # Perform validation. + # if not validate_entry_size(lesson_prompt_entry.get(), 50): + # alert = tk.Label(main_frame, background='red2', text='Invalid Values', font=menuButton_font) + + # Other options: + # Option A: StringVar() + # v = StringVar() + # e = Entry(master, textvariable=v) + # e.pack() + # Option B: Use validate attribute OR Trigger validation alert when prompt text passes width. + # https://stackoverflow.com/questions/18741078/ttk-entry-widget-validate-entry-invalid-text-entry-does-not-cause-reverting + + if bool(lesson_title_entry.get() and not lesson_title_entry.get().isspace()) and bool( + lesson_prompt_entry.get() and not lesson_prompt_entry.get().isspace()) and bool( + lesson_hint_entry.get() and not lesson_hint_entry.get().isspace()) and all( + [all(c in digits for c in register_fields[i]['entry'].get() if not (c is register_fields[i]['entry'].get()[0] and (c == '-' or c == '+'))) for i in register_fields.keys() if + not register_fields[i]['entry'].get().isspace() and register_fields[i]['entry'].get()]): + + append_new_lesson(initialize_workbook('/lesson_files/' + lesson_title_entry.get(), + lesson_title=lesson_title_entry.get(), + lesson_prompt=lesson_prompt_entry.get(), + lesson_hint=lesson_hint_entry.get(), + lesson_filepath=lesson_filepath_current_label + ['text'], + registers={ + j: register_fields[j]['entry'].get() for j in + register_fields.keys()}, references=references)) + + lesson_title_entry.delete(0, 'end') + lesson_prompt_entry.delete(0, 'end') + lesson_hint_entry.delete(0, 'end') + lesson_filepath_current_label.config(text='None Set') + + included_references.clear() + reference_menu['menu'].delete(0, 'end') + reference_menu['menu'].add_command(label='None', command=lambda: str_var.set('None')) + + for i in register_fields.keys(): + register_fields[i]['entry'].delete(0, 'end') + + # Make a lesson created user alert here, then grid forget. + # NOTICE: It is forgotten, NOT destroyed. + alert = tk.Label(main_frame, background='green2', text='Lesson Created!', font=menuButton_font) + else: + alert = tk.Label(main_frame, background='red2', text='Invalid Values', font=menuButton_font) + + alert.grid(row=40, column=1) + # Root must be updated for changes to be displayed. + root.update() + # Modest sleep time of about 3 seconds, for user to take notice. + alert.after(500, alert.grid_forget) + pass + + # This part is just to make the button string more accessible. + # It might be deleted later. + create_lesson_str = tk.StringVar() + create_lesson_str.set('Create Lesson') + submit_lesson_button = ttk.Button(main_frame, text=create_lesson_str.get(), cursor='target', + style='menu_buttons.TButton', + command=submit_confirmation) + + def submit_ref(ref, dict, win): + ref.append(dict) + included_references.append(dict['Name']) + reference_menu['menu'].add_command(label=dict['Name'], command=lambda value=dict['Name']: str_var.set(value)) + win.destroy() + pass + + def add_pdf_reference_form(references): + + win = tk.Toplevel() + win.wm_title("Add PDF Reference") + + name_label = tk.Label(win, text="Reference Name") + name_label.grid(row=0, column=0) + + name_entry = tk.Entry(win, font=create_field_font) + name_entry.grid(row=0, column=1) + + current_path_label = tk.Label(win, text='None Set', font=create_field_font) + select_file_button = ttk.Button(win, text="Select PDF", command=lambda: current_path_label.config( + text=get_relative_file_path([('PDF files', '*.pdf')]))) + select_file_button.grid(row=1, column=0) + current_path_label.grid(row=1, column=1) + + submit_button = tk.Button(win, text='Add Reference', command=lambda: submit_ref(references, + {'Name': name_entry.get(), + 'Type': 'local_file', + 'Path': current_path_label[ + 'text']}, win)) + submit_button.grid(row=2, column=0) + pass + + def add_link_reference_form(references): + + win = tk.Toplevel() + win.wm_title("Add Link Reference") + + name_label = tk.Label(win, text="Reference Name") + name_label.grid(row=0, column=0) + + name_entry = tk.Entry(win, font=create_field_font) + name_entry.grid(row=0, column=1) + + url_label = tk.Label(win, text='URL') + url_entry = tk.Entry(win, font=create_field_font) + url_label.grid(row=1, column=0) + url_entry.grid(row=1, column=1) + + submit_button = tk.Button(win, text='Add Reference', + command=lambda: submit_ref(references, {'Name': name_entry.get(), 'Type': 'web_link', + 'Path': url_entry.get()}, + win)) + submit_button.grid(row=2, column=0) + pass + + popup = Menu(root, tearoff=0, bg='#f27446', font=20) + popup.add_command(label='PDF', command=lambda: add_pdf_reference_form(references)) + popup.add_command(label='Link', command=lambda: add_link_reference_form(references)) + + reference_menu_button = ttk.Button(main_frame, text='Add a Reference', cursor='target', + style='menu_buttons.TButton') + reference_menu_button.bind("", lambda event: popup.tk_popup(event.x_root, event.y_root, 0)) + + reference_menu_button.grid(row=2, column=2, padx=10) + main_menu_button.grid(row=40, column=0, sticky='s', pady=15) + submit_lesson_button.grid(row=40, column=1, sticky='s', pady=15) + pass + + #TODO MYG + # def truncate_entry_size(entry, size): + # """ + # Takes a TTK Entry object and truncates it. + # :param entry: A Entry object. + # :param size: Number of characters to limit + # :return: The truncated Entry object. + # """ + # str_var = tk.StringVar() + # text = entry.get() + # if len(entry.get()) > size: + # entry.set(text[:size]) + # return entry + + # def validate_entry_size(entry, size): + # """ + # Takes a TTK Entry object and returns if it is the valid size. + # :param entry: A Entry object. + # :param size: Valid number of characters. + # :return: Returns bool value of whether the Entry text is valid. + # """ + # value = entry.get() + # return len(value) <= size + + +def draw_practice(root, ttk): + # Set fonts for the menu widgets. + # print(font.families()) to print available font families. + + menuLabel_font = font.Font(family="Loma", size=22, weight="bold") + menuButton_font = font.Font(family="Loma", size=20, weight="normal") + # background="..." doesn't work... + ttk.Style().configure('B_DO1.TLabel', foreground='black', background='DarkOrange1', font=menuLabel_font) + ttk.Style().configure('B_DO1.TButton', foreground='black', background='DarkOrange1', font=menuButton_font, width=15) + + lesson_header = tk.Frame(master=root, bg="medium blue") + center_frame = tk.Frame(master=root, bg="medium blue") + bottom_frame_top = tk.Frame(master=root, bg="medium blue") + bottom_frame_bottom = tk.Frame(master=root, bg="medium blue") + register_frame = tk.Frame(root, width=200, bg='white', height=500, relief='sunken', borderwidth=2) + + registers = [] + draw_sidebar(register_frame, registers) + + # Pack lesson_header Frame over the top of the center_frame. + register_frame.pack(expand=True, fill='both', side='left') + lesson_header.pack(fill="x") + center_frame.pack(expand=True, fill="both") + bottom_frame_top.pack(expand=True, fill="both") + bottom_frame_bottom.pack(expand=True, fill="both", side="bottom") + + label_instruction = ttk.Label(center_frame, text="Time to Practice Some MIPS ", style='B_DO1.TLabel') + lesson_input = tk.Text(center_frame, height=30, width=100) + lesson_input.insert(tk.END, get_text(is_practice=True)) + + label_instruction.pack(side="top", pady=5) + lesson_input.pack(pady=20, padx=10) + lesson = "" + + menu_escape = ttk.Button(bottom_frame_top, text='Main Menu', style='B_DO1.TButton', cursor="target", + command=lambda: transfer_to(lambda: draw_menu(root, ttk), center_frame, + bottom_frame_top, bottom_frame_bottom, register_frame)) + + reference_button = ttk.Button(bottom_frame_bottom, text='Reference', style='B_DO1.TButton', + cursor="target", command=draw_reference) + run_button = ttk.Button(bottom_frame_top, text='Run Code', style='B_DO1.TButton', + cursor="target", command=lambda: submit_code(lesson_input, registers, is_practice=True)) + menu_escape.pack(side='left', padx=10) + run_button.pack(side='right', padx=10) + reference_button.pack(padx=10) + pass + + +def draw_lesson_select(root, ttk): + main_frame = tk.Frame(root, bg='medium blue', width=root.winfo_width(), height=root.winfo_height()) + main_frame.pack(expand=True, fill="both") + + scframe = VerticalScrolledFrame(main_frame) + scframe.pack() + + def lesson_selected(i): + set_current_lesson_index(i) + transfer_to(lambda: draw_lesson(root, ttk, lessons[i]), main_frame) + pass + + for i in range(len(lessons)): + color = lambda i=i: 'green' if lessons[i].lesson_completed else 'red' + btn = tk.Button(scframe.interior, height=1, width=20, relief=tk.FLAT, + bg="gray99", fg=color(i), + font="Dosis", text=lessons[i].lesson_title, command=lambda i=i: lesson_selected(i)) + + btn.pack(padx=10, pady=5, side=tk.TOP) + main_menu_button = ttk.Button(main_frame, text='Main Menu', cursor='target', style='menu_buttons.TButton', + command=lambda: transfer_to(lambda: draw_menu(root, ttk), main_frame)) + main_menu_button.pack() + pass + + +# tkinter hates scrollbars with buttons so I grabbed this solution from stackoverflow +# https://stackoverflow.com/questions/31762698/dynamic-button-with-scrollbar-in-tkinter-python +class VerticalScrolledFrame(tk.Frame): + """A pure Tkinter scrollable frame that actually works! + + * Use the 'interior' attribute to place widgets inside the scrollable frame + * Construct and pack/place/grid normally + * This frame only allows vertical scrolling + """ + + def __init__(self, parent, *args, **kw): + tk.Frame.__init__(self, parent, *args, **kw) + + # create a canvas object and a vertical scrollbar for scrolling it + vscrollbar = tk.Scrollbar(self, orient=tk.VERTICAL) + vscrollbar.pack(fill=tk.Y, side=tk.RIGHT, expand=tk.FALSE) + canvas = tk.Canvas(self, bd=0, highlightthickness=0, + yscrollcommand=vscrollbar.set) + canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=tk.TRUE) + vscrollbar.config(command=canvas.yview) + + # reset the view + canvas.xview_moveto(0) + canvas.yview_moveto(0) + + # create a frame inside the canvas which will be scrolled with it + self.interior = interior = tk.Frame(canvas) + interior_id = canvas.create_window(0, 0, window=interior, + anchor=tk.NW) + + # track changes to the canvas and frame width and sync them, + # also updating the scrollbar + def _configure_interior(event): + # update the scrollbars to match the size of the inner frame + size = (interior.winfo_reqwidth(), interior.winfo_reqheight()) + canvas.config(scrollregion="0 0 %s %s" % size) + if interior.winfo_reqwidth() != canvas.winfo_width(): + # update the canvas's width to fit the inner frame + canvas.config(width=interior.winfo_reqwidth()) + + interior.bind('', _configure_interior) + + def _configure_canvas(event): + if interior.winfo_reqwidth() != canvas.winfo_width(): + # update the inner frame's width to fill the canvas + canvas.itemconfigure(interior_id, width=canvas.winfo_width()) + + canvas.bind('', _configure_canvas) + + +#TODO: MYG +# class ValidatingEntry(tk.Entry): +# # base class for validating entry widgets +# +# def __init__(self, master, value="", **kw): +# apply(Entry.__init__, (self, master), kw) +# self.__value = value +# self.__variable = tk.StringVar() +# self.__variable.set(value) +# self.__variable.trace("w", self.__callback) +# self.config(textvariable=self.__variable) +# +# def __callback(self, *dummy): +# value = self.__variable.get() +# newvalue = self.validate(value) +# if newvalue is None: +# self.__variable.set(self.__value) +# elif newvalue != value: +# self.__value = newvalue +# self.__variable.set(self.newvalue) +# else: +# self.__value = value +# +# def validate(self, value): +# # override: return value, new value, or None if invalid +# return value +# +# class MaxLengthEntry(ValidatingEntry): +# +# def __init__(self, master, value="", maxlength=None, **kw): +# self.maxlength = maxlength +# apply(ValidatingEntry.__init__, (self, master), kw) +# +# def validate(self, value): +# if self.maxlength is None or len(value) <= self.maxlength: +# return value +# return None # new value too long +# Useful References: +# https://stackoverflow.com/questions/4140437/interactively-validating-entry-widget-content-in-tkinter +# https://mail.python.org/pipermail/tkinter-discuss/2006-August/000863.html diff --git a/gui/LessonPage.py b/gui/LessonPage.py index a1c1833..9957430 100644 --- a/gui/LessonPage.py +++ b/gui/LessonPage.py @@ -1,5 +1,7 @@ import tkinter as tk from lessons.Submission import run_MIPS +from lessons.Lesson_Transition import write_completed +from gui.Utilities import get_path """ Draws a lesson to the frame root: tkinter root to draw to @@ -7,27 +9,54 @@ lesson: the lesson to be drawn to the screen """ +practice_filename = get_path('/lesson_files/Submissions/(Practice).s') -def submit_code(user_input, register_labels, lesson): - f = open('input.s', 'w') + +def get_submission_file(lesson): + return get_path('/lesson_files/Submissions/'+lesson.lesson_title + '(Submission).s') + + +def submit_code(user_input, register_labels, lesson=None, is_practice=False): + if is_practice: + filename = practice_filename + else: + filename = get_submission_file(lesson) + + f = open(filename, 'w') f.write(user_input.get("1.0", tk.END)) f.close() - results = run_MIPS('input.s') + results = run_MIPS(filename) update_registers(results, register_labels) - if lesson.check_solution(results): - print('Passed!!') - else: - print('Failed!!') + if not is_practice: + if lesson.check_solution(results): + lesson.lesson_completed = True + write_completed(lesson.lesson_title, True) + print('Passed!!') + else: + lesson.lesson_completed = False + write_completed(lesson.lesson_title, False) + print('Failed!!') pass -def get_text(): - f = open('Sample1.s', 'r') - output = "" - for x in f.readlines(): - output += x +def get_text(lesson=None, is_practice=False): + if is_practice: + try: + f = open(practice_filename) + except FileNotFoundError: + return '# Welcome to Practice! Write some code!!' + else: + try: + filename = get_submission_file(lesson) + f = open(filename, 'r') + except FileNotFoundError: + try: + f = open(get_path(lesson.code_base), 'r') + except FileNotFoundError: + return "# No base code" + output = ''.join(line for line in f.readlines()) f.close() return output @@ -36,5 +65,3 @@ def update_registers(answers, register_labels): for register, value in answers.items(): register_labels[register].config(text=value) pass - - diff --git a/gui/Main.py b/gui/Main.py index abad396..0fa9656 100644 --- a/gui/Main.py +++ b/gui/Main.py @@ -1,28 +1,24 @@ import tkinter as tk from tkinter import ttk from gui.Drawer import draw_menu -from gui.LessonPage import get_text -from lessons.Lesson import Lesson -root = tk.Tk() -root.title("SMIP: The Student MIPS Instruction Program") +if __name__ == '__main__': + root = tk.Tk() + root.title("SMIP: The Student MIPS Instruction Program") + # Turning off pack propagate to prevent widgets from determining window size. + # Max and Min window sizes may change. + root.pack_propagate(0) + # Max size of window is the dimensions of their screen. + their_width = root.winfo_screenwidth() + their_height = root.winfo_screenheight() + root.maxsize(their_width, their_height) + # Buttons need room, so we need max height. + root.minsize(700, their_height) + # Put app at the center of the screen. + x = (their_width / 2) - (700 / 2) + y = (their_height / 2) - (800 / 2) + root.geometry("%dx%d+%d+%d" % (700, 800, x, y)) + root.configure(background='medium blue') + draw_menu(root, ttk) -sample_lesson = Lesson('Lesson 1: Register Addition'," Use the \'add\' instruction to set\n $t0 to 3,$t1 to 5,\n and then their sum in $t2.", - {8: 3, 9: 5, 10: 8}, " The add instruction expects a destination register followed by two source registers," - " which are comma separated.", get_text()) - -# Turning off pack propagate to prevent widgets from determining window size. -# Max and Min window sizes may change. -root.pack_propagate(0) -# Max size of window is the dimensions of their screen. -their_width = root.winfo_screenwidth() -their_height = root.winfo_screenheight() -root.maxsize(their_width, their_height) -root.minsize(700, 800) -# Put app at the center of the screen. -x = (their_width / 2) - (700 / 2) -y = (their_height / 2) - (800 / 2) -root.geometry("%dx%d+%d+%d" % (700, 800, x, y)) - -draw_menu(root, ttk, sample_lesson) -root.mainloop() + root.mainloop() diff --git a/gui/ReferenceWindow.py b/gui/ReferenceWindow.py index f1d3cc2..029980e 100644 --- a/gui/ReferenceWindow.py +++ b/gui/ReferenceWindow.py @@ -1,13 +1,14 @@ -import os, sys, subprocess +import os, sys, subprocess, webbrowser, enum +from gui.Utilities import get_path -def draw_reference(): - dirname = os.path.dirname(__file__) +class Reference(enum.Enum): + web_link = 'web_link' + local_file = 'local_file' - #Removing gui folder from the absolute path - dirname = dirname[:-3] - - filename = os.path.join(dirname,'References','MIPS_Green_Sheet.pdf') + +def draw_reference_path(file_path): + filename = get_path(file_path) if sys.platform == "win32": os.startfile(filename) @@ -15,4 +16,16 @@ def draw_reference(): opener = "open" if sys.platform == "darwin" else "xdg-open" subprocess.call([opener, filename]) - pass \ No newline at end of file + pass + + +def draw_reference_link(file_link): + webbrowser.open(file_link) + + +def draw_reference(type, path): + if Reference[type].value == Reference.local_file.value: + draw_reference_path(file_path = path) + elif Reference[type].value == Reference.web_link.value: + draw_reference_link(file_link = path) + diff --git a/gui/Utilities.py b/gui/Utilities.py index 6c30ee7..f01c0eb 100644 --- a/gui/Utilities.py +++ b/gui/Utilities.py @@ -1,3 +1,11 @@ +from os.path import relpath +from tkinter.filedialog import askopenfilename +from pathlib import Path + + +top_path = str(Path().absolute())[:str(Path().absolute()).rfind('SMIP')+len('SMIP')] + + """ Destroys all content inside of the given frame frame: frame who's content will be destroyed @@ -10,6 +18,16 @@ def destroy_content(frame): pass +def get_relative_file_path(filetypes): + try: + s = askopenfilename(filetypes=filetypes) + return '/'+relpath(s, top_path) + except ValueError: + return 'None Set' + + +def get_path(relpath): + return top_path + relpath """ Destroys the given frames content and then draws new content to that frame using the given function diff --git a/lesson_files/Another One.xlsx b/lesson_files/Another One.xlsx new file mode 100644 index 0000000..e1fc3d0 Binary files /dev/null and b/lesson_files/Another One.xlsx differ diff --git a/lesson_files/Another Sample.xlsx b/lesson_files/Another Sample.xlsx new file mode 100644 index 0000000..bd9cfbb Binary files /dev/null and b/lesson_files/Another Sample.xlsx differ diff --git a/gui/Sample1.s b/lesson_files/Base_code/Sample1.s similarity index 100% rename from gui/Sample1.s rename to lesson_files/Base_code/Sample1.s diff --git a/lesson_files/Sample Lesson.xlsx b/lesson_files/Sample Lesson.xlsx new file mode 100644 index 0000000..bda5eca Binary files /dev/null and b/lesson_files/Sample Lesson.xlsx differ diff --git a/lesson_files/Submissions/(Practice).s b/lesson_files/Submissions/(Practice).s new file mode 100644 index 0000000..c123b17 --- /dev/null +++ b/lesson_files/Submissions/(Practice).s @@ -0,0 +1,58 @@ +# Welcome to Practice! Write some code!! +.globl main +.globl init_values +.globl end_program +.globl print_value + +# $t0 = $s5 +# $s5 += $t0 +# $s5 += $t0 +# $s5 += $t0 +# $s5 += $t0 +# $s5 += $t0 +# +# $s4 = $s5 + $s3 +# $s4 -= $s2 + +.text +init_values: + addi $s4, $0, 4 + addi $s5, $0, 4 + addi $s3, $0, 4 + addi $s2, $0, 4 + jr $ra + add $0, $0, $0 + +main: + jal init_values + add $0, $0, $0 + + add $t0, $s5, $0 + add $s5, $s5, $t0 + add $s5, $s5, $t0 + add $s5, $s5, $t0 + add $s5, $s5, $t0 + add $s5, $s5, $t0 + + add $s4, $s5, $s3 + + sub $s4, $s4, $s2 + + jal print_value + add $0, $0, $0 + j end_program + add $0, $0, $0 + +print_value: + addi $v0, $0, 1 + add $a0, $s4, $0 + syscall + jr $ra + add $0, $0, $0 + +end_program: + addi $v0, $0, 10 + syscall + + + diff --git a/gui/input.s b/lesson_files/Submissions/Another Sample(Submission).s similarity index 88% rename from gui/input.s rename to lesson_files/Submissions/Another Sample(Submission).s index df48157..c0f3644 100644 --- a/gui/input.s +++ b/lesson_files/Submissions/Another Sample(Submission).s @@ -12,11 +12,12 @@ main: # your code here - addi $t0, $0, 3 - addi $t1, $0, 5 - add $t4, $t0, $t1 - + addi $s0, $0, 0 # All memory structures are placed after the # .data assembler directive .data + + + + diff --git a/lesson_files/Submissions/Sample Lesson(Submission).s b/lesson_files/Submissions/Sample Lesson(Submission).s new file mode 100644 index 0000000..7155507 --- /dev/null +++ b/lesson_files/Submissions/Sample Lesson(Submission).s @@ -0,0 +1,23 @@ +# "Hello World" in MIPS assembly +# From: http://labs.cs.upt.ro/labs/so2/html/resources/nachos-doc/mipsf.html + + # All program code is placed after the + # .text assembler directive + .text + + # Declare main as a global function + .globl main + +# The label 'main' represents the starting point +main: + + # your code here + addi $a3, $0, 5 + + # All memory structures are placed after the + # .data assembler directive + .data + + + + diff --git a/lesson_files/Submissions/profile.pickle b/lesson_files/Submissions/profile.pickle new file mode 100644 index 0000000..5ca5614 Binary files /dev/null and b/lesson_files/Submissions/profile.pickle differ diff --git a/lessons/Lesson.py b/lessons/Lesson.py index beab7df..d6212f6 100644 --- a/lessons/Lesson.py +++ b/lessons/Lesson.py @@ -7,12 +7,14 @@ class Lesson: answer: Dict that stores the necessary registers as keys and the correct final values as their value mapping """ - def __init__(self, title, prompt, answer, hint, base_code): + def __init__(self, title, prompt, answer, hint, reference, base_code, completed): self.lesson_prompt = prompt self.lesson_hint = hint + self.lesson_reference = reference self.lesson_title = title self.lesson_answer = answer self.code_base = base_code + self.lesson_completed = completed pass """ diff --git a/lessons/Lesson_Transition.py b/lessons/Lesson_Transition.py new file mode 100644 index 0000000..79082c2 --- /dev/null +++ b/lessons/Lesson_Transition.py @@ -0,0 +1,70 @@ +import pickle +lesson_index = 0 +lessons = [] +filename = '../lesson_files/Submissions/profile.pickle' + + +def set_current_lesson_index(i): + global lesson_index + if i > -1: + lesson_index = i + pass + + +def get_current_lesson(): + if lessons: + return lessons[lesson_index] + pass + + +def get_next_lesson(): + if lessons: + global lesson_index + if lesson_index + 1 < len(lessons): + lesson_index += 1 + return lessons[lesson_index] + else: + return lessons[lesson_index] + + +def get_previous_lesson(): + global lesson_index + if lesson_index - 1 > -1: + lesson_index -= 1 + return lessons[lesson_index] + else: + return lessons[lesson_index] + + +def append_new_lesson(new_lesson): + lessons.append(new_lesson) + pass + + +def read_from_pickle(filename): + dict = {} + try: + with open(filename, 'rb') as file: + dict = pickle.load(file) + except FileNotFoundError: + return dict + return dict + + +dict = read_from_pickle(filename) + + +def write_completed(title, bool): + dict[title] = bool + with open(filename, 'wb') as file: + pickle.dump(dict, file) + pass + + +def init_lessons(): + from lessons.Lesson_Workbook import load_lessons + global lessons + lessons = load_lessons() + + +init_lessons() \ No newline at end of file diff --git a/lessons/Lesson_Workbook.py b/lessons/Lesson_Workbook.py new file mode 100644 index 0000000..935da9f --- /dev/null +++ b/lessons/Lesson_Workbook.py @@ -0,0 +1,124 @@ +from openpyxl import Workbook, load_workbook +from openpyxl.styles import PatternFill, Font +from .Lesson_Transition import dict +from .Lesson import Lesson +from gui.Utilities import get_path +import os +from string import digits + + +def get_register_index(register_num, get_value_index=True): + if get_value_index: + if register_num < 16: + return 'D' + str(register_num+1) + else: + return 'F' + str(register_num+1 - 16) + else: + if register_num < 16: + return 'C' + str(register_num+1) + else: + return 'E' + str(register_num+1 - 16) + + +def initialize_workbook(filename, **kwargs): + filename = get_path(filename) + book = Workbook() + sheet = book.active + + sheet.column_dimensions['A'].width = 50 + sheet.column_dimensions['B'].width = 50 + sheet.column_dimensions['G'].width = 50 + sheet.column_dimensions['H'].width = 50 + sheet.column_dimensions['I'].width = 50 + + greenFill = PatternFill(start_color='00FF00', end_color='00FF00', fill_type='solid') + cyanFill = PatternFill(start_color='00FFFF', end_color='00FFFF', fill_type='solid') + labelFont = Font(name='Calibri', size=14, bold=True) + valueFont = Font(name='Calibri', size=14) + + sheet['A1'] = 'Lesson Title' + sheet['A2'] = 'Lesson Prompt' + sheet['A3'] = 'Lesson Hint' + sheet['A4'] = 'Base Code Relative File Path' + sheet['B1'] = kwargs['lesson_title'] + sheet['B2'] = kwargs['lesson_prompt'] + sheet['B3'] = kwargs['lesson_hint'] + sheet['B4'] = kwargs['lesson_filepath'] + + sheet['A1'].fill = greenFill + sheet['A2'].fill = greenFill + sheet['A3'].fill = greenFill + sheet['A4'].fill = greenFill + sheet['A1'].font = labelFont + sheet['A2'].font = labelFont + sheet['A3'].font = labelFont + sheet['A4'].font = labelFont + + sheet['B1'].fill = cyanFill + sheet['B2'].fill = cyanFill + sheet['B3'].fill = cyanFill + sheet['B4'].fill = cyanFill + sheet['B1'].font = valueFont + sheet['B2'].font = valueFont + sheet['B3'].font = valueFont + sheet['B4'].font = valueFont + + for i in range(32): + sheet[get_register_index(i, False)] = '$r'+str(i) + sheet[get_register_index(i, False)].fill = greenFill + sheet[get_register_index(i, False)].font = labelFont + + sheet[get_register_index(i)] = kwargs['registers'][i] + sheet[get_register_index(i)].fill = cyanFill + sheet[get_register_index(i)].font = valueFont + + sheet['G1'] = 'Reference Name' + sheet['H1'] = 'Reference Type' + sheet['I1'] = 'Reference Path' + + sheet['G1'].fill = greenFill + sheet['H1'].fill = greenFill + sheet['I1'].fill = greenFill + sheet['G1'].font = labelFont + sheet['H1'].font = labelFont + sheet['I1'].font = labelFont + + index = 2 + for reference in kwargs.get('references', []): + sheet['G'+str(index)] = reference['Name'] + sheet['H'+str(index)] = reference['Type'] + sheet['I'+str(index)] = reference['Path'] + if reference['Type'] == 'web_link': + sheet['I'+str(index)].style = 'Hyperlink' + + sheet['G' + str(index)].fill = cyanFill + sheet['H' + str(index)].fill = cyanFill + sheet['I' + str(index)].fill = cyanFill + sheet['G' + str(index)].font = valueFont + sheet['H' + str(index)].font = valueFont + sheet['I' + str(index)].font = valueFont + index += 1 + + book.save(filename+'.xlsx') + return load_lesson_from_workbook(filename+'.xlsx') + + +def load_lesson_from_workbook(filename): + book = load_workbook(filename) + sheet = book.active + answer = {i: int(sheet[get_register_index(i)].value) for i in range(32) if + sheet[get_register_index(i)].value is not None and all(c in digits for c in sheet[ + get_register_index(i)].value if not(c is sheet[get_register_index(i)].value[0] and(c == '-' or c == '+')))} + + references = [] + index = 2 + while sheet['G'+str(index)].value is not None: + references.append({'Name': sheet['G'+str(index)].value, 'Type': sheet['H'+str(index)].value, 'Path': sheet['I'+str(index)].value}) + index += 1 + return Lesson(sheet['B1'].value, sheet['B2'].value, answer, [sheet['B3'].value], references, sheet['B4'].value, dict.get(sheet['B1'].value, False)) + + +def load_lessons(): + lesson_path = get_path('/lesson_files/') + return [load_lesson_from_workbook(lesson_path + file) for file in os.listdir(lesson_path) if file.endswith('.xlsx')] + diff --git a/lessons/Submission.py b/lessons/Submission.py index 6b8bce8..1a64f44 100644 --- a/lessons/Submission.py +++ b/lessons/Submission.py @@ -1,10 +1,8 @@ from pyspim.pyspim import Spim -sp = Spim(debug = False) - def run_MIPS(filename): + sp = Spim(debug=False) sp.load(filename) sp.run() - - return {i: sp.reg(i) for i in range(32)} \ No newline at end of file + return {i: sp.reg(i) for i in range(32)} diff --git a/pyspim/__pycache__/pyspim.cpython-35.pyc b/pyspim/__pycache__/pyspim.cpython-35.pyc deleted file mode 100644 index a75992b..0000000 Binary files a/pyspim/__pycache__/pyspim.cpython-35.pyc and /dev/null differ diff --git a/pyspim/__pycache__/pyspim.cpython-36.pyc b/pyspim/__pycache__/pyspim.cpython-36.pyc deleted file mode 100644 index a2a1a03..0000000 Binary files a/pyspim/__pycache__/pyspim.cpython-36.pyc and /dev/null differ diff --git a/test/gui/.cache/v/cache/lastfailed b/test/gui/.cache/v/cache/lastfailed new file mode 100644 index 0000000..35f86d2 --- /dev/null +++ b/test/gui/.cache/v/cache/lastfailed @@ -0,0 +1,3 @@ +{ + "Drawer.py::TransitionMenuLessonTestCase::runTest": true +} \ No newline at end of file diff --git a/test/gui/Drawer.py b/test/gui/Drawer.py new file mode 100644 index 0000000..057c831 --- /dev/null +++ b/test/gui/Drawer.py @@ -0,0 +1,109 @@ +# Templates for GUI tests found at the following resource: +# http://cs.smith.edu/~jfrankli/220s05/bookEx/qt3/ch9/pyunit-1.3.0/doc/PyUnit.html#GUI +# +# Test Makers: gsingh37, cprowesl. + + +import unittest as ut +import tkinter as tk +from tkinter import font, ttk +from gui.Drawer import draw_menu, draw_lesson, draw_create_lessons_form +from lessons.Lesson_Transition import get_current_lesson +from gui.Utilities import transfer_to + + +class BasicLabelTestCase(ut.TestCase): + def setUp(self): + self.label = tk.Label(text='Test Label.') + + +class LabelExistsTestCase(BasicLabelTestCase): + def runTest(self): + assert self.label is not None, 'Label does not exist.' + return print("Label Exists Test : Pass") + + +class LabelTextConfigurableTestCase(BasicLabelTestCase): + def runTest(self): + self.label.configure(text='Test Text.') + assert self.label.cget('text') == 'Test Text.', 'Label failed to configure text variable.' + return print('Label Text Configurable: Pass') + + +class ButtonTest(ut.TestCase): + def setUp(self): + self.button = tk.Button(text="Button") + + +class BasicButtonTest(ButtonTest): + def runTest(self): + assert self.button is not None, "Button Existence Test: Fail" + return print("Button Existence Test: Pass") + + +class FrameTest(ut.TestCase): + def setUp(self): + self.frame = tk.Frame() + + +class BasicFrameTest(FrameTest): + def runTest(self): + assert self.frame is not None, "Frame Existence Test: Fail" + return print("Frame Existence Test: Pass") + + +class TextTest(ut.TestCase): + def setUp(self): + self.text = tk.Text() + + +class BasicTextTest(TextTest): + def runTest(self): + assert self.text is not None, "TextBox Existence Test: Fail" + return print("TextBox Existence Test: Pass") + + +class GridTest(ut.TestCase): + def setUp(self): + self.grid = tk.Grid() + + +class BasicGridTest(GridTest): + def runTest(self): + assert self.grid is not None, "Grid Existence Test: Fail" + return print("Grid Existence Test: Pass") + + +class StyleTest(ut.TestCase): + def setUp(self): + self.style = ttk.Style() + + +class BasicStyleTest(StyleTest): + def runTest(self): + assert self.style is not None, "Style GUI Test: Fail" + return print("Style GUI Test: Pass") + + +class BasicTransitionTestCase(ut.TestCase): + def setUp(self): + self.frame = tk.Tk() + self.ttk = ttk + draw_menu(self.frame, self.ttk) + + +class TransitionMenuLessonTestCase(BasicTransitionTestCase): + def runTest(self): + print(self.frame.winfo_width()) + assert self.frame.winfo_width() <= 700, 'Main Menu width check failed.' + transfer_to(lambda: draw_lesson(self.frame, self.ttk, get_current_lesson()), *self.frame.children.values()) + assert self.frame.winfo_width() <= 875, 'Lesson width check failed.' + assert self.frame.winfo_children() is not [], 'Drawn Lesson failed with no children.' + transfer_to(lambda: draw_menu(self.frame, self.ttk), *self.frame.children.values()) + assert self.frame.winfo_width() <= 700, 'Main Menu width failed after transfer from Lesson.' + return print('Transition Menu Lesson Test: Pass.') + + +if __name__ == '__main__': + ut.main() + diff --git a/test/lessons/.cache/v/cache/lastfailed b/test/lessons/.cache/v/cache/lastfailed new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/test/lessons/.cache/v/cache/lastfailed @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/test/lessons/Lesson_Transition.py b/test/lessons/Lesson_Transition.py new file mode 100644 index 0000000..982aed9 --- /dev/null +++ b/test/lessons/Lesson_Transition.py @@ -0,0 +1,55 @@ +import unittest as ut +from lessons.Lesson_Transition import lesson_index, get_next_lesson, get_previous_lesson, set_current_lesson_index, lessons, get_current_lesson +from lessons.Lesson_Workbook import initialize_workbook, load_lessons + + +class TestGetCurrLesson1(ut.TestCase): + def runTest(self): + set_current_lesson_index(0) + lesson = get_current_lesson() + self.assertEqual(lesson, lessons[0]) + + +class TestGetCurrLesson2(ut.TestCase): + def runTest(self): + set_current_lesson_index(len(lessons)-1) + lesson = get_current_lesson() + self.assertEqual(lesson, lessons[-1]) + + +class TestGetNextLesson1(ut.TestCase): + def runTest(self): + set_current_lesson_index(0) + next = get_next_lesson() + set_current_lesson_index(1) + lesson = get_current_lesson() + self.assertEqual(lesson, next) + + +class TestGetNextLesson2(ut.TestCase): + def runTest(self): + set_current_lesson_index(len(lessons)-1) + next = get_next_lesson() + lesson = get_current_lesson() + self.assertEqual(lesson, next) + + +class TestGetPrevLesson1(ut.TestCase): + def runTest(self): + set_current_lesson_index(0) + prev = get_previous_lesson() + lesson = get_current_lesson() + self.assertEqual(lesson, prev) + + +class TestGetPrevLesson2(ut.TestCase): + def runTest(self): + set_current_lesson_index(len(lessons)-1) + prev = get_previous_lesson() + set_current_lesson_index(len(lessons)-2) + lesson = get_current_lesson() + self.assertEqual(lesson, prev) + + +if __name__ == '__main__': + ut.main() diff --git a/test/lessons/Lesson_Workbook.py b/test/lessons/Lesson_Workbook.py new file mode 100644 index 0000000..5f94fc7 --- /dev/null +++ b/test/lessons/Lesson_Workbook.py @@ -0,0 +1,34 @@ +import unittest as ut +import lessons.Lesson_Transition +from lessons.Lesson_Workbook import initialize_workbook, load_lessons +from string import ascii_letters, digits +from random import choice + + +class TestLoadLessons(ut.TestCase): + def runTest(self): + printable = ascii_letters + digits + ' ' + + def comp_lessons(actual, expected): + return actual.lesson_title == expected.lesson_title and actual.lesson_prompt == expected.lesson_prompt and actual.lesson_hint == expected.lesson_hint and actual.code_base == expected.code_base and actual.lesson_reference == expected.lesson_reference + + test_lessons = [initialize_workbook(filename='/lesson_files/(Testing)0' + str(_), + lesson_title=''.join(choice(printable) for _ in range(50)), + lesson_prompt=''.join(choice(printable) for _ in range(50)), + lesson_hint=''.join(choice(printable) for _ in range(50)), + lesson_filepath=''.join(choice(printable) for _ in range(50)), + registers={j: ''.join(choice(digits) for _ in range(10)) for j in + range(32)}, references=[]) for _ in range(5)] + + d = {lesson.lesson_title: lesson for lesson in load_lessons()} + for i in range(len(test_lessons)): + self.assertTrue(comp_lessons(d[test_lessons[i].lesson_title], test_lessons[i])) + + from os import remove + from gui.Utilities import get_path + for i in range(len(test_lessons)): + remove(get_path('/lesson_files/(Testing)0' + str(i)) + '.xlsx') + + +if __name__ == '__main__': + ut.main()