row=output_row,
                            sticky=(W, E),
                            columnspan=2)
ttk.Button(mainframe, text="Browse",
           command=file_explore_outputs).grid(column=4,
                                              row=output_row,
                                              sticky=W)

ttk.Label(mainframe, text="Delete entries from before:").grid(column=1,
                                                              row=date_row,
                                                              sticky=W)
default_date = date.today() - timedelta(days=2)
delete_before_entry = DateEntry(mainframe, locale='en_UK')
delete_before_entry.set_date(default_date)
delete_before_entry.grid(column=3, row=date_row, sticky=(W, E))
delete_before_entry.grid_remove()


def show_or_hide_date():
    check_flag = delete_flag.get()
    if check_flag == 0:
        delete_before_entry.grid_remove()
    elif check_flag == 1:
        delete_before_entry.grid(column=3, row=date_row, sticky=(W, E))


delete_flag = IntVar()
delete_flag.set(1)
date_toggle = ttk.Checkbutton(mainframe,
                              variable=delete_flag,
                              command=show_or_hide_date)
Esempio n. 2
0
class InputWindow(tk.Tk):
    #  ----- initialize -----
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

    # ----- paths -----
        icon_path = f"media{os.sep}icons" #use os.sep for tracker to work in different OS
        # print(icon_path) #uncomment for troubleshooting
        self.img = ImageTk.PhotoImage(Image.open(f"media{os.sep}icons{os.sep}main.png"))

    # ----- Data ------
    #check for command line arguments
        if len(sys.argv) > 1:
            print(f"Currently running: {sys.argv[0]}")
            print(f"Data loading from: {sys.argv[1]}")
            # self.tracker = sys.argv[1]
        else:
            print('No data...creating new Tracker-object.')
            # self.tracker = ''


    # ----- Styles -----

        style = Style(self)

        # colors
        self.BG_COL_1 = "#DCDAD5"

        # custom styles
        style = ttk.Style()
        style.theme_use("clam")
        f = tkFont.Font(family='helvetica', size=15)
        style.configure('Test.TFrame', font=f)
        # customed_style.configure('Custom.TNotebook.Tab', padding=[12, 12], font=('Helvetica', 10))

    # ----- customize -----

        # title
        self.title("Health Tracker")

        # change taskbar icon
        # self.iconphoto(False, ImageTk.PhotoImage(file=os.path.join(icon_path, "main.png"))) 
        self.iconphoto(False, self.img) 

        # make fullscreen
        self.state('zoomed')

        # closing function
        self.protocol("WM_DELETE_WINDOW", self.on_exit)

        # configure rows and columns
        self.grid_columnconfigure(0, weight=0)
        self.grid_columnconfigure(1, weight=1)
        for n in range(20):
            self.grid_rowconfigure(n, weight=1)

        # save todays date on attribute
        self.current_date = datetime.datetime.now().date()

        # list to save EntryFrame objects in to use fot latter iterations
        self.entry_frames = []

        # properties to save info about user logging in
        self.user = None
        self.sex = None

        # list property to collect system processes in --> to be closed on frame exit (e.g. created using subprocess.Popen())
        self.sysproc = []

    # # ----- Frame Container -----

    #     # save a ttk frame - object in a variable named "container" 
    #     container = ttk.Frame(self) 
    #     container.grid() #position the frame in the parent widget in a grid
    #     container.columnconfigure(0, weight=1)

    #----- Login and SignUp Screen -----

        # add login frame that is placed within "container"
        self.login_frame = LoginWindow(self) #initiate Timer-class and pass self as the controller #, self.switch_frame
        self.login_frame#.grid(row=0,column=0, rowspan=20, columnspan=2, sticky='EWNS') #configure timer frame placed in the first row and first column and to fill the entire frame ("container")

        # add signup frame 
        self.signup_frame = SignupWindow(self, name="signup")
        self.signup_frame#.grid(row=0,column=0, rowspan=20, columnspan=2, sticky='EWNS')

        # add signup frame 
        self.userinfo_frame = UserinfoWindow(self)

    # ----- Tabs -----

        # initiate ttk.Notebook as parent for tabs
        self.tabControl = ttk.Notebook(self)#, style="Custom.TNotebook.Tab")

        # create tabs
        self.mood_tab = ttk.Frame(self.tabControl)
        self.health_tab = ttk.Frame(self.tabControl) #, relief = tk.SUNKEN
        self.sleep_tab = ttk.Frame(self.tabControl)
        self.food_tab = ttk.Frame(self.tabControl)
        self.fitness_tab = ttk.Frame(self.tabControl)
        self.period_tab = ttk.Frame(self.tabControl)
        self.longterm_tab = ttk.Frame(self.tabControl)

        # add tabs
        self.tabControl.add(self.mood_tab, text='Mood')
        self.tabControl.add(self.health_tab, text='Health')
        self.tabControl.add(self.sleep_tab, text='Sleep')
        self.tabControl.add(self.food_tab, text='Food')
        self.tabControl.add(self.fitness_tab, text='Fitness')
        self.tabControl.add(self.period_tab, text='Period')
        self.tabControl.add(self.longterm_tab, text='Longterm Changes')

        # # pack tabs - to make them visible 
        # # tabControl.pack(expand=0, fill="both", pady=(10,10))
        # self.tabControl.grid(row=0,column=0, rowspan=20, sticky='EWNS')
        # self.tabControl.grid_columnconfigure(0, weight=1)
        # for n in range(15):
        #     self.tabControl.grid_rowconfigure(n, weight=1)
        # tk.Button(self,text="Change date NOW",command=self.change_date, borderwidth=0, fg='darkslateblue').grid(row=1,column=1,rowspan=1, sticky='N')
        # # print(tabControl.tab(tabControl.select(), "text")) #uncomment for troubleshooting

    # ----- Labels ----- 
        fontLab = tkFont.Font(family='Verdana', size=40, weight='bold', slant='roman')
        ttk.Label(self.mood_tab,  text ="How's your head feeling? \n", font=fontLab).grid(row=0, column=0, columnspan=2)
        ttk.Label(self.food_tab,  text ="How's your stomach feeling? \n", font={'size':12}).grid(row=0, column=0, columnspan=2)
        ttk.Label(self.fitness_tab,  text ="How's your muscles feeling? \n", font={'size':12}).grid(row=0, column=0, columnspan=2)
        ttk.Label(self.longterm_tab,  text ="How have you been? \n", font={'size':12}).grid(row=0, column=0, columnspan=2)
        ttk.Label(self.health_tab,  text ="How's your body feeling? \n", font={'size':12}).grid(row=0, column=0, columnspan=2)
        ttk.Label(self.period_tab,  text ="How's your uterus feeling? \n", font={'size':12}).grid(row=0, column=0, columnspan=2)
        ttk.Label(self.sleep_tab,  text ="How's your ZZZZZZZs feeling? \n", font={'size':12}).grid(row=0, column=0, columnspan=2)

    #  ----- Entry -----

        EntryFrame(self.mood_tab, mood_info, self.tabControl.tab(self.mood_tab)['text'], name='mood_tab').grid(row=1, column=0, sticky="NSEW", padx=10, pady=10)
        EntryFrame(self.health_tab, health_info, self.tabControl.tab(self.health_tab)['text']).grid(row=1, column=0, sticky="NSEW", padx=10, pady=10)
        EntryFrame(self.food_tab, food_info, self.tabControl.tab(self.food_tab)['text']).grid(row=1, column=0, sticky="NSEW", padx=10, pady=10)
        EntryFrame(self.sleep_tab, sleep_info, self.tabControl.tab(self.sleep_tab)['text']).grid(row=1, column=0, sticky="NSEW", padx=10, pady=10)
        EntryFrame(self.fitness_tab, fitness_info, self.tabControl.tab(self.fitness_tab)['text']).grid(row=1, column=0, sticky="NSEW", padx=10, pady=10)
        EntryFrame(self.period_tab, period_info, self.tabControl.tab(self.period_tab)['text']).grid(row=1, column=0, sticky="NSEW", padx=10, pady=10)
        EntryFrame(self.longterm_tab, longterm_info, self.tabControl.tab(self.longterm_tab)['text']).grid(row=1, column=0, sticky="NSEW", padx=10, pady=10)

        # if self.sex != 'male':
        #     period_tab = ttk.Frame(self.tabControl)
        #     self.tabControl.add(period_tab, text='Period')
        #     ttk.Label(period_tab,  text ="How's your uterus feeling? \n", font={'size':12}).grid(row=0, column=0, columnspan=2)
        #     EntryFrame(period_tab, period_info, self.tabControl.tab(period_tab)['text']).grid(row=1, column=0, sticky="NSEW", padx=10, pady=10)
        #     self.all_tabs = [mood_tab, food_tab, fitness_tab, period_tab, longterm_tab, health_tab, sleep_tab]
        # else:
        #     self.all_tabs = [mood_tab, food_tab, fitness_tab, longterm_tab, health_tab, sleep_tab]

        self.all_tabs = [self.mood_tab, self.food_tab, self.fitness_tab, self.period_tab, self.longterm_tab, self.health_tab, self.sleep_tab]

        # current tab
        tabName = self.tabControl.select()  #get name of current tab
        self.active_tab = self.tabControl.nametowidget(tabName)  #get widget-object from widget name (string)

        # create dictionary to keep track of frames
        self.frames = dict()
        self.frames_visibility = dict()

        # add both frames to dict
        self.frames['LoginWindow'] = self.login_frame
        self.frames['SignupWindow'] = self.signup_frame
        self.frames['UinfoWindow'] = self.userinfo_frame
        self.frames['TC'] = self.tabControl

        # start with timer_frame in front
        self.switch_frame('LoginWindow')

        self.tabControl.grid(row=0,column=0, rowspan=20, sticky='EWNS')
        self.tabControl.grid_columnconfigure(0, weight=1)
        for n in range(15):
            self.tabControl.grid_rowconfigure(n, weight=1)

        self.date_button = tk.Button(self,text="Change date NOW",command=self.change_date, borderwidth=0, fg='darkslateblue')

    # ----- Date Picker ------
        self.cal = DateEntry(self, width=12, background='darkblue',
                            foreground='white', borderwidth=2)

        # print([obj.winfo_name() for obj in self.entry_frames])


    # function printing selected date from calendar
    def print_sel(self, event=None):
        """
        Changes selected data in Entryframes

        Parameters:
            event: event - not None when called by self.change_date(); None when called by self.reset_tracker()
        """
        if event:
            try:
                old_date = self.current_date
            except:
                old_date = datetime.datetime.now().date()
            new_date = self.cal.get_date()
            self.current_date = new_date
            print(f"Date changed from {old_date} to {self.current_date}")
        else:
            self.current_date = datetime.datetime.now().date()  

        date_string = self.current_date.strftime("%Y-%m-%d") #convert datetime object to string in order to be able to pass it to .loc[]
        # get data
        data = db_transact.query_data_by_date_and_user(date_string, self.user)

        # update all EntryFrame fields based on date
        for entry_frame in self.entry_frames:
            if type(data) != dict:  #check if any data was returned from database for specified date and user
                entry_frame.create_blank_entryframes()
            else:
                entry_frame.update_selection(data, date_string) 


        # ----- method toggling date picker and printing selection ------
    
    def change_date(self):

        self.cal.bind("<<DateEntrySelected>>", lambda event: self.print_sel(event=event))

        if self.cal.winfo_ismapped():
            self.cal.grid_remove()
        else:
            self.cal.grid(row=3,column=1,rowspan=1, sticky='N')


    def on_tab_change(self, *event):

        #get EntryFrame of interest from current tab (2nd child object in current tab)
        '''
        The current tab (saved in self.active_tab) is a custom object parameter is used to save info on current active tab outside of native tkinter functionality ).
        The active tab is a ttk.Frame-object, that contains 3 children: a label, an EntryFrame and a Past_Entry_Frame.
        --> below: get the 2nd child-object from active tab - this is an EntryFrame-type object
        '''
        active_entryframe = self.active_tab.winfo_children()[1]  

        # check if any information was entered in current EntryFrame (-> changes for each information field saved as TRUE-values in value_entry_record-property) 
        if any(v==True for v in active_entryframe.value_entry_record.values()):  #check for any True-values in active_entryframe.value_entry_record-dict
            # insert EntryFrame data in database and display success message
            active_entryframe.insert_database(self.user, self.current_date) 
            message = f"""Database table {active_entryframe.tab} filled with values"""
            # change all values in entry-dict back to False, in order to avoid unneccesary database connections when re-opening this tab
            active_entryframe.value_entry_record = {k: False for k in active_entryframe.value_entry_record}
            self.toplevel_message('Success!', message, 1500)

        # change value of self.active_tab, as the tab was changed - done AFTER calling active_entryframe.insert_database(), otherwise the un-filled newly opened tab info is inserted to database
        tabName = self.tabControl.select() 
        self.active_tab = self.tabControl.nametowidget(tabName) 
        # print(self.tabControl.tab(self.tabControl.select(), "text"))  #uncomment for troubleshooting


    # ----- funtion to run upon closing the window -----
    def on_exit(self):
        print("Closing down background processes...")
        self.kill_processes(self.sysproc)
        print("Saving latest changes...")
        self.on_tab_change()  #save entries of last tab without tab change
        print("Exiting app...")
        self.destroy()  #destroy window

    def kill_processes(self, processes):
        """
        Kills every process in a list of processes.

        Parameters:
            processes (list) - list of processes

        Returns:
            void function
        """
        for process in processes:
            process.kill()
        return

    def add_plots(self):
        # ----- iterate over all notebook tabs -----
        for tab in self.all_tabs:

            # save each tab's EntryFrame object in list for latter use
            for child in tab.winfo_children():
                if type(child)==EntryFrame:
                    self.entry_frames.append(child)

            # get current notebook tab's text
            tab_name = self.tabControl.tab(tab)['text']

            # create tk.Canvas-objects as plotting area
            cur_tab = PastEntryFrame(tab, tab_name)
            cur_tab.grid(row=1, column=1, sticky="NSEW", padx=10, pady=10)
            cur_tab.display_plots(tab_name)


    # ----- function that brings frame on the back to the front -----
    def switch_frame(self, container):

        # ungrid current frame --> to avoid seeing "background"-frame, as frames are of different size, to avoid seeing "background"-frame
        for fname, frame in self.frames.items():
            if frame.winfo_viewable():
                frame.grid_forget()
    
        # indicate which frame to bring to show
        frame = self.frames[container]

        # create UI if frame to switch to is 'TC' - important to load TC after login only and to directly load correct user data based on self.user
        if frame == self.frames['TC']:
            if self.sex == 'male':
                self.change_tab_state(self.period_tab)
            else:
                self.change_tab_state(self.period_tab, method="enable")
            self.add_plots()
            self.date_button.grid(row=1,column=1,rowspan=1, sticky='N')
            frame.grid(row=0,column=0, rowspan=20, sticky='EWNS') 
        else:
            frame.grid(row=0,column=0, rowspan=20, columnspan=2,sticky='EWNS') 

        #brings indicated frame to the front
        frame.tkraise()  #keep tk.raise() in addition to .grid_forget() as 'TC' is always loaded to grid


    def toplevel_message(self, title, message, duration):
        top = tk.Toplevel()
        top.title(title)
        tk.Message(top, text=message, padx=20, pady=20).pack()
        top.after(duration, top.destroy)

    
    def change_tab_state(self, tab, method="disable"):
        """
        Changes status of specified ttk.Notebook-tab.
        If state change to "enabled", the state needs to be checked, as enabling from hidden vs disabled status use different functions.

        Parameters:
            tab: variable name of tab (NOT a string) - e.g. mood_tab
            method: string - "disable" (default) or "hide" or "enable"

        Returns: Void function
        """

        if method == "disable":
            self.tabControl.tab(tab, state="disabled")
            print(self.tabControl.tab(tab)['state'])
        elif method == "hide":
            self.tabControl.hide(tab)
            print(self.tabControl.tab(tab)['state'])
        elif method == "enable":
            state = self.tabControl.tab(tab)['state']
            if state == "hidden":
                self.tabControl.add(tab)
            elif state == "disabled":
                self.tabControl.tab(tab, state="normal")


    def reset_tracker(self):
        """
        Resets tracker interface (=ttk.Notebook object) and tkcalendar.DateEntry-object, when switching between users.
        This method is run from LoginWindow-object.


        Returns: Void function
        """

        self.tabControl.select(0)  #make first tab current tab every time new TC loads
        self.current_date = datetime.datetime.now().date()  
        self.cal.set_date(self.current_date)  #reset DateEntry-object
        self.print_sel()
        if self.cal.winfo_ismapped():  #toggle in
            self.cal.grid_remove()