Matrices-Practice-Tool / src / main.py
main.py
Raw
# Imports from KivyMD
from kivymd.app import MDApp
from kivymd.uix.datatables import MDDataTable
# Imports from Kivy
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.metrics import dp
from kivy.core.window import Window
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.anchorlayout import AnchorLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.clock import Clock
# Main functionality from setup
from Setup.setup import Setup
from report import Report
# Importing time for quiz timing
import time
# Import QuizClasses for quiz generation
from QuizClasses.quiz import Quiz
# Import Matplot to draw matrix as image
import matplotlib as mpl
import matplotlib.pyplot as plt
import matplotlib.mathtext as mathtext
# import os for deleting images
import os
# import numpy for checking answers
import numpy as np
# Constraining window sizes
Window.minimum_height = 288
Window.minimum_width = 512
Window.maximum_height = 1080
Window.maximum_width = 1920

# Importing setup from setup.py
S = Setup()


class MenuScreen(Screen):
    def disable_login_registration(self):
        self.ids["log_in_button"].disabled = True
        self.ids["register_button"].disabled = True
        self.ids["practice_button"].disabled = False
        self.ids["report_button"].disabled = False

    def disable_registration(self):
        self.ids["register_button"].disabled = True


class LoginScreen(Screen):
    login_data = []
    current_user = ""

    def get_login_input(self):
        for textInput in list(self.ids)[1:]:
            self.login_data.append(self.ids[textInput].text)
        response = S.user_login(self.login_data)
        if response == "Incorrect password":
            self.ids["loginPasswordText"].error = True
            self.ids["loginPasswordText"].text = ""
        elif response == "User does not exist":
            self.ids["loginUsernameText"].error = True
            self.ids["loginUsernameText"].text = ""
        else:
            self.change_menu()
            self.parent.current = "MenuScreen"
            self.parent.transition.direction = "up"
            self.current_user = self.login_data[0]
        Clock.schedule_once(self.clear_errors, 3)

    def clear_errors(self, dt):
        for textfield in self.ids:
            self.ids[textfield].error = False

    def clear_login_input(self):
        self.login_data = []

    def change_menu(self):
        menu = self.manager.get_screen('MenuScreen')
        menu.disable_login_registration()


class RegistrationScreen(Screen):
    registration_data = []

    def get_registration_input(self):
        for textInput in self.ids:
            self.registration_data.append(self.ids[textInput].text)
        response = S.user_register(self.registration_data)
        if response == "Passwords are not equal":
            self.ids["registrationRePasswordText"].error = True
            self.ids["registrationRePasswordText"].text = ""
        elif response == "Password does not meet requirements":
            self.ids["registrationPasswordText"].error = True
            self.ids["registrationPasswordText"].text = ""
        elif response == "Username does not meet requirements":
            self.ids["registrationUsernameText"].error = True
            self.ids["registrationUsernameText"].text = ""
        elif response[-6:] == "exists":
            self.ids["registrationUsernameText"].error = True
            self.ids["registrationUsernameText"].text = ""
        else:
            self.remove_registration_option()
            self.parent.current = "MenuScreen"
            self.parent.transition.direction = "down"
        Clock.schedule_once(self.clear_errors, 3)

    def clear_registration_input(self):
        self.registration_data = []

    def clear_errors(self, dt):
        for textfield in self.ids:
            self.ids[textfield].error = False

    def remove_registration_option(self):
        menu = self.manager.get_screen('MenuScreen')
        menu.disable_registration()


class PracticeScreen(Screen):
    def set_timer_true(self):
        tempScreen = self.manager.get_screen('QuestionSelectionScreen')
        tempScreen.set_timer_true()

    def set_timer_false(self):
        tempScreen = self.manager.get_screen('QuestionSelectionScreen')
        tempScreen.set_timer_false()


class ReportScreen(Screen):
    def load_table(self):
        layout = BoxLayout(orientation='vertical')
        rep = Report(S.current_user.username, S.current_user.email)
        quizzes = rep.get_last_5_shortened()
        data_tables = MDDataTable(
            size_hint=(0.9, 0.6),
            pos_hint={"center_x": .5, "center_y": .5},
            column_data=[("Date", dp(30)), ("Score", dp(30)),
                         ("Time", dp(30)), ],
            row_data=quizzes
        )
        title = Label(text='Report', size_hint=(1, .1))
        button_email = Button(
            text='Email this table as a CSV file', size_hint=(1, .2))
        button_email.bind(on_press=lambda x: rep.email_last_7())
        button_export = Button(
            text='Email all results as a CSV file', size_hint=(1, .2))
        button_export.bind(on_press=lambda x: rep.email_everything())
        button_menu = Button(text='Back to the Menu', size_hint=(1, .2))
        button_menu.bind(on_press=lambda x: self.fix())
        layout.add_widget(title)
        layout.add_widget(data_tables)
        layout.add_widget(button_email)
        layout.add_widget(button_export)
        layout.add_widget(button_menu)
        self.add_widget(layout)

    def fix(self):
        self.manager.transition.direction = 'right'
        self.manager.current = 'MenuScreen'

    def on_enter(self):
        self.load_table()


class ResetScreen(Screen):
    def reset_password(self):
        username = self.ids['resetUsernameText'].text
        response = S.user_login([username, ""])
        if response != "User does not exist":
            password = self.ids["resetPasswordText"].text
            rePassword = self.ids["resetRePasswordText"].text
        if S.reset_password([username, password, rePassword]) == 'Reset':
            self.manager.transition.direction = 'right'
            self.manager.current = 'LoginScreen'


class QuestionSelectionScreen(Screen):
    question_types = []
    timed_quiz = False

    def mark_all(self):
        if self.ids["mark_all"].active == True:
            for checkbox in list(self.ids)[1:]:
                self.ids[checkbox].active = True
        else:
            for checkbox in list(self.ids)[1:]:
                self.ids[checkbox].active = False

    def set_types(self):
        self.question_types = []
        for checkbox in list(self.ids)[1:-1]:
            if self.ids[checkbox].active == True:
                self.question_types.append(checkbox)

    def make_test(self):
        if self.question_types != []:
            S.current_quiz = Quiz(self.question_types, int(time.time()))
            self.parent.current = "QuestionScreen"

    def set_timer_true(self):
        self.timed_quiz = True

    def set_timer_false(self):
        self.timed_quiz = False

    def start_timer(self):
        if self.question_types != []:
            tempScreen = self.manager.get_screen('QuestionScreen')
            if self.timed_quiz == True:
                tempScreen.start_timer()
            else:
                tempScreen.ids["time_label"].text = "No Time Limit"


class QuestionScreen(Screen):
    question_number = 0
    current_time = 0
    currently_rendering = 0
    questions = []
    answers = [["", "", ""] for i in range(10)]
    questions_right = []

    question_instructions = {
        "+": "Please add the 2 matrices together",
        "*": "Please multiply the 2 matrices together",
        "-": "Please Subtract the second matrix from the first",
        "inv": "Please find the inverse of the matrix",
        "det": "Please find the determinant of the matrix",
    }

    def reset_details(self):
        self.question_number = 0
        self.current_time = 0
        self.currently_rendering = 0
        self.answers.clear()
        self.questions.clear()
        self.questions_right.clear()
        self.increment_question()
        self.ids["question_back_button"].disabled = True

    def set_questions(self):
        self.questions = S.current_quiz.questions
        self.answers = [["", "", ""] for i in range(10)]

    def increment_question(self):
        self.question_number += 1
        self.load_answer()
        if self.question_number < 11:
            self.ids["question_next_button"].disabled = False
            if self.question_number > 1:
                self.ids["question_back_button"].disabled = False
        else:
            self.mark_quiz()
        self.ids["question_label"].text = "Question "+str(self.question_number)
        try:
            self.ids["question_contents"].text = self.question_instructions.get(
                self.questions[self.question_number-1].operation)
            if os.path.exists("question"+str(self.question_number-1)+"A.png"):
                self.ids["matrix_a"].source = "question" + \
                    str(self.question_number-1)+"A.png"
            else:
                self.ids["matrix_a"].source = "empty.png"
            if os.path.exists("question"+str(self.question_number-1)+"B.png"):
                self.ids["matrix_b"].source = "question" + \
                    str(self.question_number-1)+"B.png"
            else:
                self.ids["matrix_b"].source = "empty.png"
        except:
            pass

    def save_current_answer(self):
        count = 0
        for textfield in list(self.ids)[5:8]:
            self.answers[self.question_number -
                         1][count] = self.ids[textfield].text
            count += 1

    def clear_current_answer(self):
        self.ids["rows_text"].text = ""
        self.ids["columns_text"].text = ""
        self.ids["matrix_text"].text = ""

    def load_answer(self):
        try:
            self.ids["rows_text"].text = self.answers[self.question_number-1][0]
            self.ids["columns_text"].text = self.answers[self.question_number-1][1]
            self.ids["matrix_text"].text = self.answers[self.question_number-1][2]
        except:
            pass

    def decrement_question(self):
        self.question_number -= 1
        self.load_answer()
        if self.question_number == 1:
            self.ids["question_back_button"].disabled = True
        else:
            self.ids["question_back_button"].disabled = False
        self.ids["question_label"].text = "Question "+str(self.question_number)
        try:
            self.ids["question_contents"].text = self.question_instructions.get(
                self.questions[self.question_number-1].operation)
            if os.path.exists("question"+str(self.question_number-1)+"A.png"):
                self.ids["matrix_a"].source = "question" + \
                    str(self.question_number-1)+"A.png"
            else:
                self.ids["matrix_a"].source = "empty.png"
            if os.path.exists("question"+str(self.question_number-1)+"B.png"):
                self.ids["matrix_b"].source = "question" + \
                    str(self.question_number-1)+"B.png"
            else:
                self.ids["matrix_b"].source = "empty.png"
        except:
            pass

    def mark_quiz(self):
        for index in range(10):
            if self.answers[index][0] == "" and self.answers[index][1] == "":
                if self.answers[index][2] == self.listify(np.round(self.questions[index].answer_matrix, 0)):
                    self.questions_right.append(index+1)
            else:
                if self.answers[index][2] == self.listify(self.questions[index].answer_matrix)[:-1]:
                    self.questions_right.append(self.questions[index].num)

        S.current_quiz.set_time(int(time.time()))
        questions_done = [str(q.num) for q in self.questions]
        for i, q in enumerate(questions_done):
            if int(q) not in self.questions_right:
                questions_done[i] = q + 'X'
            else:
                questions_done[i] = q + 'O'
        S.current_quiz.write_to(S.current_user.username,
                                S.date, questions_done)

        self.parent.transition.direction = 'left'
        self.parent.current = 'ResultsScreen'

    def get_score(self):
        if self.questions_right == 0:
            return 0
        else:
            return len(self.questions_right)

    def listify(self, array):
        return str(array.flatten().tolist()).replace("[[", "").replace("]]", "").replace(" ", "").split(".", 1)[0][1:]

    def cancel_quiz(self):
        self.reset_details()
        self.delete_question_png()
        self.remove_timer()
        self.parent.transition.direction = 'right'
        self.parent.current = 'MenuScreen'

    def remove_timer(self):
        Clock.unschedule(self.increment_timer)

    def delete_question_png(self):
        AB = ["A", "B"]
        for index in range(10):
            for x in AB:
                if os.path.exists("question"+str(index)+x+".png"):
                    os.remove("question"+str(index)+x+".png")

    def start_timer(self):
        Clock.schedule_once(self.call_mark_quiz, 600)
        Clock.schedule_interval(self.increment_timer, 1)

    def call_mark_quiz(self, dt):
        self.mark_quiz()

    def increment_timer(self, dt):
        self.current_time += 1
        self.ids["time_label"].text = "Time: "+str(self.current_time)

    def pretty(self, a):
        lines = str(a).replace('[[', '[').replace(']]', ']').replace(
            " [", "[").replace(" ]", "]").splitlines()
        stringVersion = ""
        for l in lines:
            stringVersion += l + '\n'
        return stringVersion.replace(" ", "  ")

    def render_matrix(self, number, which):
        mpl.rcParams['font.size'] = 12
        mpl.rcParams['figure.figsize'] = [1, 1]
        matrix_text = self.pretty(
            self.questions[number].matrices_dict.get(which))
        if matrix_text != "None" or None:
            plt.text(0, 0, matrix_text, color="white")
            fig = plt.gca()
            fig.axes.axis('off')
            plt.savefig(f'question{number}'+which+'.png',
                        transparent=True, bbox_inches='tight', pad_inches=0)
        for txt in fig.texts:
            txt.set_visible(False)

    def draw_questions(self):
        for question in self.questions:
            if question.q_type == 1 or question.q_type == 2 or question.q_type == 3:
                self.render_matrix(self.currently_rendering, "A")
                self.render_matrix(self.currently_rendering, "B")

            else:
                self.render_matrix(self.currently_rendering, "A")
            self.currently_rendering += 1


class ResultsScreen(Screen):
    score = 0

    def set_score(self):
        self.score = self.get_score()
        self.ids['score_label'].text = "Your score was: " + \
            str(self.score) + "/10"

    def get_score(self):
        temporary_screen = self.manager.get_screen('QuestionScreen')
        return temporary_screen.get_score()

    def cancel_quiz(self):
        temporary_screen = self.manager.get_screen('QuestionScreen')
        temporary_screen.reset_details()
        temporary_screen.delete_question_png()
        temporary_screen.remove_timer()
        self.parent.transition.direction = 'right'
        self.parent.current = 'MenuScreen'


class Team16App(MDApp):
    def build(self):
        self.theme_cls.theme_style = 'Dark'
        self.theme_cls.primary_palette = 'Amber'
        pass


if __name__ == '__main__':
    Team16App().run()