def test_disordered_test(self):
     garden = Garden("VCRRGVRG\nRVGCCGCV",
                     students="Samantha Patricia Xander Roger".split())
     self.assertEqual("Violets Clover Radishes Violets".split(),
                      garden.plants("Patricia"))
     self.assertEqual("Radishes Grass Clover Violets".split(),
                      garden.plants("Xander"))
 def test_disordered_test(self):
     garden = Garden("VCRRGVRG\nRVGCCGCV",
                     students="Samantha Patricia Xander Roger".split())
     self.assertEqual("Violets Clover Radishes Violets".split(),
                      garden.plants("Patricia"))
     self.assertEqual("Radishes Grass Clover Violets".split(),
                      garden.plants("Xander"))
 def test_full_garden(self):
     garden = Garden("VRCGVVRVCGGCCGVRGCVCGCGV\nVRCCCGCRRGVCGCRVVCVGCGCV")
     self.assertEqual("Violets Radishes Violets Radishes".split(),
                      garden.plants("Alice"))
     self.assertEqual("Clover Grass Clover Clover".split(),
                      garden.plants("Bob"))
     self.assertEqual("Grass Clover Clover Grass".split(),
                      garden.plants("Kincaid"))
     self.assertEqual("Grass Violets Clover Violets".split(),
                      garden.plants("Larry"))
 def xtest_full_garden(self):
     garden = Garden("VRCGVVRVCGGCCGVRGCVCGCGV\nVRCCCGCRRGVCGCRVVCVGCGCV")
     self.assertEqual("Violets Radishes Violets Radishes".split(),
                      garden.plants("Alice"))
     self.assertEqual("Clover Grass Clover Clover".split(),
                      garden.plants("Bob"))
     self.assertEqual("Grass Clover Clover Grass".split(),
                      garden.plants("Kincaid"))
     self.assertEqual("Grass Violets Clover Violets".split(),
                      garden.plants("Larry"))
Beispiel #5
0
def run_event_loop(logger, gardens, garden, window):
    """
    Display and interact with the main window and subwindows using an event loop.

    Create, select, display, update, and remove gardens 
    and the creatures, plants, and tasks they contain.
    Save the gardens dict as gardens.pickle.
    If a fatal exception occurs, log it in gardenlife.log.
    """

    # Keeps track of whether any changes have been made since the gardens dict was saved
    gardens_changed = False

    try:
        while True:
            event, values = window.read()
            # print(event, values)

            ##################### Menu & Window Closure Events #####################

            # See if user wants to quit or attempted to close the window
            if event in ("Exit", sg.WINDOW_CLOSE_ATTEMPTED_EVENT):

                if gardens_changed:
                    subwindows.unsaved_changes_window(window, gardens)
                else:
                    break

            elif event == "Save":
                with open("gardens.pickle", "wb") as file:
                    pickle.dump(gardens, file)
                gardens_changed = False

            elif event == "About...":
                popups.about()

            elif event == "Open web tutorial":
                webbrowser.open("https://github.com/jonboland/gardenlife/blob/master/README.rst")

            ################ Creature, Plant, & Task Summary Events ################

            elif event == "VIEW ALL CREATURES":
                subwindows.view_creatures_window(window, garden)

            elif event == "VIEW ALL PLANTS":
                subwindows.view_plants_window(window, garden)

            elif event == "VIEW EDIBLE PLANTS":
                subwindows.view_plants_window(window, garden, attr="edible", title="Edible ")

            elif event == "VIEW ALL TASKS":
                subwindows.view_tasks_window(window, garden)

            ######################### Manage Garden Events #########################

            elif event == "GARDEN CREATE/UPDATE":
                # Validate garden name and ownership info
                g_name = values["-GARDEN NAME-"].strip()
                g_owners = values["-OWNER NAMES-"].strip()
                g_since = values["-OWNED SINCE-"].strip()
                if not g_name:
                    popups.invalid_name("garden name")
                    continue
                if not g_owners:
                    popups.invalid_name("owner names")
                    continue
                try:
                    event_funcs.check_date_validity(g_since)
                except ValueError:
                    popups.invalid_date(field="owned since", date=g_since)
                # If there are no validation errors, create/update the garden
                else:
                    cu_garden = Garden(
                        g_name, values["-LOCATION-"], values["-SIZE-"], g_since, g_owners.split()
                    )
                    # If garden already exists add all existing items to the updated version
                    garden_instance = gardens.get(g_name)
                    if garden_instance:
                        cu_garden.creatures = garden_instance.creatures
                        cu_garden.plants = garden_instance.plants
                        cu_garden.tasks = garden_instance.tasks
                    # Add created/updated garden to gardens dict. Overwrite if already exists
                    gardens[g_name] = cu_garden
                    # Update dropdowns and clear field values and links
                    event_funcs.update_garden_dropdown(window, gardens)
                    event_funcs.clear_all_item_dropdowns(window)
                    event_funcs.clear_all_values_and_links(window, garden)
                    gardens_changed = True

            elif event == "GARDEN REMOVE":
                if not values["-SELECT GARDEN-"]:
                    continue
                g_confirmation = popups.remove_confirmation(garden.name, "garden")
                if g_confirmation == "OK":
                    del gardens[values["-GARDEN NAME-"]]
                    event_funcs.update_garden_dropdown(window, gardens)
                    event_funcs.clear_all_item_dropdowns(window)
                    event_funcs.clear_all_values_and_links(window, garden)
                    gardens_changed = True

            elif event == "-SELECT GARDEN-":  # A garden is selected from the dropdown
                # Load the selected garden
                garden = gardens[values["-SELECT GARDEN-"]]
                # If the default garden (blank row) is selected, clear fields
                if not values["-SELECT GARDEN-"]:
                    event_funcs.clear_garden_values(window)
                    event_funcs.clear_summary_values(window)
                # Otherwise, update the fields with the selected garden's values
                else:
                    window["-GARDEN NAME-"].update(garden.name)
                    window["-LOCATION-"].update(garden.location)
                    window["-SIZE-"].update(garden.size)
                    window["-OWNER NAMES-"].update(" ".join(garden.owners))
                    window["-OWNED SINCE-"].update(garden.since)
                    # And update the associated summary fields
                    window["-SUMMARY GARDEN NAME-"].update(garden.name)
                    window["-SUMMARY LOCATION-"].update(garden.location)
                    window["-SUMMARY SIZE-"].update(garden.garden_size())
                    window["-SUMMARY OWNED BY-"].update(garden.ownership())
                    window["-SUMMARY OWNED FOR-"].update(garden.ownership_length())
                    window["-SUMMARY TOTAL CREATURES-"].update(len(garden.creatures))
                    window["-SUMMARY TOTAL PLANTS-"].update(len(garden.plants))
                    event_funcs.update_task_summaries(window, garden)
                # Then update the item dropdowns and clear item field values and links
                event_funcs.update_all_item_dropdowns(window, garden)
                event_funcs.clear_all_item_values_and_links(window, garden)

            ####################### Manage Creatures Events ########################

            elif event == "CREATURE CREATE/UPDATE":
                # Validate garden name, creature name and appeared date
                c_name = values["-CREATURE NAME-"].strip()
                c_appeared = values["-CREATURE APPEARED DATE-"].strip()
                # Check that a garden has been selected
                if not values["-SELECT GARDEN-"]:
                    popups.garden_not_selected("creature")
                    continue
                if not c_name:
                    popups.invalid_name("creature name")
                    continue
                try:
                    if c_appeared:
                        event_funcs.check_date_validity(c_appeared)
                except ValueError:
                    popups.invalid_date(field="appeared date", date=c_appeared)

                else:
                    creature = Creature(
                        name=c_name,
                        org_type=values["-CREATURE TYPE-"],
                        appeared=c_appeared,
                        notes=values["-CREATURE NOTES-"],
                        impact=values["-CREATURE IMPACT SLIDER-"],
                        prevalence=values["-CREATURE PREVALENCE SLIDER-"],
                        trend=values["-CREATURE TREND SLIDER-"],
                    )
                    if values["-CREATURE STATUS-"] == "Archived":
                        creature.status.archive()
                    garden.add_item("creatures", creature)
                    event_funcs.update_creature_dropdowns(window, garden)
                    event_funcs.clear_creature_values(window)
                    # Clear the task fields and organism links
                    event_funcs.clear_task_values(window)
                    event_funcs.clear_organism_links(window, garden)
                    # Update the associated summary field
                    window["-SUMMARY TOTAL CREATURES-"].update(len(garden.creatures))
                    gardens_changed = True

            elif event == "CREATURE REMOVE":
                if not values["-CREATURE NAME-"] in garden.creatures:
                    popups.item_not_created("creature")
                    continue
                c_confirmation = popups.remove_confirmation(values["-CREATURE NAME-"], "creature")
                if c_confirmation == "OK":
                    garden.remove_item("creatures", values["-CREATURE NAME-"])
                    event_funcs.update_creature_dropdowns(window, garden)
                    event_funcs.clear_creature_values(window)
                    event_funcs.clear_task_values(window)
                    event_funcs.clear_organism_links(window, garden)
                    window["-SUMMARY TOTAL CREATURES-"].update(len(garden.creatures))
                    gardens_changed = True

            elif event == "-CREATURE NAME-":
                if not values["-CREATURE NAME-"]:
                    event_funcs.clear_creature_values(window)
                    continue
                # If a creature is selected populate the relevant fields with its values
                creature_instance = garden.creatures.get(values["-CREATURE NAME-"])
                window["-CREATURE NAME-"].update(creature_instance.name)
                window["-CREATURE TYPE-"].update(creature_instance.org_type)
                window["-CREATURE APPEARED DATE-"].update(creature_instance.appeared)
                window["-CREATURE STATUS-"].update(creature_instance.status.get())
                window["-CREATURE NOTES-"].update(creature_instance.notes)
                window["-CREATURE IMPACT SLIDER-"].update(creature_instance.impact)
                window["-CREATURE PREVALENCE SLIDER-"].update(creature_instance.prevalence)
                window["-CREATURE TREND SLIDER-"].update(creature_instance.trend)

            ######################### Manage Plant Events ##########################

            elif event == "PLANT CREATE/UPDATE":
                # Validate plant name and planted date
                p_name = values["-PLANT NAME-"].strip()
                p_planted = values["-PLANT PLANTED DATE-"].strip()
                # Check that a garden has been selected
                if not values["-SELECT GARDEN-"]:
                    popups.garden_not_selected("plant")
                    continue
                if not p_name:
                    popups.invalid_name("plant name")
                    continue
                try:
                    if p_planted:
                        event_funcs.check_date_validity(p_planted)
                except ValueError:
                    popups.invalid_date(field="planted date", date=p_planted)

                else:
                    plant = Plant(
                        name=p_name,
                        org_type=values["-PLANT TYPE-"],
                        planted=p_planted,
                        edible=values["-PLANT EDIBLE-"],
                        notes=values["-PLANT NOTES-"],
                        impact=values["-PLANT IMPACT SLIDER-"],
                        prevalence=values["-PLANT PREVALENCE SLIDER-"],
                        trend=values["-PLANT TREND SLIDER-"],
                    )
                    if values["-PLANT STATUS-"] == "Archived":
                        plant.status.archive()
                    garden.add_item("plants", plant)
                    event_funcs.update_plant_dropdowns(window, garden)
                    event_funcs.clear_plant_values(window)
                    event_funcs.clear_task_values(window)
                    event_funcs.clear_organism_links(window, garden)
                    window["-SUMMARY TOTAL PLANTS-"].update(len(garden.plants))
                    gardens_changed = True

            elif event == "PLANT REMOVE":
                if not values["-PLANT NAME-"] in garden.plants:
                    popups.item_not_created("plant")
                    continue
                p_confirmation = popups.remove_confirmation(values["-PLANT NAME-"], "plant")
                if p_confirmation == "OK":
                    garden.remove_item("plants", values["-PLANT NAME-"])
                    event_funcs.update_plant_dropdowns(window, garden)
                    event_funcs.clear_plant_values(window)
                    event_funcs.clear_task_values(window)
                    event_funcs.clear_organism_links(window, garden)
                    window["-SUMMARY TOTAL PLANTS-"].update(len(garden.plants))
                    gardens_changed = True

            elif event == "-PLANT NAME-":
                if not values["-PLANT NAME-"]:
                    event_funcs.clear_plant_values(window)
                    continue
                # If a plant is selected populate the relevant fields with its values
                plant_instance = garden.plants.get(values["-PLANT NAME-"])
                window["-PLANT NAME-"].update(plant_instance.name)
                window["-PLANT TYPE-"].update(plant_instance.org_type)
                window["-PLANT PLANTED DATE-"].update(plant_instance.planted)
                window["-PLANT STATUS-"].update(plant_instance.status.get())
                window["-PLANT EDIBLE-"].update(plant_instance.edible)
                window["-PLANT NOTES-"].update(plant_instance.notes)
                window["-PLANT IMPACT SLIDER-"].update(plant_instance.impact)
                window["-PLANT PREVALENCE SLIDER-"].update(plant_instance.prevalence)
                window["-PLANT TREND SLIDER-"].update(plant_instance.trend)

            ########################## Manage Task Events ##########################

            elif event == "TASK CREATE/UPDATE":
                # Check that a garden has been selected
                if not values["-SELECT GARDEN-"]:
                    popups.garden_not_selected("task")
                    continue
                # Strip and validate task name and set schedule values
                # NB: Frequency is not validated because it's a readonly dropdown
                t_name = values["-TASK NAME-"].strip()
                start_date = values["-TASK START-"].strip()
                count = values["-TASK COUNT-"].strip()
                bymonth = values["-TASK BY MONTH-"].strip()
                interval = values["-TASK INTERVAL-"].strip()
                if not t_name:
                    popups.invalid_name("task name")
                    continue
                try:
                    if start_date:
                        event_funcs.check_date_validity(start_date)
                except ValueError:
                    popups.invalid_date(field="first due", date=start_date)
                    continue
                if count and not count.isdigit():
                    popups.invalid_digit(field="count", digit=count)
                elif bymonth and any(month not in MONTHS for month in bymonth.split(" ")):
                    popups.invalid_bymonth(bymonth)
                elif interval and not interval.isdigit():
                    popups.invalid_digit(field="interval", digit=interval)
                # If there are no validation errors, create/update the task
                else:
                    task = Task(
                        name=t_name,
                        assignee=values["-TASK ASSIGNEE-"],
                        length=values["-TASK LENGTH-"],
                        linked_creatures=values["-TASK LINKED CREATURES-"],
                        linked_plants=values["-TASK LINKED PLANTS-"],
                        description=values["-TASK NOTES-"],
                    )

                    task.set_schedule(
                        start_date=start_date,
                        freq=values["-TASK FREQUENCY-"],
                        count=count,
                        bymonth=bymonth,
                        interval=interval,
                    )
                    # Handle rare situation where provided shedule doesn't produce any due dates
                    if not task.schedule:
                        popups.no_due_dates()
                        continue

                    if values["-TASK STATUS-"] == "Archived":
                        task.status.archive()
                    # If the task already exists add any pre-existing completed dates to it
                    task_instance = garden.tasks.get(values["-TASK NAME-"])
                    if task_instance:
                        task.completed_dates = task_instance.completed_dates
                    # Add the task to the garden, overwriting the old version if it already exists
                    garden.add_item("tasks", task)
                    event_funcs.update_task_dropdown(window, garden)
                    # Clear the task fields and variable once the task has been added to the garden
                    event_funcs.clear_task_values(window)
                    event_funcs.clear_organism_links(window, garden)
                    task = None
                    # Update the task numbers shown on the summary tab
                    event_funcs.update_task_summaries(window, garden)
                    gardens_changed = True

            elif event == "TASK REMOVE":
                if not values["-TASK NAME-"] in garden.tasks:
                    popups.item_not_created("task")
                    continue
                t_confirmation = popups.remove_confirmation(values["-TASK NAME-"], "task")
                if t_confirmation == "OK":
                    garden.remove_item("tasks", values["-TASK NAME-"])
                    event_funcs.update_task_dropdown(window, garden)
                    event_funcs.clear_task_values(window)
                    task = None
                    event_funcs.clear_organism_links(window, garden)
                    event_funcs.update_task_summaries(window, garden)
                    gardens_changed = True

            elif event == "ADD PROGRESS":
                # Check the task variable exists and has been assigned to a task
                if "task" in locals() and task:
                    # Open the progress subwindow and set gardens_changed flag if progress added
                    if subwindows.add_progress_window(window, task):
                        gardens_changed = True
                else:
                    popups.item_not_created("task", "progress can be added")

            elif event == "-TASK NAME-":
                if not values["-TASK NAME-"]:
                    event_funcs.clear_task_values(window)
                    event_funcs.clear_organism_links(window, garden)
                    task = None
                    continue
                # If a task is selected populate the relevant fields with its values
                task_instance = garden.tasks[values["-TASK NAME-"]]
                window["-TASK PROGRESS-"].update(task_instance.get_current_progress())
                window["-TASK NEXT DUE-"].update(task_instance.get_next_due_date())
                window["-TASK ASSIGNEE-"].update(task_instance.assignee)
                window["-TASK LENGTH-"].update(task_instance.length)
                window["-TASK LINKED CREATURES-"].set_value(task_instance.linked_creatures)
                window["-TASK LINKED PLANTS-"].set_value(task_instance.linked_plants)
                window["-TASK STATUS-"].update(task_instance.status.get())
                window["-TASK NOTES-"].update(task_instance.description)
                window["-TASK START-"].update(task_instance.raw_schedule["start date"])
                window["-TASK FREQUENCY-"].update(task_instance.raw_schedule["freq"])
                window["-TASK COUNT-"].update(task_instance.raw_schedule["count"])
                window["-TASK BY MONTH-"].update(task_instance.raw_schedule["bymonth"])
                window["-TASK INTERVAL-"].update(task_instance.raw_schedule["interval"])
                # Then assign instance to task variable so progress can be added
                task = task_instance

    # Handle fatal exceptions gracefully and log them in the gardenlife.log file
    except Exception as ex:
        logger.exception("Fatal Error")
        popups.fatal_error(ex)

    # Finish up by removing from the screen
    window.close()
 def test_bob_and_charlies_gardens(self):
     garden = Garden("VVCCGG\nVVCCGG")
     self.assertEqual(["Clover"] * 4, garden.plants("Bob"))
     self.assertEqual(["Grass"] * 4, garden.plants("Charlie"))
 def xtest_bob_and_charlies_gardens(self):
     garden = Garden("VVCCGG\nVVCCGG")
     self.assertEqual(["Clover"] * 4, garden.plants("Bob"))
     self.assertEqual(["Grass"] * 4, garden.plants("Charlie"))