diff --git a/README.md b/README.md index ac74ba9a..7b67c3f4 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,6 @@ Currently in early alpha release, fuller features, proper install/packaging and Excitedly welcoming contributors! - ### Installation Currently to use the application, you must clone or download the project from here on github. See [releases](https://github.com/toonarmycaptain/dionysus/releases) for a `zip` or `tar.gz`. @@ -19,4 +18,5 @@ For development you will also need `pip install -r requirements_dev.txt`. Either click on `app_main.py` in the project folder, or navigate to the folder and run `python -m app_main` on the command line. #### Versioning -As much as possible this project will follow [Semantic Versioning](https://semver.org/). \ No newline at end of file +As much as possible this project will follow [Semantic Versioning](https://semver.org/). + \ No newline at end of file diff --git a/dionysus_app/UI_functions.py b/dionysus_app/UI_functions.py index 1806ca84..37fbe378 100644 --- a/dionysus_app/UI_functions.py +++ b/dionysus_app/UI_functions.py @@ -2,6 +2,9 @@ UI functions: user interface functions used throughout the application. """ +import tkinter as tk +from tkinter import filedialog + HUNDRED_NEWLINES = '\n'*100 @@ -64,3 +67,34 @@ def scrub_candidate_filename(dirty_string: str): if c.isalnum() or c in allowed_special_characters]).rstrip() return cleaned_string + + +def select_file_dialogue(title_str=None, filetypes=None): + """ + Prompts user to select a file. Calls tkinter filedialog.askopenfilename + with title (if provided), and filetype argument (if provided) eg '*.png'. + + filetypes is a list of tuples with 2 values, a label and a pattern + eg for png and all files: [('.png', '*.png'), ("all files", "*.*")] + + Returns None instead of empty string if no file is selected. + + :param title_str: str + :param filetypes: list + :return: str + """ + root = tk.Tk() + root.withdraw() + + default_filetypes = [("all files", "*.*")] + if not filetypes: + filetypes = default_filetypes + filename = filedialog.askopenfilename(title=title_str, filetype=filetypes) + + if filename == '': + return None + return filename + + +if __name__ == '__main__': + pass diff --git a/dionysus_app/chart_generator/take_chart_data.py b/dionysus_app/chart_generator/take_chart_data.py index 8625e3ff..9b9316c7 100644 --- a/dionysus_app/chart_generator/take_chart_data.py +++ b/dionysus_app/chart_generator/take_chart_data.py @@ -44,7 +44,8 @@ def take_score_data(class_name: str): student_score = take_score_entry(student_name) # add avatar to list of avatars for score - student_scores[student_score] = student_scores.get(student_score, []) + [avatar_path] + if student_score: + student_scores[student_score] = student_scores.get(student_score, []) + [avatar_path] print('\n') # Newline between entering last score and 'Please enter a chart name/title: ' diff --git a/dionysus_app/class_functions.py b/dionysus_app/class_functions.py index 54cac875..d3d7df77 100644 --- a/dionysus_app/class_functions.py +++ b/dionysus_app/class_functions.py @@ -9,8 +9,8 @@ from dionysus_app.class_registry_functions import classlist_exists, register_class from dionysus_app.data_folder import DataFolder, CLASSLIST_DATA_FILE_TYPE -from dionysus_app.file_functions import convert_to_json, load_from_json -from dionysus_app.UI_functions import clean_for_filename, input_is_essentially_blank +from dionysus_app.file_functions import convert_to_json, load_from_json, copy_file +from dionysus_app.UI_functions import clean_for_filename, input_is_essentially_blank, select_file_dialogue CLASSLIST_DATA_PATH = DataFolder.generate_rel_path(DataFolder.CLASS_DATA.value) @@ -83,16 +83,16 @@ def setup_class_data_storage(classlist_name): def create_classlist_data(class_name: str): - class_data = compose_classlist_dialogue() + class_data = compose_classlist_dialogue(class_name) class_data_feedback(class_name, class_data) write_classlist_to_file(class_name, class_data) time.sleep(2) # Pause for user to look over feedback. -def compose_classlist_dialogue(): +def compose_classlist_dialogue(class_name): while True: - class_data = take_class_data_input() + class_data = take_class_data_input(class_name) if not class_data: # Test for empty class. cancelled = blank_class_dialogue() @@ -105,7 +105,7 @@ def compose_classlist_dialogue(): return class_data -def take_class_data_input(): +def take_class_data_input(class_name): """ Take student names, avatars, return dictionary of data. @@ -116,7 +116,7 @@ def take_class_data_input(): student_name = take_student_name_input(class_data) if student_name.upper() == 'END': break - avatar_filename = take_student_avatar(student_name) + avatar_filename = take_student_avatar(class_name, student_name) class_data[student_name] = [avatar_filename] return class_data @@ -140,31 +140,53 @@ def take_student_name_input(class_data): return student_name -def take_student_avatar(student_name): +def take_student_avatar(class_name, student_name): """ Prompts user for path to avatar file. + :param class_name: str :param student_name: str :return: str or None """ - print(f'Load avatar image for {student_name}:') - while True: - avatar_file = input('Please paste complete filepath and name \n' - 'eg C:\\my_folder\\my_avatar.jpg or N/None to skip: ') - if avatar_file.upper() == 'NONE' or avatar_file.upper() == 'N': - return None - if avatar_file_exists(avatar_file): - break - # else: - print('Supplied filepath cannot be found.') + avatar_file = select_avatar_file_dialogue() if avatar_file is None: return None + cleaned_student_name = clean_for_filename(student_name) - avatar_filename = f'{cleaned_student_name}.png' + target_avatar_filename = f'{cleaned_student_name}.png' + # TODO: process_student_avatar() - # TODO: convert to png or whatever, copy image file to class_data avatar folder with student name as filename - return avatar_filename + # TODO: convert to png, copy image file to class_data avatar folder with student name as filename + copy_avatar_to_app_data(class_name, avatar_file, target_avatar_filename) + + return target_avatar_filename + + +def select_avatar_file_dialogue(): + """ + Prompts user to select an avatar file. Only displays PNG files. + :return: str or None + """ + dialogue_box_title = 'Select .png format avatar:' + filetypes = [('.png files', '*.png'), ("all files", "*.*")] + + filename = select_file_dialogue(dialogue_box_title, filetypes) + + return filename + + +def copy_avatar_to_app_data(classlist_name, avatar_filename, save_filename): + """ + Copies given avatar image to classlist_name/avatars/ with given save_filename. + + :param classlist_name: str + :param avatar_filename: str or Path + :param save_filename: str or Path + :return: None + """ + save_avatar_path = CLASSLIST_DATA_PATH.joinpath(classlist_name, 'avatars', save_filename) + copy_file(avatar_filename, save_avatar_path) def avatar_file_exists(avatar_file): diff --git a/dionysus_app/file_functions.py b/dionysus_app/file_functions.py index 178e81ee..e65d900d 100644 --- a/dionysus_app/file_functions.py +++ b/dionysus_app/file_functions.py @@ -5,6 +5,8 @@ import json +from shutil import copyfile + def convert_to_json(data_to_convert): """ @@ -28,4 +30,22 @@ def load_from_json(data_to_convert: str): return converted_data +def copy_file(origin_fullpath: str, destination_fullpath: str): + """ + Takes two filepaths, copying the origin file to the destination path and filename. + + Converts non-string object to string in case of Path object argument. + + + + :param origin_fullpath: str or Path + :param destination_fullpath: str or Path + :return: None + """ + origin_fullpath = str(origin_fullpath) + destination_fullpath = str(destination_fullpath) + + copyfile(origin_fullpath, destination_fullpath) + + # use Path.rename(new_name) to rename a class.