solutions.py
solutions.py
from __future__ import print_function
import tkinter as tk
from tkinter import ttk

list_emoji = ['😀','😄','😉','😍','😐','😴','🦊','🦝','🐹','🐾','🐕','🦥','🦨','🐘','🦔','🐿','🦎','🐊','🦖','🦈','🐬','🦅',
              '🦢','🦉','🦇','🕷','🎄','🎁','🎞','🖼','🎨','🛒','🏆','🎮','🕹','♟','🔈','🔔','🎵','🎤','🎧','🎹','🔒','🔑','🛠',
              '🔧','⚙','🧱','💊','🔭','🧰','🛡','🏹','🗡','⚔','💣','☎','📞','📱','💻','🖥','🖨','⌨','🖱','💾','📀','🎬','📷',
              '📸','📹','🔍','💡','🔦','📕','📖','📗','📚','💰','💷','📦','✏','🖊','🖌','📝','💼','📁','📅','📆','📈','📉','📊',
              '📋','📏','⌛','⏳','⌚','⏰','⏱','🍕','🍟','🥪','🌮','🍦','🍩','🎂','🍰','🍫','🍬','🍭','🍽','🥝','🍓','🍏','🍄',
              '🌸','🌻','🌼','🌲','🌳','🌴','🌵','🍀','🍁','🚗','🚀','⛵','🌍','🌎','🗺','🧭','⛰','🏕','🏞','🏜','🏝','🏡','⛺',
              '🌄','🛏','🧻','🧽','⛅','🌤','🌦','🌩','🌠','🌈','⚡','🔥','🌊','💕','💤','💥','❌','⭕','❗','❓',
              '➕','➖','💭','🗯','🕒','✔','✖','⬜','✅','❎','⬛','🔲','🔳']

########################################################################################################################
#######################################    W I N D O W S    H I G H    D P I    ########################################
########################################################################################################################

try:
    from ctypes import windll
    windll.shcore.SetProcessDpiAwareness(1)
except:
    pass

########################################################################################################################
########################################################################################################################
################################                                                       #################################
################################  S Q L I T E    D A T A B A S E    F U N C T I O N S  #################################
################################                                                       #################################
########################################################################################################################
########################################################################################################################

import sqlite3

class Database:
    def create_databases(self):
        connection = sqlite3.connect("./assets/solutions/sample_data.db")
        cursor = connection.cursor()
        cursor.execute(
            "CREATE TABLE IF NOT EXISTS animals("
            "animal_id integer primary key, "
            "animal_name text, "
            "animal_notes text, "
            "animal_state integer, "
            "animal_option text)")
        connection.commit()
        connection.close()

    def animals_add(self, a_id, a_name, a_notes, a_state, a_option):
        connection = sqlite3.connect("./assets/solutions/sample_data.db")
        cursor = connection.cursor()
        try:
            cursor.execute("INSERT INTO animals VALUES(?, ?, ?, ?, ?)",
                           (a_id, a_name, a_notes, a_state, a_option))
            connection.commit()
        except:
            print('Database error')
        connection.close()

    def animals_update(self, a_id, a_name, a_notes, a_state, a_option):
        connection = sqlite3.connect("./assets/solutions/sample_data.db")
        cursor = connection.cursor()
        cursor.execute(f'UPDATE animals SET animal_name=? WHERE animal_id={a_id}', (a_name,))
        cursor.execute(f'UPDATE animals SET animal_notes=? WHERE animal_id={a_id}', (a_notes,))
        cursor.execute(f'UPDATE animals SET animal_state=? WHERE animal_id={a_id}', (a_state,))
        cursor.execute(f'UPDATE animals SET animal_option=? WHERE animal_id={a_id}', (a_option,))
        connection.commit()
        connection.close()

    def animals_delete(self, a_id):
        connection = sqlite3.connect("./assets/solutions/sample_data.db")
        cursor = connection.cursor()
        cursor.execute(f'DELETE FROM animals WHERE animal_id={a_id}')
        connection.commit()
        connection.close()

    def animals_select_all(self):
        connection = sqlite3.connect("./assets/solutions/sample_data.db")
        cursor = connection.cursor()
        cursor.execute("SELECT * FROM animals ORDER BY animal_name")
        all_animals = [[row[0], row[1], row[2], row[3], row[4]] for row in cursor.fetchall()]
        connection.close()
        return all_animals

    def animals_select_by_id(self, a_id):
        connection = sqlite3.connect("./assets/solutions/sample_data.db")
        cursor = connection.cursor()
        cursor.execute("SELECT * FROM animals WHERE animal_id=? ORDER BY animal_name DESC",
                       (a_id,))  # I can select multiple entries using WHERE, for example all the same names
        animal = [[row[0], row[1], row[2], row[3], row[4]] for row in cursor.fetchall()]
        # I can also format the data as Dictionary
        animal_dictionary = [{'id': row[0], 'name': row[1], 'notes': row[2], 'state': row[3], 'option': row[4]} for row in cursor.fetchall()]
        connection.close()
        return animal

    def animals_select_all_of_type(self):
        connection = sqlite3.connect("./assets/solutions/sample_data.db")
        cursor = connection.cursor()
        cursor.execute(
            "SELECT animal_name, animal_notes FROM animals ORDER BY animal_name ASC LIMIT 1")  # LIMIT 1 will return only one entry
        animal = [[row[0], row[1], row[2], row[3], row[4]] for row in cursor.fetchall()]
        connection.close()
        return animal

    ''' 
    GROUP BY 
    select movie_type, AVG(rating) FROM movies GROUP BY movie_type 
    this query will create groups of movie types containing all movies inside, and calculate the average rating for each group 
    '''


########################################################################################################################
########################################################################################################################
#####################################################             ######################################################
#####################################################    A P I    ######################################################
#####################################################             ######################################################
########################################################################################################################
########################################################################################################################

########################################################################################################################
###########################    G O O G L E    C A L E N D A R    A P I    -    S E T U P    ############################
########################################################################################################################

''' 
TERMINAL: 
pip install --upgrade google-api-python-client google-auth-httplib2 google-auth-oauthlib 
 
WEBSITE: 
✔ Go to https://developers.google.com/calendar/quickstart/python and log in 
✔ Click on "Enable the Google Calendar API" 
✔ Name the and agree to terms -> Select "Desktop App" and click on "Create" 
✔ Click on "Download Client Configuration", .json file 
 
PROJECT FOLDER: 
✔ Copy the downloaded .json file to my project folder 
✔ Create new python file: quickstart.py and copy code from https://developers.google.com/calendar/quickstart/python 
✔ Run the quickstart.py file and follow the log in procedure 
✔ At this point I can only view my calendars 
✔ In the quickstart.py file edit this line: SCOPES = ['https://www.googleapis.com/auth/calendar.readonly'] 
✔ replacing it with: SCOPES = ['https://www.googleapis.com/auth/calendar'] 
✔ delete the newly created pickle token.pickle file 
✔ After the new login procedure I will be able to see, edit, share and delete calendars 
✔ Paste and edit the following code to my app 
✔ Add this import to the top of my file: from __future__ import print_function 
'''

########################################################################################################################
###################    G O O G L E    C A L E N D A R    A P I    -    P R I N T    E V E N T S    #####################
########################################################################################################################

import datetime
import pickle
import os.path
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request

SCOPES = ['https://www.googleapis.com/auth/calendar']
def google_calendar_print():
    cscroll()
    creds = None
    if os.path.exists('./assets/solutions/token.pickle'):
        with open('./assets/solutions/token.pickle', 'rb') as token:
            creds = pickle.load(token)
    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            creds.refresh(Request())
        else:
            flow = InstalledAppFlow.from_client_secrets_file('./assets/solutions/credentials.json', SCOPES)
            creds = flow.run_local_server(port=0)
        with open('./assets/solutions/token.pickle', 'wb') as token:
            pickle.dump(creds, token)
    service = build('calendar', 'v3', credentials=creds)
    now = datetime.datetime.utcnow().isoformat() + 'Z' # 'Z' indicates UTC time
    statusbar.set('Getting the upcoming 10 events')
    events_result = service.events().list(calendarId='primary', timeMin=now, maxResults=10, singleEvents=True, orderBy='startTime').execute()
    events = events_result.get('items', [])
    if not events:
        statusbar.set('No upcoming events found.')
    for event in events:
        start = event['start'].get('dateTime', event['start'].get('date'))
        tk.Label(scroll, text=f"{start}, {event['summary']}").pack()

########################################################################################################################
#####################    G O O G L E    A P I    C A L E N D A R    -    A D D    E V E N T S    #######################
########################################################################################################################

import datetime
import pickle
import os.path
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request

SCOPES = ['https://www.googleapis.com/auth/calendar']
def google_calendar_add():
    def add_event():
        if check_input_date(calendar_entry.get()) and check_input_time(time_entry.get()):
            user_time = f"{calendar_entry.get()} {time_entry.get()}:00"
            user_time_as_date = datetime.datetime.strptime(user_time, '%Y-%m-%d %H:%M:%S')
            calculated_time_as_date = user_time_as_date + relativedelta(hours=1)
            title = 'Google Calendar API'
            event_data = 'Google Calendar API - Add Events'
            event_date = calendar_entry.get()
            event_time = time_entry.get()
            event_time_end = f"{str(calculated_time_as_date)[:10]}T{str(calculated_time_as_date)[11:]}"
            creds = None
            print(f"{title}\n{event_data}\n{event_date}\n{event_time}\n{event_time_end}\n\n")
            if os.path.exists('./assets/solutions/google_api_calendar/token.pickle'):
                with open('./assets/solutions/google_api_calendar/token.pickle', 'rb') as token:
                    creds = pickle.load(token)
            if not creds or not creds.valid:
                if creds and creds.expired and creds.refresh_token:
                    creds.refresh(Request())
                else:
                    flow = InstalledAppFlow.from_client_secrets_file('./assets/solutions/google_api_calendar/credentials.json', SCOPES)
                    creds = flow.run_local_server(port=0)
                with open('./assets/solutions/google_api_calendar/token.pickle', 'wb') as token:
                    pickle.dump(creds, token)
            event = {
                'summary': f'{title}',
                'location': '',
                'description': f'{event_data}',
                'start': {
                    'dateTime': f'{event_date}T{event_time}:00',
                    'timeZone': 'Europe/London',
                },
                'end': {
                    'dateTime': f'{event_time_end}',
                    'timeZone': 'Europe/London',
                },
                'reminders': {
                    'useDefault': False,
                    'overrides': [
                        {'method': 'email', 'minutes': 24 * 60},
                        {'method': 'popup', 'minutes': 10},
                    ],
                },
            }
            service = build('calendar', 'v3', credentials=creds)
            event = service.events().insert(calendarId='primary', body=event).execute()
            calendar_frame.destroy()
            statusbar.set('Event added successfully')
        else:
            date_statusbar.set('Invalid format')
    cscroll()
    calendar_frame = tk.Toplevel()
    calendar_frame.title("Calendar")
    calendar_frame.resizable(False, False)
    calendar_frame.configure(background='white')
    calendar_frame.rowconfigure(0, weight=1)
    date_statusbar = tk.StringVar()
    date_statusbar.set('')
    tk.Label(calendar_frame, text='Enter event date:', anchor='center', background='white').grid(row=1, column=0, sticky="W", pady=5)
    calendar_entry = ttk.Entry(calendar_frame, width=10)
    calendar_entry.grid(row=1, column=1, sticky="W", padx=10, pady=(0, 10))
    calendar_entry.focus()
    calendar_entry.bind("<Return>", lambda event: add_event())
    calendar_entry.bind("<Escape>", lambda event: calendar_frame.destroy())
    calendar_entry.insert(0, datetime.date.today())
    tk.Label(calendar_frame, text='Enter start hour:', anchor='center', background='white').grid(row=2, column=0, sticky="W", pady=5)
    time_entry = ttk.Entry(calendar_frame, width=10)
    time_entry.grid(row=2, column=1, sticky="W", padx=10, pady=(0, 10))
    time_entry.bind("<Return>", lambda event: add_event())
    time_entry.bind("<Escape>", lambda event: calendar_frame.destroy())
    time_entry.insert(0, str(datetime.datetime.now())[11:16])
    button_frame = tk.Frame(calendar_frame, bg='white')
    button_frame.grid(row=3, column=0, columnspan=2, sticky="EW")
    button_frame.columnconfigure(0, weight=1)
    tk.Button(button_frame, text='Send', font=("Georgia", 7), bg='white', relief='flat', command=add_event).grid(row=0, column=1, sticky="W")
    tk.Button(button_frame, text='Cancel', font=("Georgia", 7), bg='white', relief='flat', command=lambda: calendar_frame.destroy()).grid(row=0, column=2, sticky="W")
    tk.Label(calendar_frame, textvariable=date_statusbar, background='grey50', foreground='white').grid(row=4, column=0, columnspan=2, sticky="EW")

########################################################################################################################
##############################    G O O G L E    A P I    D R I V E    -    S E T U P    ###############################
########################################################################################################################

''' 
TERMINAL: 
pip install --upgrade google-api-python-client google-auth-httplib2 google-auth-oauthlib 
 
WEBSITE: 
✔ Go to https://developers.google.com/drive/api/v3/quickstart/python and log in 
✔ Click on "Enable the Google Drive API" 
✔ Name the and agree to terms -> Select "Desktop App" and click on "Create" 
✔ Click on "Download Client Configuration", .json file 
 
PROJECT FOLDER: 
✔ Copy the downloaded .json file to my project folder 
✔ Create a new function and code from https://developers.google.com/drive/api/v3/quickstart/python 
✔ Run the function file and follow the log in procedure 
✔ Edit SCOPES and change it to: SCOPES = ['https://www.googleapis.com/auth/drive'] 
✔ Delete the newly created pickle token.pickle file 
✔ Add this import to the top of my file: from __future__ import print_function'''

########################################################################################################################
################    G O O G L E    A P I    D R I V E    -    C R E A T E    S P R E A D S H E E T    ##################
########################################################################################################################

def google_drive_add():

    '''IMPORTANT: Each Google API Pickle and Credentials file should have it's own Folder 
    Check paths'''

    SCOPES = ['https://www.googleapis.com/auth/drive']
    from googleapiclient import discovery
    creds = None
    if os.path.exists('./assets/solutions/google_api_drive/token.pickle'):
        with open('./assets/solutions/google_api_drive/token.pickle', 'rb') as token:
            creds = pickle.load(token)
    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            creds.refresh(Request())
        else:
            flow = InstalledAppFlow.from_client_secrets_file(
                './assets/solutions/google_api_drive/credentials.json', SCOPES)
            creds = flow.run_local_server(port=0)
        # Save the credentials for the next run
        with open('./assets/solutions/google_api_drive/token.pickle', 'wb') as token:
            pickle.dump(creds, token)

    try:
        # CREATE SPREADSHEET
        service = discovery.build('sheets', 'v4', credentials=creds)
        my_title = 'Solutions'
        spreadsheet = {
            'properties': {'title': my_title},
        }
        spreadsheet = service.spreadsheets().create(body=spreadsheet, fields='spreadsheetId').execute()
        # print('Spreadsheet ID: {0}'.format(spreadsheet.get('spreadsheetId')))
        my_spreadsheet_id = spreadsheet.get('spreadsheetId')
        # print(my_spreadsheet_id)
        range_name = 'Sheet1'

        # Data
        formatted_list = [SAMPLE_LIST]

        body = {'values': formatted_list}

        result = service.spreadsheets().values().append(
            spreadsheetId=my_spreadsheet_id, range=range_name,
            valueInputOption='RAW', body=body).execute()
        # statusbar.set('{0} cells appended.'.format(result.get('updates').get('updatedCells')))
        statusbar.set("Google Drive Spreadsheet created")
    except:
        statusbar.set("Error")

########################################################################################################################
############################    W E A T H E R    -    O P E N    W E A T H E R    A P I    #############################
########################################################################################################################

# https://openweathermap.org/current

import requests
import json
from PIL import ImageTk, Image
from io import BytesIO

def weather_api():
    def get_api(town):
        try:
            api_request = requests.get(f"http://api.openweathermap.org/data/2.5/weather?id={town}&appid={api_id}")
            api = json.loads(api_request.content)
        except Exception as e:
            statusbar.set("Error")
        return api

    def create_labels(i, each_place):
        image_url = f"https://openweathermap.org/img/w/{get_api(each_place)['weather'][0]['icon']}.png"
        response = requests.get(image_url)
        icon_image = ImageTk.PhotoImage(Image.open(BytesIO(response.content)))
        image_label = tk.Label(scroll, image=icon_image)
        image_label.grid(row=i, column=0, padx=10, pady=0)
        image_label.image = icon_image
        tk.Label(scroll, text=get_api(each_place)['name'], font=("Calibri", 10)).grid(row=i, column=1, padx=10, pady=0)
        tk.Label(scroll, text=f"{round(((get_api(each_place)['main']['temp']) - 273.15), 2)}\u00B0C", font=("Calibri", 10)).grid(row=i, column=2, padx=10, pady=0)
        tk.Label(scroll, text=(get_api(each_place)['weather'][0]['description']).title(), font=("Calibri", 10)).grid(row=i, column=3, padx=10, pady=0)

    cscroll()
    places = ['2657839', '2656565', '2651753', '2646630', '2646384']
    api_id = 'your_api_here'
    try:
        for i, each_place in enumerate(places):
            create_labels(i, each_place)
    except:
        statusbar.set("Error")

########################################################################################################################
########################################################################################################################
##################################                                                     #################################
##################################    A R T I F I C I A L    I N T E L L I G E N C E   #################################
##################################                                                     #################################
########################################################################################################################
########################################################################################################################

########################################################################################################################
######################################    S P E E C H    R E C O G N I T I O N    ######################################
########################################################################################################################

''' 
pip install SpeechRecognition 
pip install pyaudio 
    or: go to https://www.lfd.uci.edu/~gohlke/pythonlibs/#pyaudio and download appropriate pyaudio module 
    or: pip install pipwin 
        pipwin install pyaudio'''

from tkinter import messagebox
import speech_recognition as sr

try:
    recognizer = sr.Recognizer()
    microphone = sr.Microphone()
except:
    pass

def ai_speech_recognition():
    try:
        with microphone as source:
            recognizer.adjust_for_ambient_noise(source)
            audio = recognizer.listen(source)
        try:
            response = recognizer.recognize_google(audio)
            statusbar.set(f"{response.capitalize()}")
        except:
            messagebox.showinfo(title='Error', message='There was a problem recording your message, please try again')
    except:
        statusbar.set('No microphone detected')

########################################################################################################################
########################################################################################################################
###################################################                  ###################################################
###################################################   C L A S S E S  ###################################################
###################################################                  ###################################################
########################################################################################################################
########################################################################################################################

########################################################################################################################
####################################    B U T T O N    W I T H    P I C T U R E    #####################################
########################################################################################################################

class MyButton(tk.Button):
    pictures_path = "./assets/solutions/"

    def __init__(self, container, pic='', pic_px=0, *args, **kwargs):
        super().__init__(container, *args, **kwargs)

        self.pic = pic
        self.px = pic_px

        self.im = Image.open(self.pictures_path + self.pic)
        self.im.thumbnail((pic_px, pic_px))
        self.pic = ImageTk.PhotoImage(self.im)

        self.config(background='white',
                    activebackground='white',
                    image=self.pic,
                    compound="top",
                    relief="flat",
                    width=pic_px,
                    borderwidth=0,
                    font=("Calibri", 9))
        self.grid(padx=15, pady=15)

def class_button():
    cscroll()
    MyButton(scroll,
             text='PictureButton',
             pic="pic01.png",
             pic_px=100,
             command=lambda: statusbar.set('Button')).grid(row=0, column=0)

########################################################################################################################
########################    D I S P L A Y    A T T R I B U T E S    O F    A    W I D G E T    #########################
########################################################################################################################

class Attributes(tk.Toplevel):
    def __init__(self, container, widget, *args, **kwargs):
        super().__init__(container, *args, **kwargs)
        self.geometry("1000x1000")
        self.focus()
        print(widget)

        tk.Label(self, text=f"Widget: {widget}", font=("Calibri", 24)).pack()

        tree_scroll = ttk.Scrollbar(self)
        tree_scroll.pack(side="right", fill="y")
        attrb_tree = ttk.Treeview(self, yscrollcommand=tree_scroll.set)
        attrb_tree.pack(fill="both", expand=True)
        tree_scroll.config(command=attrb_tree.yview)

        attrb_tree['columns'] = ('Attribute', 'Value')
        attrb_tree.heading("Attribute", text="Attribute", anchor="center")
        attrb_tree.heading("Value", text="Value", anchor="center")
        attrb_tree.column("#0", width=0, stretch="no")
        attrb_tree.column("Attribute", anchor="w", width=250)
        attrb_tree.column("Value", anchor="w", width=350)
        try:
            attrb_tree.insert(parent='', index='end', iid=0, text="", values=('WIDGET', f'{widget.widgetName}'.upper()))
        except AttributeError:
            pass

        for i, each_key in enumerate(widget.keys(), start=1):
            attrb_tree.insert(parent='', index='end', iid=i, text="", values=(each_key, widget[f'{each_key}']))

########################################################################################################################
###########################################    T K I N T E R    F R A M E    ###########################################
########################################################################################################################

class MyFrame(tk.Frame):
    def __init__(self, container, *args, **kwargs): # container is root
        super().__init__(container, *args, **kwargs)

        self.columnconfigure(0, minsize=500, weight=1)
        self.rowconfigure(0, minsize=500, weight=1)
        self.config(bg="white")


def display_my_frame():
    cscroll()
    my_frame = MyFrame(scroll)
    my_frame.pack()
    tk.Label(my_frame, text="This label is inside MyFrame Class Frame").grid(row=0, column=0)

########################################################################################################################
#################################################    L I M I T E R    ##################################################
########################################################################################################################

class Limiter(tk.Frame):
    def __init__(self, container, data, widgets_per_window=10):
        super().__init__(container)
        self.data = data
        self.widgets_per_window = widgets_per_window
        self.columnconfigure(0, weight=1)
        self.rowconfigure(0, weight=1)
        root.unbind("<Delete>")
        root.unbind("<Right>")
        root.unbind("<Left>")

        if self.data:
            self.divided_data = [self.data[x:x+self.widgets_per_window] for x in range(0, len(self.data), self.widgets_per_window)]
            self.chunks_number = len(self.divided_data)
            self.divided_data_index = 0

            self.frame_labels = tk.Frame(self, bg="#F9F7F7")
            self.frame_labels.grid(row=0, column=0, sticky="NEWS")
            self.frame_labels.columnconfigure(0, weight=1)

            if len(self.data) > self.widgets_per_window:
                root.bind("<Delete>", lambda event: self.reset_screen())
                root.bind("<Button-2>", lambda event: self.reset_screen())
                self.frame_buttons = tk.Frame(self, bg="#F9F7F7")
                self.frame_buttons.grid(row=1, column=0, sticky="EW")
                self.frame_buttons.columnconfigure((0, 4), weight=1)

                self.button_previous = tk.Button(self.frame_buttons, text="Previous", state="disabled", command=lambda: self.previous_screen(), width=20, bg="#3C4048", fg="#F9F7F7", relief="flat")
                self.button_previous.grid(row=0, column=0, sticky="E")

                self.button_reset = tk.Button(self.frame_buttons, text=f"Reset", command=lambda: self.reset_screen(), width=20, bg="#3C4048", fg="#F9F7F7", relief="flat")
                self.button_reset.grid(row=0, column=1, padx=2)
                self.button_next = tk.Button(self.frame_buttons, text="Next", command=lambda: self.next_screen(), width=20, bg="#3C4048", fg="#F9F7F7", relief="flat")
                self.button_next.grid(row=0, column=2, sticky="W")
                root.bind("<Right>", lambda event: self.next_screen())
                root.bind("<MouseWheel>", lambda event: self.mouse_scroll(event, "right"))

                self.button_reset["text"] = f"Page {self.divided_data_index + 1} / {self.chunks_number} | {len(self.data)}"

            self.prepare_widgets(self.divided_data_index)

    def mouse_scroll(self, event, allowed_direction):
        if allowed_direction == 'both':
            if event.delta < 0:
                self.next_screen()
            else:
                self.previous_screen()
        if allowed_direction == 'right':
            if self.divided_data_index < self.chunks_number-1:
                if event.delta < 0:
                    self.next_screen()
        else:
            if self.divided_data_index > 0:
                if event.delta > 0:
                    self.previous_screen()

    def next_screen(self):
        self.button_previous["state"] = 'normal'
        root.bind("<Left>", lambda event: self.previous_screen())
        root.bind("<MouseWheel>", lambda event: self.mouse_scroll(event, "both"))
        self.divided_data_index += 1
        self.prepare_widgets(self.divided_data_index)
        if self.divided_data_index == self.chunks_number-1:
            self.button_next["state"] = 'disabled'
            root.unbind("<Right>")
            root.bind("<MouseWheel>", lambda event: self.mouse_scroll(event, "left"))

    def previous_screen(self):
        self.button_next["state"] = 'normal'
        root.bind("<Right>", lambda event: self.next_screen())
        root.bind("<MouseWheel>", lambda event: self.mouse_scroll(event, "both"))
        self.divided_data_index -= 1
        self.prepare_widgets(self.divided_data_index)
        if self.divided_data_index == 0:
            self.button_previous["state"] = "disabled"
            root.unbind("<Left>")
            root.bind("<MouseWheel>", lambda event: self.mouse_scroll(event, "right"))

    def reset_screen(self):
        self.divided_data_index = 0
        self.button_next["state"] = 'normal'
        root.bind("<Right>", lambda event: self.next_screen())
        root.bind("<MouseWheel>", lambda event: self.mouse_scroll(event, "right"))
        self.button_previous["state"] = "disabled"
        root.unbind("<Left>")
        self.prepare_widgets(self.divided_data_index)


    def prepare_widgets(self, main_index):
        for child in self.frame_labels.winfo_children():
            child.destroy()
        if self.data:
            self.button_reset["text"] = f"Page {self.divided_data_index + 1} / {self.chunks_number} | {len(self.data)}"
            for index, d in enumerate(self.divided_data[main_index]):
                self.create_widgets(index, d)

    def create_widgets(self, index, dt):
        tk.Label(self.frame_labels, bg="#F9F7F7", fg="#3C4048", text=f"Label {dt}").grid(row=index, column=0)

def display_limiter():
    cscroll()
    dt = ['I am first 😆']
    for i in range(1, 25):
        dt.append(i)
    dt.append('I am last 😭')

    Limiter(root, data=dt, widgets_per_window=10).grid(row=0, column=0, sticky='NEWS')

########################################################################################################################
########################################################################################################################
####################################                                                ####################################
####################################   C H A R T S    A N D    S T A T I S T I C S  ####################################
####################################                                                ####################################
########################################################################################################################
########################################################################################################################

########################################################################################################################
###############################################    P I E    C H A R T    ###############################################
########################################################################################################################

import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg


def doughnut_chart():
    chart_window = tk.Toplevel()
    chart_window.title('Chart')

    plt.rcParams['font.size'] = 9
    plt.rcParams.update({'figure.autolayout': True})

    chart_values = [80, 95, 85]
    chart_labels = ['Nikon D3400', 'Nikon D7500', 'Samsung S20']
    chart_explode = [0, 0.1, 0]
    chart_colours = ['lightblue', 'lightgreen', 'blue', 'orange']

    my_chart = plt.Figure(figsize=(5, 5), dpi=100)
    my_chart_subplot = my_chart.add_subplot(111)
    my_chart_subplot.pie(chart_values,
                         labels=chart_labels,
                         explode=chart_explode,
                         colors=chart_colours,
                         rotatelabels=False,
                         labeldistance=1.1,
                         pctdistance=0.85,
                         autopct='%1.1f%%',
                         shadow=False,
                         startangle=90)
    my_chart_subplot.axis('equal')
    my_chart_subplot.set_title('Cameras')
    my_chart_circle = plt.Circle((0, 0), 0.7, color='white')
    my_chart.gca().add_artist(my_chart_circle)
    my_chart_pie = FigureCanvasTkAgg(my_chart, chart_window)
    my_chart_pie.get_tk_widget().grid(row=0, column=0, sticky="NEWS")

########################################################################################################################
############################################    S I M P L E    G R A P H    ############################################
########################################################################################################################

def simple_graph():
    import numpy as np
    house_prices = np.random.normal(200000, 25000, 5000)
    plt.hist(house_prices, 10)
    plt.show()

########################################################################################################################
########################################################################################################################
###################################################                 ####################################################
###################################################    E M A I L    ####################################################
###################################################                 ####################################################
########################################################################################################################
########################################################################################################################

########################################################################################################################
###########################################    E M A I L    S I M P L E    #############################################
########################################################################################################################

import smtplib
from email.message import EmailMessage

def email_simple():
    email_content = 'Email Content'
    email = EmailMessage()
    email['from'] = 'New Message'
    email['to'] = 'lord.huriko@gmail.com'
    email['subject'] = "New message"
    email.set_content(email_content)
    with smtplib.SMTP(host='smtp.gmail.com', port=587) as smtp:
        smtp.ehlo()
        smtp.starttls()
        smtp.login('photoituk@gmail.com', 'ulrzfqnmxdwjgbxm')
        smtp.send_message(email)
        statusbar.set('Email sent')

########################################################################################################################
#########################################    E M A I L    A D V A N C E D    ###########################################
########################################################################################################################

import smtplib
from email.message import EmailMessage
import re

def email_advanced():
    def check_email(email_address):
        email_str = '^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$'
        return re.search(email_str, email_address)

    def send_now():
        if check_email(email_entry.get()):
            email_content = 'Email Content'
            email = EmailMessage()
            email['from'] = 'App'
            email['to'] = email_entry.get()
            email['subject'] = "Text from App"
            email.set_content(email_content)
            with smtplib.SMTP(host='smtp.gmail.com', port=587) as smtp:
                smtp.ehlo()
                smtp.starttls()
                smtp.login('photoituk@gmail.com', 'ulrzfqnmxdwjgbxm')
                smtp.send_message(email)
                statusbar.set('Email sent')
                email_frame.destroy()
        else:
            statusbar.set('Invalid email address')

    email_frame = tk.Toplevel()
    email_frame.title("Send Email")
    email_frame.columnconfigure(0, weight=1)
    email_frame.rowconfigure(2, weight=1)
    ttk.Label(email_frame, text='Your email', anchor='center').grid(row=1, column=0, columnspan=3, sticky="EW", pady=5)
    email_entry = ttk.Entry(email_frame)
    email_entry.grid(row=2, column=0, columnspan=3, sticky="EW", padx=10, pady=(0, 10))
    email_entry.focus()
    email_entry.bind("<Return>", lambda event: send_now())
    ttk.Button(email_frame, text='Send', command=lambda: send_now()).grid(row=3, column=1, sticky="W")
    ttk.Button(email_frame, text='Cancel', command=lambda: email_frame.destroy()).grid(row=3, column=2, sticky="W")


########################################################################################################################
########################################################################################################################
########################################                                        ########################################
########################################    E X P O R T    F U N C T I O N S    ########################################
########################################                                        ########################################
########################################################################################################################
########################################################################################################################

########################################################################################################################
#########################################    E X P O R T    D A T A B A S E    #########################################
########################################################################################################################

import shutil

def backup_database():
    path = filedialog.askdirectory(title='Select folder')
    shutil.copyfile("./assets/solutions/DATA_SAMPLE.DAT", f'{path}/DATA_SAMPLE.DAT')
    statusbar.set('Backup complete')


########################################################################################################################
####################################    E X P O R T    F I L E    A S    C S V    ######################################
########################################################################################################################

def export_csv():
    header_text = 'ITEM\tDESCRIPTION\t\n'
    list_text = ['Break', 'This\ttext\nwould normally; break the: CSV, import'],\
                ['Better', 'This text is OK and will be imported without errors.']
    # Remove all characters which may break the CSV import
    message_text = ''
    for each_text in list_text:
        text_to_clear = str(each_text[1])
        text_to_clear = text_to_clear.replace(",", "")
        text_to_clear = text_to_clear.replace("\n", " ")
        text_to_clear = text_to_clear.replace("\t", " ")
        text_to_clear = text_to_clear.replace(";", " ")
        message_text += f'{each_text[0]}\t{text_to_clear}\t\n'
    data_to_export = header_text + message_text
    try:
        file_path = filedialog.asksaveasfilename(title="Save File",
                                                 defaultextension="csv",
                                                 filetypes=(("CSV files", "*.csv"), ("all files", "*.*")))
        with open(file_path, "w", encoding="utf-8") as file:
            file.write(data_to_export)
            statusbar.set('All messages exported.')
    except (AttributeError, FileNotFoundError):
        statusbar.set("Export cancelled.")
        return

########################################################################################################################
###################################    E X P O R T    F I L E    A S    P D F    #######################################
########################################################################################################################

from fpdf import FPDF
import os

def create_pdf():
    if not os.path.exists('./assets/solutions/PDF'):
        os.makedirs('./assets/solutions/PDF')
    document_name = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
    pdf = FPDF(format="A4")
    pdf.set_font("arial", '', 8)
    pdf.add_page()
    list_for_pdf = ['Fox', 'Cat', 'Dog', 'Dolphin', 'Owl', 'Heron', 'Red Deer', 'Mountain Hare', 'Ptarmigan', 'Robin']
    for each_item in list_for_pdf:
        pdf.image('./assets/solutions/pic01.png', None, None, w=20, h=20)
        pdf.ln(1)
        pdf.cell(10, 5, str(each_item))
        pdf.ln(5)
    pdf_path = f'.\\assets\\solutions\\PDF\\'
    pdf.output(f'{pdf_path}{document_name}.pdf', 'F')
    statusbar.set('PDF created')
    os.startfile(pdf_path)


########################################################################################################################
####################################    E X P O R T    F I L E    A S    T X T    ######################################
########################################################################################################################

from tkinter import filedialog

def export_txt():
    data_to_print = 'Squirrels are awesome'
    try:
        file_path = filedialog.asksaveasfilename(title='Save File',
                                                 defaultextension="txt",
                                                 filetypes=(("TXT files", "*.txt"), ("all files", "*.*")))
        with open(file_path, "w") as file:
            file.write(data_to_print)
            statusbar.set('Timesheet exported')
    except (AttributeError, FileNotFoundError):
        statusbar.set("Save operation cancelled")
        return

########################################################################################################################
############################    P R I N T    W I T H    D E F A U L T    P R I N T E R    ##############################
########################################################################################################################

import os
import tempfile

def printer_print():
    try:
        # This option doesn't work with UTF characters, like 🏕 ☀
        temporary_file = tempfile.mktemp(".txt")
        open(temporary_file, "w").write(SAMPLE_LIST[0])
        os.startfile(temporary_file, 'print')
    except:
        # So this option will run if UTF characters used
        with open("./assets/solutions/temp.txt", "w", encoding="utf-8") as file:
            file.write(SAMPLE_LIST[0])
        os.startfile(".\\assets\\solutions\\temp.txt", 'print')


########################################################################################################################
########################################################################################################################
######################################                                           #######################################
######################################    F I L E S    A N D    F O L D E R S    #######################################
######################################                                           #######################################
########################################################################################################################
########################################################################################################################

########################################################################################################################
#################################    C R E A T E    A N D    O P E N    F O L D E R    #################################
########################################################################################################################

import os

def create_and_open_folder():
    if not os.path.exists('./assets/solutions/FOLDER_NAME'):
        os.makedirs('./assets/solutions/FOLDER_NAME')
    os.startfile('.\\assets\\solutions\\FOLDER_NAME')

########################################################################################################################
###############    C R E A T E    F O L D E R S    W I T H    U N I Q U E    Y-M-D-H-M-S    N A M E S    ###############
########################################################################################################################

import datetime
from pathlib import Path
import os

def create_timestamp_folder():
    folder_name = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
    path = f'.\\assets\\solutions\\{folder_name}'
    Path(path).mkdir(parents=True, exist_ok=True)
    os.startfile(f'.\\assets\\solutions\\{folder_name}')

########################################################################################################################
##################################    D I S P L A Y    A B S O L U T E    P A T H    ###################################
########################################################################################################################

import os

def absolute_path():
    cscroll()
    file_path = filedialog.askopenfilename()
    absolute_folder_path = os.path.abspath(file_path)
    statusbar.set(absolute_folder_path)

########################################################################################################################
#########################    D I S P L A Y    A L L    F I L E S    I N    A    F O L D E R    #########################
########################################################################################################################

def display_all_files():
    cscroll()
    for i, each_item in enumerate(glob('assets/*')):
        create_labels(i, each_item)

########################################################################################################################
######################################    D I S P L A Y    F I L E    N A M E    #######################################
########################################################################################################################

import os

def display_file_name():
    cscroll()
    file_path = filedialog.askopenfilename()
    file_name = os.path.basename(file_path)
    statusbar.set(file_name)

########################################################################################################################
#########################    D I S P L A Y    F I R S T    P N G    I N    A    F O L D E R    #########################
########################################################################################################################

from glob import glob

def select_first_png():
    cscroll()
    selected_png = str(glob(f'assets/*.PNG')[0])
    statusbar.set(selected_png)

########################################################################################################################
###################################    O P E N    E X T E R N A L    P R O G R A M    ##################################
########################################################################################################################

def open_app():
    import os
    cscroll()
    tk.Button(scroll,
              text="Open program",
              command=lambda: os.system('"%s"' % filedialog.askopenfilename())).pack()
    ''' 
    '"%s"' % - This expression is here in case there are spaces in the path. 
    This allows Tkinter to read the spaces as part of path and not split the string. 
    '''

########################################################################################################################
###############################################    O P E N    F I L E    ###############################################
########################################################################################################################

from tkinter import filedialog

def open_file():
    filename = filedialog.askopenfilename(initialdir='/assets',
                                          title="Select a file",
                                          filetypes=(("PNG FIles", "*.png"), ("All files", "*.*")))
    statusbar.set(filename)

########################################################################################################################
########################################################################################################################
##################################################                   ###################################################
##################################################    I M A G E S    ###################################################
##################################################                   ###################################################
########################################################################################################################
########################################################################################################################

########################################################################################################################
##########################################    A N I M A T E    I M A G E    ############################################
########################################################################################################################

def animate_image():
    cscroll()
    happy = tk.PhotoImage(file="./assets/solutions/face_happy.png")
    crazy = tk.PhotoImage(file="./assets/solutions/face_crazy.png")
    image_label = tk.Label(scroll, bg='white', image=happy)
    image_label.image = happy
    image_label.pack(padx=20, pady=20)
    image_label.bind("<Enter>", lambda x: image_label.configure(image=crazy))
    image_label.bind("<Leave>", lambda x: image_label.configure(image=happy))

########################################################################################################################
##########################################    D I S P L A Y    I M A G E    ############################################
########################################################################################################################

from PIL import Image, ImageTk

'''I should always define images outside of functions, it is simpler this way: 
my_img = ImageTk.PhotoImage(Image.open('pic.jpg')) 
tk.Label(image=my_img).pack() 
 
Important: I cannot create image like this inside a function, just a python error. To make it work, first I have to 
define the my_img as global variable within the function: global my_img, and then the rest. This way it will work. 
To create image indide a function I have to write this code: 
'''

def display_image():
    image_original = Image.open("./assets/solutions/pic01.png")
    # image_edited = image_original.resize((100, 100), Image.ANTIALIAS)
    image_final = ImageTk.PhotoImage(image_original) # or image_edited
    cscroll()
    label_with_images = ttk.Label(scroll, image=image_final, anchor="nw")
    label_with_images.image = image_final
    label_with_images.grid(row=0, column=0, sticky="NEWS")

########################################################################################################################
##################################    D I S P L A Y    I M A G E    S I M P L E    #####################################
########################################################################################################################

def display_image_simple():
    # Remember that image has to be created outside a function
    # Also image has to be assigned twice
    cscroll()
    my_image = tk.PhotoImage(file="./assets/solutions/picture.png")
    label_image = ttk.Label(scroll, image=my_image, anchor="nw")
    label_image.image = my_image
    label_image.grid(row=0, column=0, sticky="NEWS", padx=200, pady=50)

########################################################################################################################
###############    D I S P L A Y    A N    I M A G E    R E S I Z E D    T O    F I T    W I N D O W    ################
########################################################################################################################

from tkinter import filedialog
from PIL import Image, ImageTk

def display_image_adjusted():
    cscroll()
    def set_image_size(path_to_image):
        image_width = root.winfo_width()
        image_height = root.winfo_height() - 50
        try:
            image_original = Image.open(f"{path_to_image}")
            width, height = image_original.size

            if height > width:
                percent = (height - root.winfo_height()) * (100 / height)
                new_width = int((width / 100 * (100 - percent)))
                image_edited = image_original.resize((new_width, image_height), Image.ANTIALIAS)
                image_final = ImageTk.PhotoImage(image_edited)
            else:
                percent = (width - root.winfo_width()) * (100 / width)
                new_height = int((height / 100 * (100 - percent)))
                image_edited = image_original.resize((image_width, new_height), Image.ANTIALIAS)
                image_final = ImageTk.PhotoImage(image_edited)
            return image_final
        except (AttributeError, FileNotFoundError):
            statusbar.set("Open File Cancelled")
    def open_file():
        file_path = filedialog.askopenfilename()
        image_imported = set_image_size(file_path)
        image_label = ttk.Label(scroll, image=image_imported, anchor='center')
        image_label.image = image_imported
        image_label.grid(row=1, column=0, sticky="NEWS")
    ttk.Button(scroll, text="Open File", command=lambda: open_file()).grid(row=0, column=0, sticky="EW")

########################################################################################################################
####################################    I M A G E    A S    B A C K G R O U N D    #####################################
########################################################################################################################

from PIL import ImageTk, Image

def image_as_background():

    def change_text(new_text):
        canvas.itemconfig(text_bg, text=new_text)
        canvas.itemconfig(text_fg, text=new_text)

    global image_canvas # Global to stop tkinter removing this image with it's garbage collector

    image_canvas = ImageTk.PhotoImage(file="./assets/solutions/pic01.png")

    canvas_window = tk.Toplevel(root)
    canvas_window.columnconfigure(0, weight=1)
    canvas_window.rowconfigure(0, weight=1)


    canvas = tk.Canvas(canvas_window, width=1680, height=800, bd=0)
    canvas.pack(fill="both", expand=True)
    canvas.create_image(0, 0, image=image_canvas, anchor="nw")

    # Create text with an outline
    text_bg = canvas.create_text(200, 100, text="Hello World!", font=("Helvetica", 40), fill='white', anchor='nw')
    text_fg = canvas.create_text(202, 102, text="Hello World!", font=("Helvetica", 40), fill='black', anchor='nw')

    # Create buttons to update text and place them in canvas
    button_1 = tk.Button(canvas_window, text="Pets", command=lambda: change_text('Exotic Pets'), width=20)
    button_2 = tk.Button(canvas_window, text="Adventures", command=lambda: change_text('Amazing Adventures'), width=20)

    button_1_window = canvas.create_window(10, 10, anchor="nw", window=button_1)
    button_2_window = canvas.create_window(200, 10, anchor="nw", window=button_2)

    # Just to show how, this is how to add other widgets to canvas
    entry_box = tk.Entry(canvas_window, font=("Calibri", 14), width=20, fg='green', bd=0)
    entry_box_window = canvas.create_window(500, 10, anchor='nw', window=entry_box)

########################################################################################################################
##################    I M A G E    A S    B A C K G R O U N D    W I T H    A U T O    R E S I Z E    ##################
########################################################################################################################


from PIL import ImageTk, Image

def image_as_background_with_resize():

    def resize_canvas_window(e):
        global image_original, image_resized, image_canvas

        image_original = Image.open("./assets/solutions/pic01.png")
        image_resized = image_original.resize((e.width, e.height), Image.ANTIALIAS)
        image_canvas = ImageTk.PhotoImage(image_resized)

        # Add the image back to canvas
        canvas.create_image(0, 0, image=image_canvas, anchor="nw")

        # Text has to be reapplied
        text_bg = canvas.create_text(200, 100, text="Hello World!", font=("Helvetica", 40), fill='white', anchor='nw')


    global image_canvas_start # Global to stop tkinter removing this image with it's garbage collector

    image_canvas_start = ImageTk.PhotoImage(file="./assets/solutions/pic01.png")

    canvas_window = tk.Toplevel(root)
    canvas_window.columnconfigure(0, weight=1)
    canvas_window.rowconfigure(0, weight=1)

    canvas_window.bind("<Configure>", resize_canvas_window)

    canvas = tk.Canvas(canvas_window, width=1280, height=800, bd=0)
    canvas.pack(fill="both", expand=True)
    canvas.create_image(0, 0, image=image_canvas_start, anchor="nw")

    # Create text with an outline
    text_bg = canvas.create_text(200, 100, text="Hello World!", font=("Helvetica", 40), fill='white', anchor='nw')


########################################################################################################################
########################################################################################################################
###########################################                                  ###########################################
###########################################    P O P U P    W I N D O W S    ###########################################
###########################################                                  ###########################################
########################################################################################################################
########################################################################################################################

########################################################################################################################
##################################################    D I A L O G    ###################################################
########################################################################################################################

from tkinter import filedialog

#######################################    A S K    O P E N    F I L E N A M E    ######################################

def open_txt():
    file_path = filedialog.askopenfilename(initialdir="./", title='Select File', filetypes=(("TXT files","*.txt"),("all files","*.*")))
    try:
        with open(file_path, "r") as file:
            content = file.read()
            cscroll()
            display_file = tk.Text(scroll)
            display_file.grid(row=0, column=0)
            display_file.insert("1.0", content)
    except (AttributeError, FileNotFoundError):
        statusbar.set("Open operation cancelled")
        return

######################################    A S K    O P E N    F I L E N A M E S    #####################################

def open_many_files():
    files = filedialog.askopenfilenames(title="Select Files")
    cscroll()
    for i, each_file in enumerate(files, 1):
        tk.Label(scroll, text=f"File {i}:\t{each_file}").grid(row=i, column=0, sticky="w")

##########################################    O P E N    D I R E C T O R Y    ##########################################

def open_directory():
    path_to_directory = filedialog.askdirectory()
    statusbar.set(path_to_directory)

#######################################    A S K    S A V E    F I L E N A M E    ######################################

def save_txt():
    def save():
        try:
            file_path = filedialog.asksaveasfilename(initialdir="./", title='Save File', defaultextension="txt", filetypes=(("TXT files", "*.txt"), ("all files", "*.*")))
            content = display_file.get("1.0", "end-1c")
            with open(file_path, "w") as file:
                file.write(content)
        except (AttributeError, FileNotFoundError):
            statusbar.set("Save operation cancelled")
            return
    cscroll()
    display_file = tk.Text(scroll)
    display_file.grid(row=0, column=0, sticky="NEWS")
    ttk.Button(scroll, text="Save", command=save).grid(row=1, column=0, sticky="EW")

########################################################################################################################
##############################################    M E S S A G E B O X    ###############################################
########################################################################################################################

from tkinter import messagebox

# Icons available: error, info, question, warning

#############################################    I N F O R M A T I O N    ##############################################

def messagebox_showerror():
    return messagebox.showerror(title='Error', message='Error')

def messagebox_showinfo():
    return messagebox.showinfo(title='Info', message='Information')

def messagebox_showwarning():
    return messagebox.showwarning(title='Warning', message='Warning')

#################################################    C H O I C E    ####################################################

def run_messagebox(function_to_call):
    if function_to_call():
        statusbar.set('Messagebox Confirmed')
    else:
        statusbar.set('Messagebox Denied')

def messagebox_askokcancel():
    return messagebox.askokcancel(title='OK/Cancel', message='OK or Cancel')

def messagebox_askretrycancel():
    return messagebox.askretrycancel(title='Retry/Cancel', message='Retry or Cancel')

def messagebox_askyesno():
    return messagebox.askyesno(title='Yes/No', message='Yes or No', icon='question')

def messagebox_askyesnocancel():
    return messagebox.askyesnocancel(title='Yes/No/Cancel', message='Yes or No or Cancel', icon='question')

########################################################################################################################
############################################    P O P U P    S I M P L E    ############################################
########################################################################################################################

def simple_popup_window():
    popup_window = tk.Toplevel()
    popup_window.geometry("350x200")
    popup_window.resizable(False, False)
    popup_window.title('Popup Window')

    frame_top = ttk.Frame(popup_window)
    frame_top.grid(row=0, column=0, sticky="EW")
    frame_top.columnconfigure(0, weight=1)
    ttk.Label(frame_top, text='Top Frame', anchor="center").grid(row=0, column=0, padx=10, pady=10, sticky="EW")

    frame_main = ttk.Frame(popup_window)
    frame_main.grid(row=1, column=0, sticky="NEWS")
    frame_main.columnconfigure(0, weight=1)
    ttk.Label(frame_main, text='Main Frame', anchor="center").grid(row=0, column=0, padx=10, pady=10, sticky='EW')

    frame_buttons = ttk.Frame(popup_window)
    frame_buttons.grid(row=2, column=0, sticky="EW")
    frame_buttons.columnconfigure(0, weight=1)
    popup_add_button = ttk.Button(frame_buttons, text='Add', command=lambda: statusbar.set("Add function"))
    popup_add_button.grid(row=0, column=1, sticky="w")
    popup_cancel_button = ttk.Button(frame_buttons, text="Cancel", command=lambda: popup_window.destroy())
    popup_cancel_button.grid(row=0, column=2, sticky="w")

########################################################################################################################
#######################################    R I G H T    C L I C K    M E N U    ########################################
########################################################################################################################

def rightclick_menu():

    def my_popup(event):
        rightclick_menu.tk_popup(event.x_root, event.y_root)

    rightclick_menu = tk.Menu(root, tearoff=False)
    rightclick_menu.add_command(label="Say Hello", command=lambda: statusbar.set("Hello"))
    rightclick_menu.add_command(label="Say Goodbye", command=lambda: statusbar.set("Goodbye"))
    rightclick_menu.add_separator()
    rightclick_menu.add_command(label="Exit", command=lambda: root.quit())

    root.bind("<Button-3>", my_popup)

########################################################################################################################
########################################################################################################################
########################################                                        ########################################
########################################    S Y S T E M    F U N C T I O N S    ########################################
########################################                                        ########################################
########################################################################################################################
########################################################################################################################

########################################################################################################################
###################################    A U T O    R E P E A T    F U N C T I O N S    ##################################
########################################################################################################################

add_number = 0
keep_running = True

def auto_repeat_function():
    def auto_repeat():
        global add_number
        global keep_running
        if keep_running:
            statusbar.set(f"Function ran {add_number} times.")
            add_number += 1
            root.after(1000, auto_repeat) # Time in milliseconds, 1000 = 1 sec

    def break_auto_repeat():
        global keep_running
        keep_running = False

    cscroll()
    ttk.Button(scroll, text='Start', command=lambda: auto_repeat()).pack(pady=5)
    ttk.Button(scroll, text='Stop', command=lambda: break_auto_repeat()).pack(pady=5)



########################################################################################################################
##############    A U T O P O P U L A T E    A N D    C R E A T E    C L I C K A B L E    L A B E L S    ###############
########################################################################################################################

def autopopulate(input_list):
    cscroll()
    for i, each_item in enumerate(input_list):
        create_labels(i, each_item)

def create_labels(i, each_item):
    label = ttk.Label(scroll, text=f'New label:\t{each_item}', anchor="w", wraplength=900, justify="center", font=("calibri", 20))
    label.grid(row=int(i), column=0, padx=10, pady=10, sticky="EW")
    label.bind("<Enter>", lambda *args: statusbar.set(f'Mouse over Label {each_item}.'))
    label.bind("<Leave>", lambda *args: statusbar.set(f'Mouse no longer over Label {each_item}.'))
    label.bind("<Button-1>", lambda *args: statusbar.set(f'Label {each_item} has just been clicked.'))

########################################################################################################################
#################################    B A C K U P    D A T A B A S E    -    A U T O    #################################
########################################################################################################################

def backup_database_auto():
    current_date_and_time = datetime.datetime.now()
    folder_name = current_date_and_time.strftime('%Y%m%d%H%M%S')
    path = f'.\\assets\\solutions\\{folder_name}'
    Path(path).mkdir(parents=True, exist_ok=True)
    shutil.copyfile("./assets/solutions/database_data.dat", f'./assets/solutions/{folder_name}/database_data_backup.dat')
    statusbar.set('Backup complete')

########################################################################################################################
###############################    B A C K U P    D A T A B A S E    -    S E L E C T    ###############################
########################################################################################################################

def backup_database_select():
    path = filedialog.askdirectory(title='Select folder')
    shutil.copyfile("./assets/solutions/database_data.dat", f'{path}/database_data.dat')
    statusbar.set('Backup complete')

########################################################################################################################
##################################    C H E C K    I F    F I L E    E D I T E D    ####################################
########################################################################################################################

def check_if_file_edited():
    def check():
        current_text = text_area.get("1.0", "end-1c")
        if hash(current_text) != hash(content):
            statusbar.set('Changed version')
        else:
            statusbar.set("Unchanged version")
    cscroll()
    text_area = tk.Text(scroll)
    text_area.grid(row=0, column=0)
    content = "Squirrels are the greatest animals!"
    text_area.insert("1.0", content)
    text_area.bind("<KeyRelease>", lambda x: check())

########################################################################################################################
#######################################    C H E C K    I N P U T    D A T E    ########################################
########################################################################################################################

import re

def check_input_date(date):
    date_pattern = re.compile(r"^\d{4}\-(0[1-9]|1[012])\-(0[1-9]|[12][0-9]|3[01])$")
    date_checked = date_pattern.fullmatch(date)
    return date_checked

########################################################################################################################
#######################################    C H E C K    I N P U T    T I M E    ########################################
########################################################################################################################

import re

def check_input_time(time):
    time_pattern = re.compile(r"^(0[0-9]|1[0-9]|2[0-3]):[0-5][0-9]$")
    time_check = time_pattern.fullmatch(time)
    return time_check

########################################################################################################################
######################################    C H E C K    I N P U T    E M A I L   ########################################
########################################################################################################################

import re

def check_input_email(email_address):
    email_str = '^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$'
    return re.search(email_str, email_address)

########################################################################################################################
######################################    C H E C K    I N P U T    P R I C E    #######################################
########################################################################################################################

import re

def check_input_price(price):
    price_pattern = re.compile(r"^\d+([.]\d{1,2})?$")
    price_checked = price_pattern.fullmatch(price)
    return price_checked

########################################################################################################################
#################    C H I L D R E N    -    C L E A R    A L L    I N    S C R O L L    W I N D O W    ################
########################################################################################################################

def cscroll():
    for child in scroll.winfo_children():
        child.destroy()

########################################################################################################################
###############    C H I L D R E N    -    C L E A R    A L L    I N    S E L E C T E D    W I N D O W    ##############
########################################################################################################################

def c(selected_widget):
    for child in selected_widget.winfo_children():
        child.destroy()

########################################################################################################################
#######################    C H I L D R E N    -    E D I T    A L L    I N    A    W I N D O W    ######################
########################################################################################################################

def edit_children():
    for child in scroll.winfo_children():
        child.grid_configure(pady=1)
        child.config(foreground='green') # .config is responsible for setting of label creation
        child.configure(font=('Arial', 40)) # .configure is responsible for grid settings

########################################################################################################################
##################    C H I L D R E N    -    F I N D    W I D G E T S    B Y    S T R I N G    I D    #################
########################################################################################################################

def find_children():
    cscroll()
    tk.Label(scroll).pack()
    ttk.Label(scroll).pack()
    tk.Button(scroll).pack()
    ttk.Button(scroll).pack()
    tk.Frame(scroll).pack()
    ttk.Frame(scroll).pack()
    tk.Entry(scroll).pack()
    ttk.Entry(scroll).pack()
    tk.Text(scroll).pack()
    ttk.Checkbutton(scroll).pack()
    tk.Checkbutton(scroll).pack()
    ttk.Combobox(scroll).pack()
    ttk.Separator(scroll).pack()
    tk.Listbox(scroll).pack()
    ttk.Radiobutton(scroll).pack()
    tk.Radiobutton(scroll).pack()
    ttk.Scale(scroll).pack()
    tk.Scale(scroll).pack()

    for i, each_child in enumerate(scroll.winfo_children()):
        print(str(type(each_child)))

        if str(type(each_child)) == "<class 'tkinter.Text'>":
            statusbar.set(f"Widget number {i} is a text widget.")

    ''' 
    P R I N T    R E S U L T: 
    <class 'tkinter.Label'> 
    <class 'tkinter.ttk.Label'> 
    <class 'tkinter.Button'> 
    <class 'tkinter.ttk.Button'> 
    <class 'tkinter.Frame'> 
    <class 'tkinter.ttk.Frame'> 
    <class 'tkinter.Entry'> 
    <class 'tkinter.ttk.Entry'> 
    <class 'tkinter.Text'> 
    <class 'tkinter.ttk.Checkbutton'> 
    <class 'tkinter.Checkbutton'> 
    <class 'tkinter.ttk.Combobox'> 
    <class 'tkinter.ttk.Separator'> 
    <class 'tkinter.Listbox'> 
    <class 'tkinter.ttk.Radiobutton'> 
    <class 'tkinter.Radiobutton'> 
    <class 'tkinter.ttk.Scale'> 
    <class 'tkinter.Scale'> 
    '''


########################################################################################################################
#####################    C H I L D R E N    -    R E A D    T E X T    F R O M    E N T R I E S    #####################
########################################################################################################################

def read_from_text_children():
    list_from_entries = []
    def read_and_create_list():
        for child in popup_window.winfo_children():
            if str(type(child)) == "<class 'tkinter.Text'>":
                word = child.get("1.0", "end-1c")
                list_from_entries.append(word)
        autopopulate(list_from_entries)
        popup_window.destroy()

    popup_window = tk.Toplevel()
    popup_window.columnconfigure(0, weight=1)
    tk.Text(popup_window, height=1).grid(row=0, column=0, padx=10, pady=(10, 0), sticky="EW")
    tk.Text(popup_window, height=1).grid(row=1, column=0, padx=10, pady=(5, 0), sticky="EW")
    tk.Text(popup_window, height=1).grid(row=2, column=0, padx=10, pady=(5, 0), sticky="EW")
    tk.Text(popup_window, height=1).grid(row=3, column=0, padx=10, pady=(5, 0), sticky="EW")
    tk.Text(popup_window, height=1).grid(row=4, column=0, padx=10, pady=(5, 10), sticky="EW")
    frame_buttons = ttk.Frame(popup_window)
    frame_buttons.grid(row=5, column=0, sticky="EW")
    frame_buttons.columnconfigure(0, weight=1)
    popup_add_button = ttk.Button(frame_buttons, text='Read', command=lambda: read_and_create_list())
    popup_add_button.grid(row=0, column=1, sticky="w")
    popup_cancel_button = ttk.Button(frame_buttons, text="Cancel", command=lambda: popup_window.destroy())
    popup_cancel_button.grid(row=0, column=2, sticky="w")

########################################################################################################################
#################    C H I L D R E N    -    S E L E C T    A C T I V E    N O T E B O O K    T A B    #################
########################################################################################################################

def select_active_child():
    def check():
        active_tab = scroll.nametowidget(notebook.select())
        statusbar.set(active_tab)
    cscroll()
    notebook = ttk.Notebook(scroll)
    notebook.grid(column=0, row=0, sticky="NEWS")
    tab_1 = ttk.Frame(notebook)
    notebook.add(tab_1, text="Tab One")
    tab_2 = ttk.Frame(notebook)
    notebook.add(tab_2, text="Tab Two")
    ttk.Button(scroll, text="Active tab", command=check).grid(row=1, column=0, sticky="EW")

########################################################################################################################
##########################    C O P Y    L A B E L    T E X T    T O    C L I P B O A R D    ###########################
########################################################################################################################

def copy_to_clipboard():
    def clear_clpb_and_copy(source):
        root.clipboard_clear()
        root.clipboard_append(source)
        statusbar.set(f'{source} copied to clipboard')
    cscroll()
    label_to_copy_from = ttk.Label(scroll, text=f'\n\n\n\nCLICK ME, COPY ME! 🐿', font=("Calibri", 40), anchor="center", width=40)
    label_to_copy_from.grid(row=0, column=0, sticky="NEWS")
    label_to_copy_from.bind("<Button-1>", lambda *args: clear_clpb_and_copy(label_to_copy_from['text']))

########################################################################################################################
################    D I S P L A Y    I N I T I A L    A N D    U P D A T E D    R O O T    S I Z E S    ################
########################################################################################################################

def window_size():
    def updated_size(event):
        statusbar.set(f"Initial W={initial_width}, H={initial_height} :: New W={event.width}, H={event.height}")
    root.update_idletasks()
    initial_width = root.winfo_width()
    initial_height = root.winfo_height()
    root.bind("<Configure>", updated_size)


########################################################################################################################
#######################    D I S P L A Y    W I N D O W    S I Z E    A N D    P O S I T I O N    ######################
########################################################################################################################

def display_window_size_and_position():
    def update_window_data():
        window_data_label.config(text=f"All data: {root.winfo_geometry()}\n"
                                      f"Window width: {root.winfo_width()}\n"
                                      f"Window height: {root.winfo_height()}\n"
                                      f"Window position X: {root.winfo_x()}\n"
                                      f"Window position Y: {root.winfo_y()}")

    cscroll()
    window_data_label = tk.Label(scroll, text='')
    window_data_button = tk.Button(scroll, text='Display current window data', command=update_window_data)
    window_data_button.pack(pady=20)
    window_data_label.pack(pady=20)

########################################################################################################################
###############################    D Y N A M I C A L L Y    R E S I Z E    W I N D O W    ##############################
########################################################################################################################

def resize_window():
    def accept_resize():
        try:
            root.geometry(f'{window_width.get()}x{window_height.get()}')
            statusbar.set(f"New width = {window_width.get()} / New height = {window_height.get()}")
        except:
            statusbar.set('Enter values')
    cscroll()
    window_width = tk.Entry(scroll)
    window_width.pack()
    window_height = tk.Entry(scroll)
    window_height.pack()
    ttk.Button(scroll, text='Resize', command=lambda: accept_resize()).pack()

########################################################################################################################
##########    P O S I T I O N    W I N D O W    I N    T H E    M I D D L E    O F    T H E    S C R E E N    ##########
########################################################################################################################

def position():
    screen_width = root.winfo_screenwidth()
    screen_height = root.winfo_screenheight()
    app_width = 1400
    app_height = 850
    x = int((screen_width / 2) - (app_width / 2))
    y = int((screen_height / 2) - (app_height / 2))
    root.geometry(f"{app_width}x{app_height}+{x}+{y}")

########################################################################################################################
####################################    R E M O V E    S I N G L E    W I D G E T    ###################################
########################################################################################################################

def remove_widget():
    cscroll()
    ttk.Button(scroll, text="Remove Widget Label", command=lambda: label_to_remove.grid_forget()).grid(row=0, column=0)
    label_to_remove = tk.Label(scroll, text="🏵", font=("Calibri, 100"))
    label_to_remove.grid(row=1, column=0)

########################################################################################################################
#####################################    S A V E / L O A D    -    P I C K L E    ######################################
########################################################################################################################

import pickle

try:
    data_save = open("./assets/solutions/DATA_SAMPLE.DAT", "rb")
    SAMPLE_LIST = pickle.load(data_save)
    data_save.close()
except:
    SAMPLE_LIST = ['🦍', '🦏', '🐈', '🐿', '🐳', '🐎', '🐋', '🐕', '🦇', '🦊']

def save_all():
    cscroll()
    data_save = open("./assets/solutions/DATA_SAMPLE.DAT", "wb")
    pickle.dump(SAMPLE_LIST, data_save)
    data_save.close()
    statusbar.set("All changes saved")

########################################################################################################################
###############################################    T I M E S T A M P    ################################################
########################################################################################################################

def timestamp():
    return datetime.datetime.now().strftime('%Y%m%d%H%M%S')

########################################################################################################################
###############################################    T H R E A D I N G    ################################################
########################################################################################################################

def task_threading():
    cscroll()
    # Import threading
    import threading

    # This function, without threading would stop the app for 5 seconds
    def five_seconds():
        import time
        time.sleep(5)
        statusbar.set("5 Seconds is up!")

    # Without threading this function wouldn't run until first function (if run) finishes
    def random_number():
        statusbar.set(f"Button pressed")

    # Call function through threading
    tk.Button(scroll, text='5 Seconds', command=lambda: threading.Thread(target=five_seconds).start()).pack(pady=20)
    tk.Button(scroll, text="Try Pressing", command=random_number).pack(pady=20)



########################################################################################################################
########################################################################################################################
#################################################                     ##################################################
#################################################    W I D G E T S    ##################################################
#################################################                     ##################################################
########################################################################################################################
########################################################################################################################

def widget_checkbutton():
    def print_selection():
        statusbar.set(f'Checkbutton 1 is in position {option1_checkbutton.get()}. / Checkbutton 2 is in position {option2_checkbutton.get()}.')
    option1_checkbutton = tk.StringVar()
    option2_checkbutton = tk.StringVar()
    cscroll()
    ttk.Checkbutton(scroll, text='Option 1', variable=option1_checkbutton, onvalue='On', offvalue='Off', command=print_selection).pack(padx=50, pady=50)
    ttk.Checkbutton(scroll, text='Option 2', variable=option2_checkbutton, onvalue='On', offvalue='Off', command=print_selection).pack(padx=50, pady=50)

def widget_combobox():
    def print_selection(event):
        statusbar.set(f'Combobox current selections is: {option_combobox.get()}.')
    option_combobox = tk.StringVar()
    cscroll()
    combobox = ttk.Combobox(scroll, textvariable=option_combobox, font=("Calibri", 14), state='readonly')
    combobox['values'] = ("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday")
    combobox.pack(padx=50, pady=50)
    combobox.bind("<<ComboboxSelected>>", print_selection)

def widget_dropdown_box():
    clicked = tk.StringVar()
    cscroll()
    options = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
    clicked.set(options[0])
    tk.OptionMenu(scroll, clicked, *options).pack() # without * before options I would get all list as one string
    tk.Button(scroll, text="Show Selection", command=lambda: tk.Label(scroll, text=clicked.get()).pack()).pack()

def entry_as_password():
    entry_password = tk.StringVar()
    cscroll()
    entry = tk.Entry(scroll, textvariable=entry_password, show="*")
    entry.pack()
    tk.Button(scroll, text="Display Password", command=lambda: statusbar.set(f"Password: {entry_password.get()}")).pack()

def entry_auto_delete():
    cscroll()
    entry_text = tk.Entry(scroll)
    entry_text.insert(0, "Enter password...")
    entry_text.pack()
    entry_text.bind("<Button-1>", lambda event: entry_text.delete(0, "end"))

def widget_labelframe():
    # https://anzeljg.github.io/rin2/book2/2405/docs/tkinter/labelframe.html
    cscroll()
    label_frame = tk.LabelFrame(scroll,
                                text='Label Frame',
                                labelanchor='n',
                                background='white',
                                padx=10,
                                pady=10,
                                width=340,
                                height=205)
    label_frame.grid(row=0, column=0, sticky="NW", padx=5, pady=5)

def widget_label_wrap():
    cscroll()
    label_wrap = tk.Label(scroll,
                            text=('Just add wrap=width_of_screen_in_pixels. To wrap text in canvas set width to number of characters: canvas.create_text(x, y, width=80)\t' * 20),
                            wrap=680,
                            background='white',
                            padx=10,
                            pady=10)
    label_wrap.grid(row=0, column=0, sticky="NW", padx=5, pady=5)



def widget_listbox():
    def print_selection(event):
        selected_items = listbox.curselection()
        delete_element_button.config(text=f'Delete selected: {SAMPLE_LIST[selected_items[0]]}')
        for each_item in selected_items:
            statusbar.set(f'Currently selected animals are: {listbox.get(each_item)}.')

    animals = tk.StringVar(value=SAMPLE_LIST)
    cscroll()
    listbox = tk.Listbox(scroll, listvariable=animals, height=10, font=("Calibri", 24))
    listbox["selectmode"] = "extended" # skip to allow only one selection
    listbox.pack(padx=50, pady=50)
    listbox.bind("<<ListboxSelect>>", print_selection)

    delete_element_button = ttk.Button(scroll, text=f'Delete selected', command=lambda: listbox.delete("anchor"))
    delete_element_button.pack()
    ttk.Button(scroll, text=f'Delete all', command=lambda: listbox.delete(0, "end")).pack()

def widget_listbox_advanced_with_scrollbar():
    cscroll()
    frame_to_insert_widget = scroll

    listbox_sample_list = ['🐳', '🐎', '🐕', '🦇', '🦊']
    animals = tk.StringVar(value=listbox_sample_list)

    listbox = tk.Listbox(frame_to_insert_widget, listvariable=animals, height=10, font=("Calibri", 24))
    listbox["selectmode"] = "single"  # skip to allow only one selection
    listbox.selection_set(first=0)  # select the first item
    listbox.insert("end", f"Autoselected item: {listbox.selection_get()}")

    listbox.grid(row=0, column=0, sticky="news")
    listbox.bind("<<ListboxSelect>>", lambda event: print(
        listbox.insert("end", f"Clicked item: {listbox_sample_list[listbox.curselection()[0]]}")))
    # listbox.bind("<Double-Button-1>", lambda event: print(f"{listbox.curselection()} doubleclicked!"))

    # Entire widget:
    listbox.config(width=20)
    listbox.config(foreground="black")  # Font colour
    listbox.config(background="white")
    listbox.config(highlightcolor='#3c3c3c')  # colour of the frame of the entire widget when selected
    listbox.config(highlightthickness=0)  # thickness of the widget frame when selected
    listbox.config(highlightbackground="white")  # colour of the widget frame when not selected
    listbox.config(relief="flat")

    # Individual entries
    listbox.config(activestyle='none')  # do not underline selected items (dotbox, none, or underline)
    listbox.config(selectborderwidth=0)  # This is border thickness of individual entries
    listbox.config(selectbackground="#3c3c3c")  # selected background colour
    listbox.config(selectforeground="white")  # selected font colour

    scrollbar = ttk.Scrollbar(frame_to_insert_widget, orient="vertical", command=listbox.yview)
    scrollbar.grid(row=0, column=1, sticky="NS")
    listbox["yscrollcommand"] = scrollbar.set

def widget_notebook(): # Important options: notebook.forget(tab_id) / notebook.hide(tab_id) / notebook.select(tab_id)
    cscroll()
    notebook = ttk.Notebook(scroll)
    notebook.grid(column=0, row=0, sticky="NEWS")
    tab_1 = ttk.Frame(notebook, width=1000, height=500)
    notebook.add(tab_1, text="Tab One")
    tab_2 = ttk.Frame(notebook, width=1000, height=500)
    notebook.add(tab_2, text="Tab Two")

def paned_window():
    # http://effbot.org/tkinterbook/panedwindow.htm
    cscroll()
    # New frame to force scroll to a larger size
    large_frame = tk.Frame(scroll)
    large_frame.grid(row=0, column=0, sticky="NEWS")
    large_frame.columnconfigure(0, minsize=500, weight=1)
    large_frame.rowconfigure(0, minsize=500, weight=1)

    panel_1 = tk.PanedWindow(large_frame, bd=4, relief='flat', bg='grey80')
    panel_1.grid(row=0, column=0, sticky="NEWS")  # Only the first one should use grid or pack

    label_left = tk.Label(panel_1, text="PANEL 1")
    # label_left.pack() would add it on top of the panel, add() function makes it a part of the panel (same colour)
    panel_1.add(label_left)

    # The second panel I should position inside the first one
    panel_2 = tk.PanedWindow(panel_1, orient="vertical", bd=4, relief='flat', bg='grey80')
    panel_1.add(panel_2)

    label_top = tk.Label(panel_2, text="PANEL 2")
    panel_2.add(label_top)

    label_bottom = tk.Label(panel_2, text="PANEL 3")
    panel_2.add(label_bottom)

def widget_progress_bar():
    def stop():
        progress.stop()  # this is how to stop the progress bar from animating
    cscroll()
    progress = ttk.Progressbar(scroll, orient="horizontal", length=500, mode="determinate")
    progress.pack(pady=50)
    # mode="indeterminate" full bar
    # mode="indeterminate" dot
    progress['value'] = 20  # set value to 20
    progress.start(10)  # make it animate, 10 points increments. This is just an animation, showing that something is happening
    tk.Button(scroll, text='Stop', command=stop).pack()

def widget_radiobutton():
    def print_selection():
        statusbar.set(f'This is your animal: {option_radiobutton.get()}')
    cscroll()
    option_radiobutton = tk.StringVar()
    ttk.Radiobutton(scroll, text="My favourite animal is a squirrel", variable=option_radiobutton, value="🐿", command=print_selection).grid(row=0, column=0, padx=50, pady=25)
    ttk.Radiobutton(scroll, text="My favourite animal is a koala", variable=option_radiobutton, value="🐨", command=print_selection).grid(row=1, column=0, padx=50, pady=25)
    ttk.Radiobutton(scroll, text="My favourite animal is a shark", variable=option_radiobutton, value="🦈", command=print_selection).grid(row=2, column=0, padx=50, pady=25)

def widget_scales():
    def print_selection(event):
        statusbar.set(f"Current selected value is: {option_scales.get()}")
    cscroll()
    option_scales = tk.DoubleVar()
    tk.Scale(scroll, orient="horizontal", from_=0, to=10, variable=option_scales, command=print_selection, length=500, label='Scales 0 - 10').grid(row=0, column=0, sticky="NEWS", padx=50, pady=50)
    tk.Label(scroll, textvariable=option_scales).grid(row=1, column=0, sticky="NEWS", padx=50, pady=50)
    # Options: orient='horizontal'

def widget_scrollbar():
    cscroll()
    text = tk.Text(scroll)
    text.grid(row=0, column=0, sticky="NEWS")
    text.insert("1.0", f'{str(SAMPLE_LIST)}\n' * 100)
    scrollbar = ttk.Scrollbar(scroll, orient="vertical", command=text.yview)
    scrollbar.grid(row=0, column=1, sticky="NS")
    text["yscrollcommand"] = scrollbar.set

def widget_spinbox():
    def print_selection():
        statusbar.set(f"Current selected value is: {option_spinbox.get()}")
    cscroll()
    option_spinbox = tk.IntVar(value=5)
    spinbox = ttk.Spinbox(
        scroll,
        from_=0, to=10, # I can also use values=(5,10,15,25,30)
        textvariable=option_spinbox,
        wrap=True,  # start over when limit is reached
        command=print_selection)
    spinbox.grid(row=0, column=0, padx=50, pady=50)


def widget_treeview():
    def delete_all():
        for record in tree.get_children():
            tree.delete(record)

    def delete_selected():
        x = tree.selection()[0]
        tree.delete(x)

    def tree_action(event):
        selected_id = tree.selection()
        selected_item = tree.item(selected_id, "text")

        statusbar.set(selected_item)

    def change_colours():
        # Create striped row tags, to make it work I need to select a style

        style.theme_use("clam")
        style.configure("Treeview", background="silver", foreground='black', rowheight=50, fieldbackground='silver')
        style.map("Treeview", background=[('selected', 'green')])


        tree.tag_configure('oddrow', background='white')
        tree.tag_configure('evenrow', background='lightblue')

        count = 0

        for each_row in list_emoji:
            if count % 2 == 0:  # this is how I tag entries with different colours
                tree.insert(parent=misi_misia, index='end', text="Emoticons", values=(each_row[0], each_row[0], each_row[0]), tags=('evenrow', ))
            else:
                tree.insert(parent=misi_misia, index='end', text="Emoticons", values=(each_row[0], each_row[0], each_row[0]), tags=('oddrow', ))
            count += 1

    '''Treeview modes: 
        my_tree.config(selectmode=" ... ") 
            none - selecting disabled 
            extended - (default) select multiple items with control 
            browse - select only one item, even if controll pressed'''

    cscroll()
    style = ttk.Style(root)
    style.configure("Treeview", rowheight=50)

    tree_frame = tk.Frame(scroll)
    tree_frame.grid(row=0, column=0, sticky="NEWS")
    tree_frame.columnconfigure(0, minsize=1000, weight=1)
    tree_scroll = ttk.Scrollbar(tree_frame)
    tree_scroll.grid(row=0, column=1, sticky="NS")


    tree = ttk.Treeview(tree_frame, yscrollcommand=tree_scroll.set)
    tree_scroll.config(command=tree.yview)

    # Define Columns
    tree["columns"] = ("1", "2", "3")

    ''' 
    tree["show"] = "headings" 
        The first empty column is the identifier, I can disable it using tree["show"] = "headings" 
        I can also leave it and replce it with an image 
    '''

    # Format Columns
    tree.column("#0", width=50)
    tree.column("1", width=90, minwidth=90, anchor="c")
    tree.column("2", width=200, minwidth=100, anchor="c")
    tree.column("3", width=150, anchor="c")

    # Create Headings
    tree.heading("1", text="Category")
    tree.heading("2", text="Priority")
    tree.heading("3", text="Added")

    # Add Data

    # Level 1
    misi = tree.insert(parent="", index='end', text="    Renatka", image=picture_renatka)
    tree.insert(misi, "end", text='Test', values=("Personal", "High", "2020-08-12"))

    misia = tree.insert(parent="", index='end', text="    Krzysio", image=picture_krzysio)
    tree.insert(parent=misia, index="end", text='Test', values=("Coding", "Low", "2020-08-12"))
    tree.insert(parent=misia, index="end", text='Test', values=("Personal", "High", "2020-08-12"))

    misi_misia = tree.insert(parent="", index='end', text="    Wspólne", image=picture_both)
    tree.insert(parent=misi_misia, index="end", text='Test', values=("House", "Medium", "2020-08-12"))




    tree.grid(row=0, column=0, sticky="NEWS")

    ttk.Button(tree_frame, text="🗑 All", command=lambda: delete_all()).grid(row=1, column=0, sticky="EW")
    ttk.Button(tree_frame, text="🗑 Selected", command=lambda: delete_selected()).grid(row=2, column=0, sticky="EW")
    ttk.Button(tree_frame, text="Change Colours", command=lambda: change_colours()).grid(row=3, column=0, sticky="EW")

    tree.bind("<Double-1>", tree_action)



########################################################################################################################
########################################################################################################################
#########################################                                      #########################################
#########################################    O T H E R    F U N C T I O N S    #########################################
#########################################                                      #########################################
########################################################################################################################
########################################################################################################################

########################################################################################################################
###########################################    B I N D    A C T I O N S    #############################################
########################################################################################################################

def bind_actions():
    cscroll()
    action_entry = tk.Entry(scroll)
    action_entry.pack()

    action_entry.bind("<Button-1>", lambda event: statusbar.set("LMB Clicked"))
    action_entry.bind("<Button-2>", lambda event: statusbar.set("MMB Clicked"))
    action_entry.bind("<Button-3>", lambda event: statusbar.set("RMB Clicked"))
    action_entry.bind("<Enter>", lambda event: statusbar.set("Mouse over"))
    action_entry.bind("<Leave>", lambda event: statusbar.set("Mouse away"))
    action_entry.bind("<FocusIn>", lambda event: statusbar.set("Selected with tab"))
    action_entry.bind("<FocusOut>", lambda event: statusbar.set("Deselected with tab"))
    action_entry.bind("<Return>", lambda event: statusbar.set("Enter Key"))

    action_entry.bind("<Key>", lambda event: statusbar.set(f"{event.char} clicked"))
        # event.char will display key pressed when "<Key>" binding used



########################################################################################################################
##########################################    C O L O U R    P I C K E R    ############################################
########################################################################################################################

from tkinter import colorchooser

def pick_colour():
    cscroll()
    my_colour = colorchooser.askcolor()
    tk.Label(scroll, text=f'You selected: {my_colour[-1]}', bg=my_colour[-1], font=("Calibri", 40)).pack()

########################################################################################################################
####################################################    F O N T S    ###################################################
########################################################################################################################

from tkinter import font
def tkinter_fonts():
    def copy_font_name(font_name):
        root.clipboard_clear()
        root.clipboard_append(font_name)


    def create_font_labels(i, selected_font):
        my_font = font.Font(family=selected_font, size=24)
        font_label = tk.Label(scroll, text=f"{selected_font} 1234568790", font=my_font, anchor='w')
        font_label.grid(row=i, column=0, sticky="EW", padx=10)
        font_label.bind("<Button-1>", lambda event: copy_font_name(selected_font))
    cscroll()

    for i, each_font in enumerate(font.families()):
        create_font_labels(i, each_font)

########################################################################################################################
############################################    O P E N    W E B S I T E    ############################################
########################################################################################################################

import webbrowser
def open_website():
    cscroll()
    tk.Button(scroll, text="Google", command=lambda: webbrowser.open("https://google.com/")).pack()

########################################################################################################################
############################    P L A Y    S O U N D S    U S I N G    P L A Y S O U N D    ############################
########################################################################################################################

from playsound import playsound
import threading

def play_sound_playsound():
    def play_now():
        playsound('./assets/solutions/music.mp3')

    cscroll()
    # Threading used, because otherwise app would become unresponsive for the duration of music
    ttk.Button(scroll, text="Play music", command=lambda: threading.Thread(target=play_now).start()).pack(pady=20)

########################################################################################################################
###############################    P L A Y    S O U N D S    U S I N G    P Y G A M E    ###############################
########################################################################################################################

# pip install pygame

def play_sound_pygame():
    def play_song():
        pygame.mixer.music.load("./assets/solutions/stormwind.mp3")
        pygame.mixer.music.play(loops=0)  # loops - number of time play

        # Display song length
        from mutagen.mp3 import MP3
        audio_file = MP3("./assets/solutions/stormwind.mp3")
        song_length = float(audio_file.info.length)
        import time
        song_length_as_time = time.strftime('%H:%M:%S', time.gmtime(song_length))
        statusbar.set(f"Song length: {song_length_as_time}")

    def stop_song():
        pygame.mixer.music.stop()

    import pygame

    pygame.mixer.init() # Initialize Pygame sound module

    cscroll()
    tk.Button(scroll, text='Play song', command=play_song).pack(pady=20)
    tk.Button(scroll, text='Stop', command=stop_song).pack(pady=20)

########################################################################################################################
#######################################    R U N    D O S    C O M M A N D S    ########################################
########################################################################################################################

import os
def run_dos_commands():
    def dos_command(command, option):
        try:
            if option == 'remain':
                os.system(f'cmd /k "{command}"')
            elif option == 'terminate':
                os.system(f'cmd /k "{command}"')
        except:
            statusbar.set("Error")

    cscroll()
    entry_command = ttk.Entry(scroll)
    entry_command.pack()
    entry_option = ttk.Entry(scroll)
    entry_option.pack()
    tk.Button(scroll,
              text='Run command & option (remain or terminate',
              command=lambda: dos_command(entry_command.get(), entry_option.get())).pack()

########################################################################################################################
########################################################################################################################
#########################################                                     ##########################################
#########################################    T I M E    A N D    D A T E S    ##########################################
#########################################                                     ##########################################
########################################################################################################################
########################################################################################################################

########################################################################################################################
######################################    C O U N T    D O W N    T I M E R    #########################################
########################################################################################################################

def timer_build_ui_and_start():

    import math
    global my_timer
    my_timer = None

    def timer_count_down_reset():
        global my_timer
        root.after_cancel(my_timer)
        timer_label['text'] = f'00:00'
        timer_start_button['state'] = 'normal'
        

    def timer_count_down_start(count=60):
        global my_timer
        timer_start_button['state'] = 'disabled'
        count_min = math.floor(count / 60)
        count_sec = count % 60
        if count_sec < 10:
            count_sec = f"0{count_sec}"
        timer_label['text'] = f'{count_min}:{count_sec}'

        if count > 0:
            my_timer = root.after(1000, timer_count_down_start, count-1)
        else:
            statusbar.set("Countdown finished!")

    cscroll()
    timer_label = tk.Label(scroll, text="Timer", font=('Calibri', 40))
    timer_label.grid(row=0, column=1, sticky='ew')
    timer_start_button = tk.Button(scroll, text='Start', bg='white', borderwidth=0, width=7, command= lambda: timer_count_down_start(15))
    timer_start_button.grid(column=0, row=2)
    tk.Button(scroll, text='Reset', bg='white', borderwidth=0, width=7, command= timer_count_down_reset).grid(column=2, row=2)

########################################################################################################################
#########################################    D A T E S    F U N C T I O N S    #########################################
########################################################################################################################

import datetime
from dateutil.relativedelta import relativedelta
import calendar

def dates():

    # Date and Time
    current_date = datetime.date.today() # print: 2022-10-31 | type: <class 'datetime.date'>
    current_date_and_time = datetime.datetime.now() # print: 2022-10-31 23:43:06.346288 | type: <class 'datetime.date'>
    add_one_year_one_month_one_day_to_current_date = datetime.date.today() + relativedelta(years=1, months=1, days=1) # print: 2023-12-01 | type: <class 'datetime.date'>

    # Day  
    current_day_of_the_month = datetime.date.today().day # print: 31 | type: <class 'int'>
    tomorrow = datetime.date.today() + relativedelta(days=1) # print: 2022-11-01 | type: <class 'datetime.date'>

    # Week
    name_day_of_the_week = calendar.day_name[datetime.date.today().weekday()] # print: Monday | type: <class 'str'>

    # Month
    current_month_number = datetime.date.today().month # print: 10 | type: <class 'int'>
    number_of_days_in_this_month = calendar.monthrange(datetime.date.today().year, datetime.date.today().month)[-1] # print: 31 | type: <class 'int'>
    days_left_this_month = (calendar.monthrange(datetime.date.today().year, datetime.date.today().month)[-1]) - datetime.date.today().day # print: 0 | type: <class 'int'>
    next_month_date_one_month_added = datetime.date.today() + relativedelta(months=1) # print: 2022-11-30 | type: <class 'datetime.date'> 
    current_month_first_day_date = datetime.datetime.now().date().replace(day=1) # print: 2022-10-01 | type: <class 'datetime.date'>
    previous_month_date_one_month_substracted = datetime.date.today() - relativedelta(months=1) # print: 2022-09-30 | type: <class 'datetime.date'>
    month_previous_number = datetime.date.today().month - 1 # print: 9 | type: <class 'int'>
    previous_month_first_day_date = (datetime.date.today() - relativedelta(months=1)).replace(day=1) # print: 2022-09-01 | type: <class 'datetime.date'>

    # Year
    current_year = datetime.date.today().year # print: 2022 | type: <class 'int'>
    current_year_start = datetime.datetime.now().date().replace(month=1, day=1) # print: 2022-01-01 | type: <class 'datetime.date'>
    number_of_days_passed_in_this_year = datetime.datetime.now().timetuple().tm_yday # print: 304 | type: <class 'int'>
    number_of_days_in_this_year = 365 + calendar.isleap(datetime.date.today().year) # print: 365 | type: <class 'int'>
    number_of_days_remaining_in_this_year = (365 + calendar.isleap(datetime.date.today().year)) - (datetime.datetime.now().timetuple().tm_yday) # print: 61 | type: <class 'int'>
    same_date_as_today_but_next_year = datetime.date.today() + relativedelta(years=1) # print: 2023-10-31 | type: <class 'datetime.date'>  
    next_year_just_the_number =  (datetime.date.today() + relativedelta(years=1)).year # print: 2023 | type: <class 'int'>

    # Other
    timestamp_ymdhms = current_date_and_time.strftime('%Y%m%d%H%M%S') # print: 20221031235546 | type: <class 'str'>
    string_to_date = datetime.datetime.strptime('2020-01-01', '%Y-%m-%d').date() # print: 2020-01-01 | type: <class 'datetime.date'>

    dates_text = ''' 
    # Date and Time 
    current_date \t = datetime.date.today() \t # print: 2022-10-31 | type: <class 'datetime.date'> 
    current_date_and_time \t = datetime.datetime.now() \t # print: 2022-10-31 23:43:06.346288 | type: <class 'datetime.date'> 
    add_one_year_one_month_one_day_to_current_date \t = datetime.date.today() + relativedelta(years=1, months=1, days=1) \t # print: 2023-12-01 | type: <class 'datetime.date'> 
 
    # Day   
    current_day_of_the_month \t = datetime.date.today().day \t # print: 31 | type: <class 'int'> 
    tomorrow \t = datetime.date.today() + relativedelta(days=1) \t # print: 2022-11-01 | type: <class 'datetime.date'> 
 
    # Week 
    name_day_of_the_week \t = calendar.day_name[datetime.date.today().weekday()] \t # print: Monday | type: <class 'str'> 
 
    # Month 
    current_month_number \t = datetime.date.today().month \t # print: 10 | type: <class 'int'> 
    number_of_days_in_this_month \t = calendar.monthrange(datetime.date.today().year, datetime.date.today().month)[-1] \t # print: 31 | type: <class 'int'> 
    days_left_this_month \t = (calendar.monthrange(datetime.date.today().year, datetime.date.today().month)[-1]) - datetime.date.today().day \t # print: 0 | type: <class 'int'> 
    next_month_date_one_month_added \t = datetime.date.today() + relativedelta(months=1) \t # print: 2022-11-30 | type: <class 'datetime.date'>  
    current_month_first_day_date \t = datetime.datetime.now().date().replace(day=1) \t # print: 2022-10-01 | type: <class 'datetime.date'> 
    previous_month_date_one_month_substracted \t = datetime.date.today() - relativedelta(months=1)  \t# print: 2022-09-30 | type: <class 'datetime.date'> 
    month_previous_number \t = datetime.date.today().month - 1 \t # print: 9 | type: <class 'int'> 
    previous_month_first_day_date \t = (datetime.date.today() - relativedelta(months=1)).replace(day=1) \t # print: 2022-09-01 | type: <class 'datetime.date'> 
 
    # Year 
    current_year \t = datetime.date.today().year \t # print: 2022 | type: <class 'int'> 
    current_year_start \t = datetime.datetime.now().date().replace(month=1, day=1) \t # print: 2022-01-01 | type: <class 'datetime.date'> 
    number_of_days_passed_in_this_year \t = datetime.datetime.now().timetuple().tm_yday \t # print: 304 | type: <class 'int'> 
    number_of_days_in_this_year \t = 365 + calendar.isleap(datetime.date.today().year) \t # print: 365 | type: <class 'int'> 
    number_of_days_remaining_in_this_year \t = (365 + calendar.isleap(datetime.date.today().year)) - (datetime.datetime.now().timetuple().tm_yday) \t # print: 61 | type: <class 'int'> 
    same_date_as_today_but_next_year \t = datetime.date.today() + relativedelta(years=1) \t # print: 2023-10-31 | type: <class 'datetime.date'>   
    next_year_just_the_number \t =  (datetime.date.today() + relativedelta(years=1)).year \t # print: 2023 | type: <class 'int'> 
 
    # Other 
    timestamp_ymdhms \t = current_date_and_time.strftime('%Y%m%d%H%M%S') \t # print: 20221031235546 | type: <class 'str'> 
    string_to_date \t = datetime.datetime.strptime('2020-01-01', '%Y-%m-%d').date() \t # print: 2020-01-01 | type: <class 'datetime.date'> 
    '''

    cscroll()
    ttk.Label(scroll, text=f"{dates_text}").grid(row=0, column=0, sticky="EW")


    #############################################    A D D    H O U R S    #############################################
    date_entry = '2020-01-01'
    time_entry = '10:00'

    # Add hours (I need both date and time given)
    user_time = f"{date_entry} {time_entry}:00"

    convert_to_datetime = datetime.datetime.strptime(user_time, '%Y-%m-%d %H:%M:%S')
    add_one_hour = convert_to_datetime + relativedelta(hours=1)

    ############################ CALCULATE MONTHS BETWEEN NOW AND THE BEGINNING OF THE YEAR ############################

    # 1.) Create strings for each month starting with the first day
    list_of_months_string = [] # ['2020-1-01', '2020-2-01', '2020-3-01', '2020-4-01']
    for each_month in range(current_month_number):
        list_of_months_string.append(f"{current_year}-{each_month+1}-01")

    # 2.) Create list of datetime ranges (first and last day of month) for each month from the previous list
    list_of_month_ranges_datetime = []  # [[datetime.date(2020, 1, 1), datetime.date(2020, 1, 31)], ... and so on
    for each_month_date in list_of_months_string:
        list_of_month_ranges_datetime.append([datetime.datetime.strptime(each_month_date, '%Y-%m-%d').date(), datetime.datetime.strptime(each_month_date, '%Y-%m-%d').date() + relativedelta(months=1, days=-1)])

    # 3.) Use the previous list to display each available month's name
    list_of_month_names = [] # ['January', 'February', 'March', 'April']
    for each_month_datetime in list_of_month_ranges_datetime:
        list_of_month_names.append(each_month_datetime[0].strftime("%B"))


########################################################################################################################
##################################    D A T E S    F U N C T I O N S    S I M P L E    #################################
########################################################################################################################

# https://docs.python.org/3/library/time.html
# https://docs.python.org/3/library/time.html
import time

def time_calculations_simple():
    def calculate_time():
        year = time.strftime("%Y")
        month = time.strftime("%m")
        day = time.strftime("%d")
        month_name = time.strftime("%B")
        day_of_week = time.strftime("%A")
        hour = time.strftime("%H")
        minute = time.strftime("%M")
        second = time.strftime("%S")
        timezone = time.strftime("%Z")
        auto_hour_minute_second = time.strftime("%X")

        my_label.config(text=f"{year}.{month}.{day}\n"
                             f"{month_name} - {day_of_week}\n"
                             f"{hour}:{minute}:{second}\n"
                             f"{auto_hour_minute_second}\n"
                             f"{timezone}")

        my_label.after(1000, calculate_time)

    cscroll()
    my_label = tk.Label(scroll, text='', font=("Calibri", 24))
    my_label.pack(pady=50, padx=50)
    calculate_time()

########################################################################################################################
##############################################    T I M E    S I N C E    ##############################################
########################################################################################################################

import datetime

def time_since_event():
    def calculate_time_since(date_to_check):
        if datetime.date.today() >= date_to_check:
            days_since = (datetime.date.today() - date_to_check).days
            statusbar.set(f"Days since: {days_since}")
            
            time_since_label['text'] = ''' 
            def calculate_time_since(date_to_check): 
            \tif datetime.date.today() >= date_to_check: 
            \t\tdays_since = (datetime.date.today() - date_to_check).days 
            \t\treturn days_since 
 
            calculate_time_since(datetime.date(year=1981, month=9, day=28)) 
             
            print: 15009 
            type: <class 'int'> 
            '''

    cscroll()
    time_since_label = tk.Label(scroll, text='', font=("Calibri", 10), anchor='nw', justify='left')
    time_since_label.pack(pady=20, padx=20)
    calculate_time_since(datetime.date(year=1981, month=9, day=28))

########################################################################################################################
##############################################    T I M E    U N T I L    ##############################################
########################################################################################################################

def time_until_event():
    def calculate_time_until(date_to_check):
        if datetime.date.today() > date_to_check: # Check if date isn't in the past
            date_to_check = date_to_check.replace(year=datetime.date.today().year) # if it is change it's year to current year
        if datetime.date.today() > date_to_check: # Check again after changing year if days or months are still in the past
            date_to_check = date_to_check.replace(year=datetime.date.today() + relativedelta(years=1)) # If yes change the year to next
        days_remaining = (date_to_check - datetime.date.today()).days
        statusbar.set(f"Days until: {days_remaining}")
            
        time_until_label['text'] = ''' 
        def calculate_time_until(date_to_check): 
        \t    if datetime.date.today() > date_to_check: # Check if date isn't in the past 
        \t\t        date_to_check = date_to_check.replace(year=datetime.date.today().year) # if it is change it's year to current year 
        \t    if datetime.date.today() > date_to_check: # Check again after changing year if days or months are still in the past 
        \t\t        date_to_check = date_to_check.replace(year=datetime.date.today() + relativedelta(years=1)) # If yes change the year to next 
        \t    days_remaining = (date_to_check - datetime.date.today()).days 
        \t    return days_remaining 
 
        calculate_time_until(datetime.date(year=2081, month=9, day=28)) 
 
        print: 21516 
        type: <class 'int'> 
        '''

    cscroll()
    time_until_label = tk.Label(scroll, text='', font=("Calibri", 10), anchor='nw', justify='left')
    time_until_label.pack(pady=20, padx=20)
    calculate_time_until(datetime.date(year=2081, month=9, day=28))

########################################################################################################################
########################################################################################################################
#######################################                                          #######################################
#######################################    S C R O L L A B L E    C A N V A S    #######################################
#######################################                                          #######################################
########################################################################################################################
########################################################################################################################

class ScrollableFrame(ttk.Frame):
    def __init__(self, container, *args, **kwargs):
        super().__init__(container, *args, **kwargs)
        self.canvas = tk.Canvas(self) # width=250 - to change the width of widget
        scrollbar = ttk.Scrollbar(self, orient="vertical", command=self.canvas.yview)
        self.frame_to_scroll = ttk.Frame(self.canvas)
        self.frame_to_scroll.bind("<Configure>", lambda e: self.canvas.configure(scrollregion=self.canvas.bbox("all")))
        self.frame_to_scroll.bind("<Enter>", self.mouse_on)
        self.frame_to_scroll.bind("<Leave>", self.mouse_off)
        self.canvas.create_window((0, 0), window=self.frame_to_scroll, anchor="nw")
        self.canvas.configure(yscrollcommand=scrollbar.set)
        self.canvas.pack(side="left", fill="both", expand=True)
        scrollbar.pack(side="right", fill="y")
    def mouse_on(self, event):
        self.canvas.bind_all("<MouseWheel>", self.mouse_use)
    def mouse_off(self, event):
        self.canvas.unbind_all("<MouseWheel>")
    def mouse_use(self, event):
        self.canvas.yview_scroll(int(-1 * (event.delta / 120)), "units")

def configure_scrollable_canvas():

    ########## CHANGE SCROLLABLE CANVAS BACKGROUND ##########

    style = ttk.Style(root)
    style.configure("white.TFrame", background='white')
    scrollable_canvas.canvas.config(bg='white')
    scrollable_canvas.frame_to_scroll.config(style='white.TFrame')

    ########## SET CANVAS SIZE TO WINDOW SIZE AND ADJUST HEIGHT BY CONTENT ##########

    root.update_idletasks()
    window_width = root.winfo_width() - 40
    number_of_window_items = 20
    scrollable_canvas.frame_to_scroll.config(width=window_width)
    scrollable_canvas.frame_to_scroll.config(height=60 + (len(range(0, number_of_window_items))) * 25)
    scrollable_canvas.frame_to_scroll.grid_propagate(0) # Canvas will no longer adjust it's size automatically

########################################################################################################################
########################################################################################################################
#############################################                              #############################################
#############################################    M A I N    W I N D O W    #############################################
#############################################                              #############################################
########################################################################################################################
########################################################################################################################


root = tk.Tk()
root.geometry("1920x1080+700+500")
root.resizable(True, True)
root.title('Solutions')
root.iconbitmap('./assets/solutions/SAMPLE_ICON.ICO')
root.columnconfigure(0, weight=1)
root.rowconfigure(0, weight=1)

# SCROLLABLE CANVAS #
scrollable_canvas = ScrollableFrame(root)
scrollable_canvas.grid(row=0, column=0, sticky="NEWS")
scroll = scrollable_canvas.frame_to_scroll

########################################################################################################################
####################################################    M E N U    #####################################################
########################################################################################################################

def app_menu():
    menubar = tk.Menu(root)
    root.config(menu=menubar)
    root.option_add("*tearOff", False)

    #########################    A I    -    A R T I F I C I A L    I N T E L L I G E N C E    #########################

    menu_ai = tk.Menu(menubar, tearoff=False)
    menubar.add_cascade(label='🤖 AI', menu=menu_ai)
    menu_ai.add_command(label='Speech Recognition', command=lambda: ai_speech_recognition())
    menubar.add_command(label="\u22EE")

    ###################################################    A P I    ####################################################

    menu_api = tk.Menu(menubar, tearoff=False)
    menubar.add_cascade(label='🧱 API', menu=menu_api)
    menu_api.add_command(label='Google Calendar - Print Events', command=lambda: google_calendar_print())
    menu_api.add_command(label='Google Calendar - Add Events', command=lambda: google_calendar_add())
    menu_api.add_command(label='Google Drive - Create Spreadsheet', command=lambda: google_drive_add())
    menu_api.add_command(label='Weather API', command=lambda: weather_api())
    menubar.add_command(label="\u22EE")

    ################################################    C H A R T S    #################################################

    charts = tk.Menu(menubar, tearoff=False)
    menubar.add_cascade(label='📈 Charts', menu=charts)
    charts.add_command(label='Doughnut Chart', command=lambda: doughnut_chart())
    charts.add_command(label="Simple Graph", command= lambda: simple_graph())
    menubar.add_command(label="\u22EE")

    ###############################################    C L A S S E S    ################################################

    classes = tk.Menu(menubar, tearoff=False)
    menubar.add_cascade(label='🏭 Classes', menu=classes)
    classes.add_command(label='Button with Picture', command=lambda: class_button())
    classes.add_command(label='Display Attributes of a Widget', command=lambda: Attributes(root, tk.Button()))
    classes.add_command(label='Limiter', command=lambda: display_limiter())
    classes.add_command(label='My Frame', command=lambda: display_my_frame())
    menubar.add_command(label="\u22EE")

    #################################################    E M A I L    ##################################################

    email = tk.Menu(menubar, tearoff=False)
    menubar.add_cascade(label='📂 Email', menu=email)
    email.add_command(label='Email simple with auto address', command=lambda: email_simple())
    email.add_command(label='Email advanced with popup text entry', command=lambda: email_advanced())
    menubar.add_command(label="\u22EE")

    ################################################    E X P O R T    #################################################

    export = tk.Menu(menubar, tearoff=False)
    menubar.add_cascade(label='🚀 Export', menu=export)
    export.add_command(label='Export database', command=lambda: backup_database())
    export.add_command(label='Export CSV', command=lambda: export_csv())
    export.add_command(label='Export PDF', command=lambda: create_pdf())
    export.add_command(label='Export TXT', command=lambda: export_txt())
    export.add_command(label='Print with default printer', command=lambda: printer_print())
    menubar.add_command(label="\u22EE")

    ####################################    F I L E S    A N D    F O L D E R S    #####################################

    files_folders = tk.Menu(menubar, tearoff=False)
    menubar.add_cascade(label='📂 Files and Folders', menu=files_folders)
    files_folders.add_command(label='Create and Open a New Folder', command=lambda: create_and_open_folder())
    files_folders.add_command(label='Create Folders with Unique Y-M-D-H-M-S Names', command=lambda: create_timestamp_folder())
    files_folders.add_command(label='Display Absolute Path to a File', command=lambda: absolute_path())
    files_folders.add_command(label='Display All Files in a Folder', command=lambda: display_all_files())
    files_folders.add_command(label='Display File Name', command=lambda: display_file_name())
    files_folders.add_command(label='Display First PNG in a Folder', command=lambda: select_first_png())
    files_folders.add_command(label='Open External Program', command=lambda: open_app())
    files_folders.add_command(label='Open File', command=lambda: open_file())
    menubar.add_command(label="\u22EE")

    ################################################    I M A G E S    #################################################

    images_functions = tk.Menu(menubar, tearoff=False)
    menubar.add_cascade(label='📂 Images', menu=images_functions)
    images_functions.add_command(label='Animate Image', command=lambda: animate_image())
    images_functions.add_command(label='Display Image', command=lambda: display_image())
    images_functions.add_command(label='Display Image - simple', command=lambda: display_image_simple())
    images_functions.add_command(label='Display Image to Fit Window', command=lambda: display_image_adjusted())
    images_functions.add_command(label='Image as Background', command=lambda: image_as_background())
    images_functions.add_command(label='Image as Background with resize', command=lambda: image_as_background_with_resize())
    menubar.add_command(label="\u22EE")

    #######################################    O T H E R    F U N C T I O N S    #######################################

    other_functions = tk.Menu(menubar, tearoff=False)
    menubar.add_cascade(label='📂 Other', menu=other_functions)
    other_functions.add_command(label='Bind actions', command=lambda: bind_actions())
    other_functions.add_command(label='Colour Picker', command=lambda: pick_colour())
    other_functions.add_command(label='Count Down Timer', command=lambda: timer_build_ui_and_start())
    other_functions.add_command(label='Dates Functions', command=lambda: dates())
    other_functions.add_command(label='Dates Functions - simple', command=lambda: time_calculations_simple())
    other_functions.add_command(label='Fonts in TKinter', command=lambda: tkinter_fonts())
    other_functions.add_command(label='Open Website', command=lambda: open_website())
    other_functions.add_command(label='Play Music using playsound', command=lambda: play_sound_playsound())
    other_functions.add_command(label='Play Music using pygame', command=lambda: play_sound_pygame())
    other_functions.add_command(label='Run DOS commands', command=lambda: run_dos_commands())
    menubar.add_command(label="\u22EE")

    ########################################    T I M E    F U N C T I O N S    ########################################

    time_functions = tk.Menu(menubar, tearoff=False)
    menubar.add_cascade(label='📂 Time', menu=time_functions)
    time_functions.add_command(label='Count Down Timer', command=lambda: timer_build_ui_and_start())
    time_functions.add_command(label='Dates Functions', command=lambda: dates())
    time_functions.add_command(label='Dates Functions - simple', command=lambda: time_calculations_simple())
    time_functions.add_command(label='Time Since an event', command=lambda: time_since_event())
    time_functions.add_command(label='Time Until an event', command=lambda: time_until_event())
    menubar.add_command(label="\u22EE")

    #########################################    P O P U P    W I N D O W S    #########################################

    popup_windows = tk.Menu(menubar, tearoff=False)
    menubar.add_cascade(label='📂 Popup', menu=popup_windows)
    popup_windows.add_command(label='Dialog Open File', command=lambda: open_txt())
    popup_windows.add_command(label='Dialog Open Many Files', command=lambda: open_many_files())
    popup_windows.add_command(label='Dialog Open Directory', command=lambda: open_directory())
    popup_windows.add_command(label='Dialog Save File', command=lambda: save_txt())
    popup_windows.add_command(label='Messagebox Show Error', command=lambda: messagebox_showerror())
    popup_windows.add_command(label='Messagebox Show Info', command=lambda: messagebox_showinfo())
    popup_windows.add_command(label='Messagebox Show Warning', command=lambda: messagebox_showwarning())
    popup_windows.add_command(label='Messagebox OK Cancel', command=lambda: run_messagebox(messagebox_askokcancel))
    popup_windows.add_command(label='Messagebox Retry Cancel', command=lambda: run_messagebox(messagebox_askretrycancel))
    popup_windows.add_command(label='Messagebox Yes No', command=lambda: run_messagebox(messagebox_askyesno))
    popup_windows.add_command(label='Messagebox Yes No Cancel', command=lambda: run_messagebox(messagebox_askyesnocancel))
    popup_windows.add_command(label='Simple Popup Window', command=lambda: simple_popup_window())
    popup_windows.add_command(label='Advanced Popup Window', command=lambda: simple_popup_window())
    popup_windows.add_command(label='Right Click Menu', command=lambda: rightclick_menu())
    menubar.add_command(label="\u22EE")

    ######################################    S Y S T E M    F U N C T I O N S    ######################################

    system_functions = tk.Menu(menubar, tearoff=False)
    menubar.add_cascade(label='📂 System', menu=system_functions)
    system_functions.add_command(label='Accelerator - menu item shortcut', accelerator="Ctrl+N",
                                 command=lambda: statusbar.set("Menu Item Clicked"))

    root.bind("<Control-n>", lambda event: statusbar.set("Shortcut Ctrl+N used to start a function"))

    system_functions.add_command(label='Auto Repeat Functions', command=lambda: auto_repeat_function())
    system_functions.add_command(label='Autopopulate with clickable labels', command=lambda: autopopulate(SAMPLE_LIST))
    system_functions.add_command(label='Backup Database Auto', command=lambda: backup_database_auto())
    system_functions.add_command(label='Backup Database Select', command=lambda: backup_database_select())
    system_functions.add_command(label='Check Input Date', command=lambda: statusbar.set(f"{check_input_date('2020-01-01')}"))
    system_functions.add_command(label='Check Input Time', command=lambda: statusbar.set(f"{check_input_time('15:15')}"))
    system_functions.add_command(label='Check Input Email', command=lambda: statusbar.set(f"{check_input_email('lord.huriko@gmail.com')}"))
    system_functions.add_command(label='Check Input Price', command=lambda: statusbar.set(f"{check_input_price('99.99')}"))
    system_functions.add_command(label='Check If File Edited', command=lambda: check_if_file_edited())
    system_functions.add_command(label='Children Clear All in Scroll Window', command=lambda: cscroll())
    system_functions.add_command(label='Children Clear All in Selected Window', command=lambda: c(root))
    system_functions.add_command(label='Children Edit All in a Window', command=lambda: edit_children())
    system_functions.add_command(label='Children Find Widget by String ID', command=lambda: find_children())
    system_functions.add_command(label='Children Read Text From Entries', command=lambda: read_from_text_children())
    system_functions.add_command(label='Children Select Active Notebook Tab', command=lambda: select_active_child())
    system_functions.add_command(label='Copy Label Text to Clipboard', command=lambda: copy_to_clipboard())
    system_functions.add_command(label='Display Initial and Updated Root sizes', command=lambda: window_size())
    system_functions.add_command(label='Display Window Size and Position', command=lambda: display_window_size_and_position())
    system_functions.add_command(label='Dynamically resize Window', command=lambda: resize_window())
    system_functions.add_command(label='Position Window in the Middle of the Screen', command=lambda: position())
    system_functions.add_command(label='Remove Single Widget', command=lambda: remove_widget())
    system_functions.add_command(label='Save Pickle', command=lambda: save_all())
    system_functions.add_command(label='Scrollable Canvas Edit Settings', command=lambda: configure_scrollable_canvas())
    system_functions.add_command(label='Threading', command=lambda: task_threading())
    system_functions.add_command(label='Timestamp', command=lambda: statusbar.set(timestamp()))
    menubar.add_command(label="\u22EE")

    ###############################################    W I D G E T S    ################################################

    widgets = tk.Menu(menubar, tearoff=False)
    menubar.add_cascade(label='📂 Widgets', menu=widgets)
    widgets.add_command(label='Checkbutton', command=lambda: widget_checkbutton())
    widgets.add_command(label='Combobox', command=lambda: widget_combobox())
    widgets.add_command(label='Dropdown Box', command=lambda: widget_dropdown_box())
    widgets.add_command(label='Entry as Password', command=lambda: entry_as_password())
    widgets.add_command(label='Entry auto delete hint', command=lambda: entry_auto_delete())
    widgets.add_command(label='LabelFrame', command=lambda: widget_labelframe())
    widgets.add_command(label='Label Wrap Text', command=lambda: widget_label_wrap())
    widgets.add_command(label='Listbox', command=lambda: widget_listbox())
    widgets.add_command(label='Listbox advanced', command=lambda: widget_listbox_advanced_with_scrollbar())
    widgets.add_command(label='Notebook', command=lambda: widget_notebook())
    widgets.add_command(label='Paned (resizable) window', command=lambda: paned_window())
    widgets.add_command(label='Progress Bar', command=lambda: widget_progress_bar())
    widgets.add_command(label='Radiobutton', command=lambda: widget_radiobutton())
    widgets.add_command(label='Scales', command=lambda: widget_scales())
    widgets.add_command(label='Scrollbar', command=lambda: widget_scrollbar())
    widgets.add_command(label='Spinbox', command=lambda: widget_spinbox())
    widgets.add_command(label='Treeview', command=lambda: widget_treeview())
    widgets.add_separator()

########################################################################################################################
##################################################    S T Y L E S    ###################################################
########################################################################################################################

style = ttk.Style(root)
# style.theme_use("clam") # 'winnative', 'clam', 'alt', 'default', 'classic', 'vista', 'xpnative'

# Global style
style.configure("TNotebook.Tab", font=("Calibri", 10), padding=10) # every notebook will have this style

# Manual style
style.configure("MyStyle.TLabel", font=("Calibri", 20), padding=5) # add using style="MyStyle.TLabel"

import tkinter.font as tkFont

labels_font = tkFont.Font(size=9, weight="bold", underline=True) # add using font=labels_font
print(root.winfo_class())

########################################################################################################################
#################################################    A U T O R U N    ##################################################
########################################################################################################################

root.after(0, app_menu())
root.after(0, display_image())

########################################################################################################################
###############################################    S T A T U S B A R    ################################################
########################################################################################################################

statusbar = tk.StringVar()
ttk.Label(root, textvariable=statusbar, anchor='center').grid(row=1, column=0, sticky="EW")

########################################################################################################################

# Images required for Treeview Widget
picture_renatka = tk.PhotoImage(file="./assets/solutions/misia.png")
picture_krzysio = tk.PhotoImage(file="./assets/solutions/misio.png")
picture_both = tk.PhotoImage(file="./assets/solutions/house.png")

# Image required for Canvas with image background
# image_canvas = ImageTk.PhotoImage(file="./assets/solutions/pic01.png")

root.mainloop()