Skip to content

Commit

Permalink
Closes #46
Browse files Browse the repository at this point in the history
- Update license year
- Update privacy policy date
- Create template KSF
- When no KSF is present, allow creation of new KSF from template
- Fix versioning numbers
- Allow KSF keys to be deleted and reassigned using popup
- Create class for key actions to remove magic numbers
- Create key edit functions
- Update KSF revision function so all cells are rewritten
  • Loading branch information
wsarce committed Jun 19, 2023
1 parent 3cdd3b6 commit 6dffb40
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 71 deletions.
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2022 Munroe Meyer Institute Virtual Reality Laboratory
Copyright (c) 2023 Munroe-Meyer Institute Virtual Reality Laboratory

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
2 changes: 1 addition & 1 deletion PRIVACY_POLICY.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Privacy Policy for cometrics
## Date: Aug 2022
## Date: June 2023

The developer of cometrics is mindful of the security and protection of user data and patient data. This Privacy Policy document explains the types of information that are collected and recorded by cometrics and in which situations the developers will have access to generated data.

Expand Down
69 changes: 36 additions & 33 deletions ksf_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -760,45 +760,48 @@ def create_new_ksf_revision(original_ksf, keystrokes):
name_cell = freq_headers[key][1] + '4'
ws[name_cell].value = key

last_cell = freq_headers[list(freq_headers.keys())[0]][0]
i = 0
difference = 0
if len(freq_headers) != len(keystrokes['Frequency']):
difference = len(keystrokes['Frequency']) - len(freq_headers)
last_cell = freq_headers[key][0]
i = 1
for key in keystrokes['Frequency'][-difference:]:
ws[increment_cell(last_cell, i)].value = key[0]
name_cell = increment_cell(last_cell, i)[:-1] + '4'
ws[name_cell].value = key[1]
i += 1
for key in dur_headers:
dur_headers[key][0] = increment_cell(dur_headers[key][0], difference)
tracker_headers['Session Data'][3] += difference
tracker_headers['Frequency'][3] += difference
tracker_headers['Duration'][0] = increment_cell(tracker_headers['Duration'][0], difference)
tracker_headers['ST'][0] = increment_cell(tracker_headers['ST'][0], difference)
tracker_headers['PT'][0] = increment_cell(tracker_headers['PT'][0], difference)
tracker_headers['Session Time'][0] = increment_cell(tracker_headers['Session Time'][0], difference)
tracker_headers['Pause Time'][0] = increment_cell(tracker_headers['Pause Time'][0], difference)

for key in keystrokes['Frequency']:
ws[increment_cell(last_cell, i)].value = key[0]
name_cell = increment_cell(last_cell, i)[:-1] + '4'
ws[name_cell].value = key[1]
i += 1
# Offset the remaining headers so they don't collide with the frequency header
for key in dur_headers:
dur_headers[key][0] = increment_cell(dur_headers[key][0], difference)
tracker_headers['Session Data'][3] += difference
tracker_headers['Frequency'][3] += difference
tracker_headers['Duration'][0] = increment_cell(tracker_headers['Duration'][0], difference)
tracker_headers['ST'][0] = increment_cell(tracker_headers['ST'][0], difference)
tracker_headers['PT'][0] = increment_cell(tracker_headers['PT'][0], difference)
tracker_headers['Session Time'][0] = increment_cell(tracker_headers['Session Time'][0], difference)
tracker_headers['Pause Time'][0] = increment_cell(tracker_headers['Pause Time'][0], difference)
# Write the duration key headers
for key in dur_headers:
ws[dur_headers[key][0]].value = dur_headers[key][2]
name_cell = dur_headers[key][0][:-1] + '4'
ws[name_cell].value = key

# Get the first duration key cell
last_cell = dur_headers[list(dur_headers.keys())[0]][0]
i = 0
difference = 0
if len(dur_headers) != len(keystrokes['Duration']):
difference = len(keystrokes['Duration']) - len(dur_headers)
last_cell = dur_headers[key][0]
i = 1
for key in keystrokes['Duration'][-difference:]:
ws[increment_cell(last_cell, i)].value = key[0]
name_cell = increment_cell(last_cell, i)[:-1] + '4'
ws[name_cell].value = key[1]
i += 1
tracker_headers['Session Data'][3] += difference
tracker_headers['Duration'][3] += difference
tracker_headers['ST'][0] = increment_cell(tracker_headers['ST'][0], difference)
tracker_headers['PT'][0] = increment_cell(tracker_headers['PT'][0], difference)
tracker_headers['Session Time'][0] = increment_cell(tracker_headers['Session Time'][0], difference)
tracker_headers['Pause Time'][0] = increment_cell(tracker_headers['Pause Time'][0], difference)
for key in keystrokes['Duration']:
ws[increment_cell(last_cell, i)].value = key[0]
name_cell = increment_cell(last_cell, i)[:-1] + '4'
ws[name_cell].value = key[1]
i += 1
tracker_headers['Session Data'][3] += difference
tracker_headers['Duration'][3] += difference
tracker_headers['ST'][0] = increment_cell(tracker_headers['ST'][0], difference)
tracker_headers['PT'][0] = increment_cell(tracker_headers['PT'][0], difference)
tracker_headers['Session Time'][0] = increment_cell(tracker_headers['Session Time'][0], difference)
tracker_headers['Pause Time'][0] = increment_cell(tracker_headers['Pause Time'][0], difference)

for key in skip_headers:
if type(tracker_headers[key]) is list:
Expand All @@ -815,9 +818,9 @@ def create_new_ksf_revision(original_ksf, keystrokes):
ksf_dir = pathlib.Path(original_ksf).parent
ksf_count = len(glob.glob1(ksf_dir, "*.xlsx"))
if ksf_count > 1:
new_ksf = f"{original_ksf[:-8]}_V{ksf_count + 1}.xlsx"
new_ksf = f"{original_ksf[:-8]}_V{ksf_count}.xlsx"
else:
new_ksf = f"{original_ksf[:-5]}_V{ksf_count + 1}.xlsx"
new_ksf = f"{original_ksf[:-5]}_V{ksf_count}.xlsx"
wb.save(new_ksf)
return new_ksf

Expand Down
115 changes: 88 additions & 27 deletions project_setup_ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import os
import pathlib
import traceback
from shutil import copy2
from tkinter import *
from tkinter import messagebox, filedialog
from menu_bar import MenuBar
Expand All @@ -18,6 +19,13 @@
small_treeview_rowheight, large_button_size, medium_button_size, small_button_size, ui_title


class KeyActions:
FREQUENCY_CREATE = 1
DURATION_CREATE = 2
FREQUENCY_EDIT = 3
DURATION_EDIT = 4


class ProjectSetupWindow:
def __init__(self, config, first_time_user):
self.config = config
Expand Down Expand Up @@ -126,13 +134,20 @@ def __init__(self, config, first_time_user):
font=(self.field_font[0], self.field_font[1], 'italic'),
bg='white', width=30, anchor='w')
self.ksf_path.place(x=10 + self.window_width / 2, y=ptp[1], anchor=NW,
width=int(self.window_width * 0.3), height=self.button_size[1])
width=int(self.window_width * 0.25), height=self.button_size[1])

self.ksf_import = Button(self.main_root, text="Import", font=self.field_font, width=10,
command=self.import_concern_ksf)
self.ksf_import.place(x=self.window_width * 0.77 + self.button_size[0] + 10, y=ptp[1],
width=self.button_size[0], height=self.button_size[1], anchor=NW)
self.ksf_import.config(state='disabled')

self.ksf_new = Button(self.main_root, text="New", font=self.field_font, width=10,
command=self.create_new_ksf)
self.ksf_new.place(x=self.window_width * 0.77 + self.button_size[0], y=ptp[1],
width=self.button_size[0], height=self.button_size[1], anchor=NE)
self.ksf_new.config(state='disabled')

# Define frequency and duration key headers
freq_heading_dict = {"#0": ["Frequency Key", 'w', 1, YES, 'w']}
dur_heading_dict = {"#0": ["Duration Key", 'w', 1, YES, 'w']}
Expand Down Expand Up @@ -474,33 +489,61 @@ def load_ksf(self):
except ValueError:
self.ksf_file = None
self.ksf_path['text'] = f"No KSF in {self.selected_concern} {self.phases_var.get()}"
self.ksf_import.config(state='active')
self.ksf_import.config(state='normal')
self.ksf_new.config(state='normal')
self.clear_duration_treeview()
self.clear_frequency_treeview()

def create_new_ksf(self):
self.tracker_file = os.path.join(self.ksf_dir, f"{self.patient_container.name}_KSF.xlsx")
copy2(r'reference\Template_KSF.xlsx', self.tracker_file)
new_ksf_file, new_keystrokes = import_ksf(self.tracker_file, self.ksf_dir)
self._ksf = new_keystrokes
self.ksf_file = new_ksf_file
self.clear_duration_treeview()
self.clear_frequency_treeview()
self.load_ksf()
self.ksf_new.config(state='disabled')
print("INFO: Successfully created tracker spreadsheet")

def generate_ksf(self):
new_tracker_file = create_new_ksf_revision(self.tracker_file, self._ksf)
new_ksf_file, new_keystrokes = import_ksf(new_tracker_file, self.ksf_dir)
if compare_keystrokes(self._ksf, new_keystrokes):
self.tracker_file = new_tracker_file
self._ksf = new_keystrokes
self.ksf_file = new_ksf_file
self.clear_duration_treeview()
self.clear_frequency_treeview()
self.load_ksf()
print("INFO: Successfully updated tracker spreadsheet")
if len(self._ksf['Duration']) == 0 or len(self._ksf['Frequency']) == 0:
messagebox.showerror("Error", "There must be at least one Frequency and Duration key!")
print("ERROR: There must be at least one Frequency and Duration key!")
else:
messagebox.showerror("Error", "Failed to update tracker spreadsheet!")
print("ERROR: Failed to update tracker spreadsheet")
new_tracker_file = create_new_ksf_revision(self.tracker_file, self._ksf)
new_ksf_file, new_keystrokes = import_ksf(new_tracker_file, self.ksf_dir)
if compare_keystrokes(self._ksf, new_keystrokes):
self.tracker_file = new_tracker_file
self._ksf = new_keystrokes
self.ksf_file = new_ksf_file
self.clear_duration_treeview()
self.clear_frequency_treeview()
self.load_ksf()
self.generate_button.config(state='disabled')
print("INFO: Successfully updated tracker spreadsheet")
else:
messagebox.showerror("Error", "Failed to update tracker spreadsheet!")
print("ERROR: Failed to update tracker spreadsheet")

def key_popup_return(self, tag, key, caller):
def key_popup_return(self, tag, key, caller, index, delete=False):
if not tag or not key:
messagebox.showwarning("Warning", "Invalid key entered! Please try again.")
print(f"WARNING: Invalid key entered {tag} {key} {caller}")
if caller == 1:
return
if delete:
if caller == KeyActions.FREQUENCY_EDIT or caller == KeyActions.FREQUENCY_CREATE:
self.delete_frequency_key(index)
elif caller == KeyActions.DURATION_EDIT or caller == KeyActions.DURATION_CREATE:
self.delete_duration_key(index)
elif caller == KeyActions.FREQUENCY_CREATE:
self.create_frequency_key(tag, key)
elif caller == 2:
elif caller == KeyActions.DURATION_CREATE:
self.create_duration_key(tag, key)
elif caller == KeyActions.FREQUENCY_EDIT:
self.edit_frequency_key(index, tag, key)
elif caller == KeyActions.DURATION_EDIT:
self.edit_duration_key(index, tag, key)

def load_concern_ksf(self):
with open(self.ksf_file) as f:
Expand Down Expand Up @@ -568,42 +611,60 @@ def populate_duration_treeview(self):
def select_frequency_key(self, event):
selection = self.frequency_key_treeview.identify_row(event.y)
if selection:
if selection == '0':
NewKeyPopup(self, self.main_root, 1)
selection = int(selection) - 1
if selection == -1:
NewKeyPopup(self, self.main_root, KeyActions.FREQUENCY_CREATE)
else:
self.delete_frequency_key(int(selection) - 1)
NewKeyPopup(self, self.main_root, KeyActions.FREQUENCY_EDIT, index=selection,
key=self._ksf['Frequency'][selection][0],
tag=self._ksf['Frequency'][selection][1])

def select_duration_key(self, event):
selection = self.duration_key_treeview.identify_row(event.y)
if selection:
if selection == '0':
NewKeyPopup(self, self.main_root, 2)
selection = int(selection) - 1
if selection == -1:
NewKeyPopup(self, self.main_root, KeyActions.DURATION_CREATE)
else:
self.delete_duration_key(int(selection) - 1)
NewKeyPopup(self, self.main_root, KeyActions.DURATION_EDIT, index=selection,
key=self._ksf['Duration'][selection][0],
tag=self._ksf['Duration'][selection][1])

def create_frequency_key(self, tag, key):
self._ksf['Frequency'].append([str(key), str(tag)])
clear_treeview(self.frequency_key_treeview)
self.populate_frequency_treeview()
self.generate_button.config(state='active')
self.generate_button.config(state='normal', background='#4abb5f')

def create_duration_key(self, tag, key):
self._ksf['Duration'].append([str(key), str(tag)])
clear_treeview(self.duration_key_treeview)
self.populate_duration_treeview()
self.generate_button.config(state='active')
self.generate_button.config(state='normal', background='#4abb5f')

def delete_frequency_key(self, index):
self._ksf['Frequency'].pop(index)
clear_treeview(self.frequency_key_treeview)
self.populate_frequency_treeview()
self.generate_button.config(state='active')
self.generate_button.config(state='normal', background='#4abb5f')

def delete_duration_key(self, index):
self._ksf['Duration'].pop(index)
clear_treeview(self.duration_key_treeview)
self.populate_duration_treeview()
self.generate_button.config(state='active')
self.generate_button.config(state='normal', background='#4abb5f')

def edit_frequency_key(self, index, tag, key):
self._ksf['Frequency'][index] = ([str(key), str(tag)])
clear_treeview(self.frequency_key_treeview)
self.populate_frequency_treeview()
self.generate_button.config(state='normal', background='#4abb5f')

def edit_duration_key(self, index, tag, key):
self._ksf['Duration'][index] = ([str(key), str(tag)])
clear_treeview(self.duration_key_treeview)
self.populate_duration_treeview()
self.generate_button.config(state='normal', background='#4abb5f')

# endregion

Expand Down
Binary file added reference/Template_KSF.xlsx
Binary file not shown.
36 changes: 27 additions & 9 deletions tkinter_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import tkinter
import traceback
import webbrowser
from tkinter import TOP, W, N, NW, messagebox, END, ttk, filedialog, INSERT
from tkinter import TOP, W, N, NW, messagebox, END, ttk, filedialog, INSERT, NE
from tkinter.ttk import Style, Combobox
from tkinter.ttk import Treeview
import numpy as np
Expand Down Expand Up @@ -579,10 +579,13 @@ def close_win(self):


class NewKeyPopup:
def __init__(self, top, root, dur_freq):
def __init__(self, top, root, action, index=None, key=None, tag=None):
assert top.key_popup_return
self.caller = top
self.dur_freq = dur_freq
self.index = index
self.action = action
self.key = key
self.tag = tag
self.tag_entry = None
self.key_entry = None
self.popup_root = None
Expand All @@ -596,33 +599,48 @@ def new_key_name_entry(self, root):
popup_root.title("Enter New Binding")

# Create an Entry Widget in the Toplevel window
self.tag_label = tkinter.Label(popup_root, text="Key Tag", bg='white')
self.tag_label = tkinter.Label(popup_root, text="Definition", bg='white')
self.tag_label.place(x=30, y=20, anchor=W)
self.tag_entry = tkinter.Entry(popup_root, bd=2, width=25, bg='white')
self.tag_entry.place(x=90, y=20, anchor=W)
if self.tag:
set_entry_text(self.tag_entry, self.tag)

self.key_label = tkinter.Label(popup_root, text="Key", bg='white')
self.key_label.place(x=30, y=50, anchor=W)
self.key_entry = tkinter.Entry(popup_root, bd=2, width=25, bg='white')
self.key_entry.place(x=90, y=50, anchor=W)
if self.key:
set_entry_text(self.key_entry, self.key)

# Create a Button Widget in the Toplevel Window
button = tkinter.Button(popup_root, text="OK", command=self.close_win)
button.place(x=150, y=70, anchor=N)
button = tkinter.Button(popup_root, text="Assign", fg='green', command=self.assign_key)
button.place(x=140, y=70, anchor=NE)

button = tkinter.Button(popup_root, text="Delete", fg='red', command=self.delete_key)
button.place(x=160, y=70, anchor=NW)

center(popup_root)
popup_root.focus_force()
self.tag_entry.focus()

def close_win(self):
def delete_key(self):
self.caller.key_popup_return(self.tag_entry.get(), self.key_entry.get(), self.action, self.index, True)
self.close_win()

def assign_key(self):
if len(self.key_entry.get()) == 1:
self.caller.key_popup_return(self.tag_entry.get(), self.key_entry.get(), self.dur_freq)
self.popup_root.destroy()
self.caller.key_popup_return(self.tag_entry.get(), self.key_entry.get(), self.action, self.index)
self.close_win()
else:
messagebox.showwarning("Warning", 'New key binding can only be one character!')
print("WARNING: New key binding can only be one character!")
self.popup_root.focus_force()
self.tag_entry.focus()

def close_win(self):
self.popup_root.destroy()


def set_entry_text(widget: tkinter.Entry, text):
widget.delete(0, END)
Expand Down

0 comments on commit 6dffb40

Please sign in to comment.