Skip to content
Snippets Groups Projects
Commit a3711e96 authored by Verbeek, J.M. (Janneke)'s avatar Verbeek, J.M. (Janneke) :speech_balloon:
Browse files

Merge branch 'review-window-functionality' into 'master'

Review window functionality

See merge request !140
parents 481b315c adca86f0
No related branches found
No related tags found
1 merge request!140Review window functionality
......@@ -2,7 +2,7 @@
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/*
*/.idea/*
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
......
......@@ -4,18 +4,19 @@ import os
from filereader import CSVReader, CSVWriter
from utils import id_regex, shadow_regex
from time import localtime, strftime
from mistake_enum import *
class Controller:
def __init__(self, view, model):
self._model = model
self._view = view
self._view.actionlistener = self.actionlistener
self._view.action_listener = self.action_listener
# File reading and writing supported for .csv only
self._filereader = CSVReader()
self._filewriter = CSVWriter()
self._file_reader = CSVReader()
self._file_writer = CSVWriter()
# Lists of paths, represented as string
self._source_files = []
......@@ -41,13 +42,13 @@ class Controller:
"""Start program user interface."""
self._view.display()
def actionlistener(self, key):
def action_listener(self, key):
"""Action listener for view events. Called by View class.
Args:
key: Identifier for View functionality
"""
action, *type_code = key.split(' ') # type_code is a (possibly empty) list
action, *type_code = key.split(' ')
if action == 'select':
self._select_files(type_code[0])
elif action == 'delete':
......@@ -57,7 +58,7 @@ class Controller:
elif action == 'save':
self._save_results()
elif action == 'save_csv':
self._filewriter = CSVWriter()
self._file_writer = CSVWriter()
elif action == 'select_folder':
self._select_folder(type_code[0])
elif action == 'rm_all':
......@@ -66,6 +67,16 @@ class Controller:
self._toggle_new()
elif action == 'anchor' or action == 'nw':
self._model.change_algorithm(action)
elif action == 'review':
self._review_files()
elif action == 'save_review':
self.save_review()
elif action == 'cancel_review':
self._view.close_review()
elif action == 'combobox_review':
self._select_review()
elif action == 'build_tree':
self.insert_participants()
def _select_folder(self, type_code='shadow'):
"""Select a folder from which multiple files can be read.
......@@ -79,7 +90,7 @@ class Controller:
file_paths = [folder_path + '/' + x
for x in file_names if '.csv' in x]
self._set_path(file_paths, type_code)
self._view.update_files(file_paths, type_code)
self._view.update_file_list(file_paths, type_code)
self._toggle_new()
else:
self._view.update_message('no_file')
......@@ -88,7 +99,7 @@ class Controller:
"""Set the file paths.
Args:
paths: Filepaths.
paths: File paths
type_code: Role of files ('source' or 'shadow')
"""
if type_code == 'source':
......@@ -106,7 +117,7 @@ class Controller:
files = getattr(self, '_{}_files'.format(type_code))
selection = self._view.select_files(type_code)
files.extend(selection)
self._view.update_files(files, type_code)
self._view.update_file_list(files, type_code)
self._toggle_new()
def _delete_files(self, type_code, remove_all=False):
......@@ -134,7 +145,7 @@ class Controller:
self._model.remove_task(selection)
self._view.button_status('select_folder {}'.format(type_code),
'normal')
self._view.update_files(files, type_code)
self._view.update_file_list(files, type_code)
self._toggle_new()
def _compare_files(self):
......@@ -153,6 +164,7 @@ class Controller:
if self._model.analysis_complete:
self._view.update_message('comparison complete')
self._view.button_status('save', 'normal')
self._view.button_status('review', 'normal')
else:
self._view.update_message('no read')
......@@ -168,12 +180,12 @@ class Controller:
mistake_title = self._compose_csv_title('mistakes')
delay_title = self._compose_csv_title('delay')
self._view.update_message('saved')
self._filewriter.write_frame(path,
mistake_results,
mistake_title)
self._filewriter.write_frame(path,
delay_results,
delay_title)
self._file_writer.write_frame(path,
mistake_results,
mistake_title)
self._file_writer.write_frame(path,
delay_results,
delay_title)
self._view.update_message('saved')
except PermissionError:
self._view.update_message('no_dir')
......@@ -195,17 +207,17 @@ class Controller:
success = False
else:
success = True
source_data = self._filereader.read(file_path, 'source')
source_data = self._file_reader.read(file_path, 'source')
shadows = np.array([])
source_success.append(success)
for shadow_candidate in non_matched_shadows:
participant, task = shadow_regex(shadow_candidate, video)
if task is not None:
shadow_data = self._filereader.read(shadow_candidate,
participant, condition = shadow_regex(shadow_candidate, video)
if condition is not None:
shadow_data = self._file_reader.read(shadow_candidate,
'shadow')
self._model.add_task(participant,
video,
task,
condition,
source_data,
shadow_data)
np.append(shadows, shadow_candidate)
......@@ -238,3 +250,157 @@ class Controller:
time = strftime("%d%m%y_%H%M%S", localtime())
title = "{}_{}_{}.csv".format(file_name, algorithm, time)
return title
def _review_files(self):
"""Open the result review window."""
task_names = [str(repr(task)) for task in self._model.shadow_tasks]
task_names.sort()
self._view.display_review(task_names)
def _select_review(self):
"""Change mistake in Treeview"""
tree = self._view._review._elements['tree']
selection = tree.selection()
mistake_string = self._view._review._elements['mistake_box'].get()
new_mistake = None
# Mistakes retrieved from Treeview are strings, therefore:
for m in Mistake:
if mistake_string.lower() == m.value:
new_mistake = m
# Change into selected mistake
for item in selection:
source, onset_src, shadow, onset_shd, mistake, review = tree.item(item)['values']
newvals = (source, onset_src, shadow, onset_shd, mistake, new_mistake.name)
tree.item(item, values=newvals)
def retrieve_participants(self, entry):
"""Get all shadow tasks corresponding to participant/condition/video
code.
Args:
entry: the participant/condition/video code
Returns:
participants: all shadow tasks corresponding to the code. """
participants = []
for m in self._model._shadow_tasks:
if m.participant+"_"+m.condition+m.video == entry:
participants.append(m)
return participants
def reset_participants(self, participants):
"""Update mistake information according to reviewed mistakes
by recalculating accuracy"""
for m in self._model._shadow_tasks:
for p in participants:
if m.participant+"_"+m.condition+m.video ==\
p.participant+"_"+p.condition+p.video:
p.results = \
self._model._stats._mistake_counter.\
calculate_accuracy(p.source, p.shadow)
def insert_participants(self):
"""Insert all shadow tasks/mistakes into the Treeview"""
# Retrieve some necessary values
tree = self._view._review._elements['tree']
entries = self._view._review._entries
counter = 1
# Insert shadow tasks into the tree
for e in entries:
part1 = tree.insert("", counter, text="{}".format(e))
parts = self.retrieve_participants(e)
for participant in parts:
# Handle source words
for s in participant.source:
if s.mistake is not None:
source = s.word
onset_src = round(s.onset, 3)
if s.shadow is None:
shadow = "NA"
onset_shd = "NA"
mistake = s.mistake
tree.insert(part1, "end", text="{}".format(e),
values=(source, onset_src, shadow,
onset_shd, mistake.name,
mistake.name))
# Handle shadow words
for s in participant.shadow:
if s.source is None: # Without source word
source = "NA"
onset_src = "NA"
shadow = s.word
onset_shd = round(s.onset, 3)
mistake = s.mistake
if mistake is not None:
tree.insert(part1, "end", text="{}".format(e),
values=(source, onset_src, shadow,
onset_shd, mistake.name,
mistake.name))
else: # Without shadow word
source = s.source.word
onset_src = s.source.onset
shadow = s.word
onset_shd = round(s.onset, 3)
mistake = s.mistake
if mistake is not None:
tree.insert(part1, "end", text="{}".format(e),
values=(source, onset_src, shadow,
onset_shd, mistake.name,
mistake.name))
counter += 1
def save_review(self):
"""Change internal data structure to reviewed mistake"""
# Retrieve some necessary values
tree = self._view._review._elements['tree']
entries = self._view._review._entries
changed = []
# Retrieve string mistake values from tree
for e in entries:
for child in tree.get_children():
for grandchild in tree.get_children(child):
if tree.item(grandchild)['text'] == e:
src, _, shd, _, _, new_mistake = \
tree.item(grandchild)['values']
mistake = None
for m in Mistake:
# Mistakes are strings, therefore:
if new_mistake.lower() == m.value:
mistake = m
changed.append([src, shd, mistake])
parts = self.retrieve_participants(e)
# Set mistakes in source to new mistake
for participant in parts:
for s in participant.source:
for c in changed:
if s.word == c[0] and (s.shadow is None):
new_mistake = c[2]
s.mistake = new_mistake
if new_mistake == Mistake.CORRECT:
s.mistake = None
s.shadowed = True
# Set mistakes in shadow to new mistake
for s in participant.shadow:
for c in changed:
if s.word == c[1] and s.source is None:
new_mistake = c[2]
s.mistake = new_mistake
elif s.word == c[1] and s.source.word == c[0]:
new_mistake = c[2]
if new_mistake == Mistake.CORRECT:
s.mistake = None
s.correct = True
s.mistake = new_mistake
# Really change internal data structure
self.reset_participants(parts)
self._view._review.update_message('review saved')
......@@ -15,6 +15,7 @@ class FileReader (ABC):
pass
@staticmethod
@abstractmethod
def extract_task_data(data):
"""Take the relevant data out of the imported data.
......@@ -24,12 +25,7 @@ class FileReader (ABC):
Returns:
extraction: Dataframe of only the relevant data
"""
extraction = data.iloc[:, 5:8]
extraction = extraction.copy()
extraction.columns = ['Onset', 'Offset', 'Word']
extraction[['Onset', 'Offset']] = (extraction[['Onset', 'Offset']]
.apply(pd.to_numeric))
return extraction
pass
@staticmethod
def df_to_words(df, type_code='source'):
......@@ -85,6 +81,24 @@ class CSVReader(FileReader):
words = self.df_to_words(data, type_code)
return words
@staticmethod
def extract_task_data(data):
"""Take the relevant data out of the already imported .csv
data.
Args:
data: Dataframe of imported .csv data
Returns:
extraction: Dataframe of only the relevant data
"""
extraction = data.iloc[:, 5:8]
extraction = extraction.copy()
extraction.columns = ['Onset', 'Offset', 'Word']
extraction[['Onset', 'Offset']] = (extraction[['Onset', 'Offset']]
.apply(pd.to_numeric))
return extraction
class FileWriter(ABC):
......
from mistake_enum import Mistake
from mistake_enum import *
class MistakeCounter:
......
......@@ -16,3 +16,4 @@ class Mistake(Enum):
SKIPPED = 'skipped' # For source words that are not shadowed
RANDOM = 'random' # For shadow words that do not reflect a source word
FORM = 'form' # For verbs that are shadowed in another form
CORRECT = 'correct'
from mistake_enum import Mistake
from mistake_enum import *
class MistakeFinder:
......
......@@ -84,14 +84,14 @@ class Model:
Dataframe with delay information
"""
delay_frame = ({'participant': [],
'assignment': [],
'condition': [],
'video': [],
'words': [],
'delays': []})
for trial in self._shadow_tasks:
for result in trial.delays:
delay_frame['participant'].append(result[0])
delay_frame['assignment'].append(result[1])
delay_frame['condition'].append(result[1])
delay_frame['video'].append(result[2])
delay_frame['words'].append(result[3])
delay_frame['delays'].append(result[4])
......@@ -104,7 +104,7 @@ class Model:
DataFrame with mistake information filled in
"""
d = ({'participant': [],
'assignment': [],
'condition': [],
'video': [],
'#mistakes': [],
'accuracy': [],
......@@ -116,7 +116,7 @@ class Model:
'%random': []})
for trial in self._shadow_tasks:
d['participant'].append(trial.participant)
d['assignment'].append(trial.condition)
d['condition'].append(trial.condition)
d['video'].append(trial.video)
d['accuracy'].append(trial.results['accuracy'])
d['#mistakes'].append(trial.results['#mistakes'])
......@@ -146,3 +146,6 @@ class Model:
String representation of the strategy
"""
return self._stats.last_used_strategy
def create_review_string(self):
return ""
from mistake_counter import MistakeCounter
class ShadowTask:
"""Wrapper for all required data in a task."""
......@@ -10,6 +12,21 @@ class ShadowTask:
self._results = None
self._delays = None
def __str__(self):
return ("participant: {} video: {} condition: {} result: {}"
.format(self._participant,
self._video,
self._condition,
self._results)
)
def __repr__(self):
return ("{}_{}{}"
.format(self._participant,
self._condition,
self._video)
)
@property
def participant(self):
return self._participant
......@@ -26,10 +43,18 @@ class ShadowTask:
def source(self):
return self._source
@source.setter
def source(self, val):
self._source = val
@property
def shadow(self):
return self._shadow
@shadow.setter
def shadow(self, val):
self._shadow = val
@property
def results(self):
return self._results
......@@ -45,10 +70,3 @@ class ShadowTask:
@delays.setter
def delays(self, delays):
self._delays = delays
def __str__(self):
return ("participant: {} video: {} condition: {} result: {}"
.format(self._participant,
self._video,
self._condition,
self._results))
......@@ -61,15 +61,15 @@ def shadow_regex(path, video):
Returns:
participant: Participant number
task: Task for the participant
condition: Code for the condition of this video
"""
match = re.search(f'(\d+)_(\D+){video}\.T', path)
task = None
condition = None
participant = None
if match:
participant = match.group(1)
task = match.group(2)
return participant, task
condition = match.group(2)
return participant, condition
def id_regex(path):
......
......@@ -4,7 +4,7 @@ import tkinter.filedialog as fd
from functools import partial
from tkinter import ttk, W
from PIL import ImageTk, Image
from mistake_enum import *
from utils import set_icon, get_path, add_to_dict
......@@ -19,6 +19,7 @@ class View:
# Draw splash
self._splash = SplashView(self._window)
self._review = None
# Create rest of GUI
self._window.wm_title("Umbra")
......@@ -26,7 +27,7 @@ class View:
self._elements = {} # Dict to avoid large number of attributes
self._selected_source = "No file selected"
self._selected_shadow = "No file selected"
self._actionlistener = None
self._action_listener = None
self._algorithm = tk.StringVar(value='anchor')
# Initialisation of visual and interactive elements is listed top-down
......@@ -38,7 +39,7 @@ class View:
"Select the algorithm",
1, 1)
# Frame with elements for algorithm selection
self._create_algselection_frame()
self._create_algorithm_frame()
# File selection label/instructions
self._create_label('file_instructions',
......@@ -47,11 +48,13 @@ class View:
4, 1)
# Frames with elements for source and shadow
self._create_fileselection_frame('source')
self._create_fileselection_frame('shadow')
self._create_import_frame('source')
self._create_import_frame('shadow')
# Compare and save buttons
# Compare, review and save buttons
self._create_button('compare', self._frame, 'Compare', 7, 1)
self._create_button('review', self._frame, 'Review results', 8, 1)
self.button_status('review', 'disabled')
self._create_button('save', self._frame, 'Save result', 7, 2)
self.button_status('save', 'disabled')
......@@ -62,6 +65,14 @@ class View:
# Window Icon
set_icon(self._window, get_path('resources/logo.ico'))
@property
def action_listener(self):
return self._action_listener
@action_listener.setter
def action_listener(self, value):
self._action_listener = value
def display(self):
"""Start main loop, displaying GUI elements."""
self._splash.terminate()
......@@ -94,36 +105,46 @@ class View:
("all files", '*.*')))
return paths
def update_files(self, paths, type_code):
def update_file_list(self, paths, type_code):
"""Update files representation to display given list of files.
Args:
paths: List of file paths to be represented
type_code: Role of files ('source' or 'shadow')
"""
filebox = self._elements['file {}'.format(type_code)]
file_box = self._elements['file {}'.format(type_code)]
if paths == []:
filebox.set("No file selected")
filebox['values'] = []
filebox['state'] = 'disabled'
if not paths:
file_box.set("No file selected")
file_box['values'] = []
file_box['state'] = 'disabled'
else:
filenames = []
file_names = []
for path in paths:
filenames.append(path.title().split("/")[-1])
filebox['values'] = filenames
filebox.set(filenames[-1])
filebox['state'] = 'read_only'
file_names.append(path.title().split('/')[-1])
self._set_entries_combobox(file_box, file_names)
# Easier to just update both than to do a type check AGAIN
self._selected_source = self._elements['file source'].get()
self._selected_shadow = self._elements['file shadow'].get()
@staticmethod
def _set_entries_combobox(combobox, entries):
"""Set the entries that a combobox lists.
Args:
combobox: the combobox which will be updated
entries: list with entries for the combobox
"""
combobox['values'] = entries
combobox.set(entries[-1])
combobox['state'] = 'read_only'
def reset_message(self):
"""Set the message frame label to read 'Messages'."""
# This usually happens when the frame text reads something
# different than 'Messages', hence the name of the method.
self._elements['msgframe']['text'] = 'Messages'
self._elements['msg_frame']['text'] = 'Messages'
def update_message(self, context=None):
"""Update message text element.
......@@ -157,7 +178,7 @@ class View:
self._i_am_error(warning=True)
elif context == 'no read':
message = "Could not read in files. " \
"Please check the selected files."
"Please check the selected files."
elif context == 'no_dir':
message = "Please select a directory"
else:
......@@ -177,23 +198,27 @@ class View:
key: Identifier for exact event
"""
# Keys that require some controller action
controllerkeys = [
'select source',
'delete source',
'select shadow',
'delete shadow',
'compare',
'save',
'select_folder source',
'select_folder shadow',
'rm_all source',
'rm_all shadow',
'new_comparison',
'anchor',
'nw'
]
if key in controllerkeys:
self._actionlistener(key)
controller_keys = [
'select source',
'delete source',
'select shadow',
'delete shadow',
'compare',
'save',
'select_folder source',
'select_folder shadow',
'rm_all source',
'rm_all shadow',
'new_comparison',
'anchor',
'nw',
'review',
'build_tree',
'combobox_review',
'cancel_review'
]
if key in controller_keys:
self._action_listener(key)
def ask_save_location(self):
"""Ask user for location to save file.
......@@ -228,7 +253,7 @@ class View:
self._pos_right = int(width + width_pad)
self._pos_down = int(height + height_pad)
window.geometry('+{}+{}'.format(self._pos_right, self._pos_down))
window.overrideredirect(overrideddirect)
#window.overrideredirect(overrode_direct)
window.update()
if withdraw:
window.withdraw()
......@@ -243,7 +268,8 @@ class View:
self._create_label('message' + str(row), frame, '', row, 1,
pad=True)
def onFrameConfigure(self, canvas):
@staticmethod
def on_frame_configure(canvas):
"""Reset the scroll region to encompass the inner frame.
Args:
......@@ -253,23 +279,24 @@ class View:
def _create_message_frame(self):
"""Create the frame in which to display messages."""
frame = tk.LabelFrame(self._frame, text='Messages', width = 200,
height = 80)
frame.grid(column=1, row=8, padx=15, pady=10,
frame = tk.LabelFrame(self._frame, text='Messages', width=200,
height=80)
frame.grid(column=1, row=9, padx=15, pady=10,
columnspan=2, sticky='NESW')
canvas = tk.Canvas(frame, borderwidth=0, height=80)
innerframe = tk.Frame(canvas, height =80)
inner_frame = tk.Frame(canvas, height=80)
vsb = tk.Scrollbar(frame, orient='vertical', command=canvas.yview)
canvas.configure(yscrollcommand=vsb.set)
vsb.pack(side='right', fill='y')
canvas.pack(side='left', fill='both', expand=True)
canvas.create_window((4, 4), window=innerframe, anchor='nw')
canvas.create_window((4, 4), window=inner_frame, anchor='nw')
innerframe.bind('<Configure>',
lambda event, canvas=canvas: self.onFrameConfigure(canvas))
self.populate(innerframe)
add_to_dict('msgframe', frame, self._elements)
inner_frame.bind('<Configure>',
lambda event,
canvas=canvas: self.on_frame_configure(canvas))
self.populate(inner_frame)
add_to_dict('msg_frame', frame, self._elements)
def _i_am_error(self, warning=False):
"""Set message label to WARNING or ERROR dependent on situation.
......@@ -278,11 +305,11 @@ class View:
warning: Whether label is to be WARNING (or ERROR)
"""
if warning:
self._elements['msgframe']['text'] = 'WARNING'
self._elements['msg_frame']['text'] = 'WARNING'
else:
self._elements['msgframe']['text'] = 'ERROR'
self._elements['msg_frame']['text'] = 'ERROR'
def _create_fileselection_frame(self, type_code):
def _create_import_frame(self, type_code):
"""Create frame for file selection for given type.
Args:
......@@ -302,7 +329,6 @@ class View:
# Interactive elements
self._create_combobox('file {}'.format(type_code),
frame,
type_code,
2, column)
self._create_button('select {}'.format(type_code),
frame,
......@@ -321,7 +347,7 @@ class View:
'Add {} folder'.format(type_code),
6, column)
def _create_algselection_frame(self):
def _create_algorithm_frame(self):
"""Create frame for the algorithm selection."""
# Create frame
......@@ -340,7 +366,7 @@ class View:
self._algorithm,
3, 1)
def _create_button(self, key, frame, text, row, column):
def _create_button(self, key, frame, text, row, column, sticky=''):
"""Create button and place it in the given frame.
Args:
......@@ -354,7 +380,7 @@ class View:
ValueError: Key already exists
"""
button = tk.Button(frame, text=text, width=17)
button.grid(column=column, row=row, padx=5, pady=10)
button.grid(column=column, row=row, padx=5, pady=10, sticky=sticky)
action_function = partial(self._perform_action, key=key)
button['command'] = lambda: action_function('<Button>')
......@@ -384,31 +410,31 @@ class View:
add_to_dict(key, radio, self._elements)
def _create_combobox(self, key, frame, type_code, row, column):
def _create_combobox(self, key, frame, row, column, sticky='W'):
"""Create combobox, a drop-down file selection widget.
Args:
key: Key to save button in _elements dictionary
frame: Frame into which to place the button
column: Column in grid to place the button in
type_code: Role of combobox ('source' or 'shadow')
Raises:
ValueError: Key already exists
"""
filebox = ttk.Combobox(frame,
state='disabled',
width=20)
filebox.set("No file selected")
file_box = ttk.Combobox(frame,
state='disabled',
width=20)
file_box.set("No file selected")
filebox.grid(column=column, row=row, sticky=W, padx=10, pady=10)
file_box.grid(column=column, row=row, sticky=sticky, padx=10, pady=10)
action_function = partial(self._perform_action, key=key)
filebox.bind('<Button>', action_function)
file_box.bind('<Button>', action_function)
add_to_dict(key, filebox, self._elements)
add_to_dict(key, file_box, self._elements)
def _create_label(self, key, frame, text, row, column, pad=False):
def _create_label(self, key, frame, text, row, column,
pad=False, sticky=''):
"""Create textual label and place it in the given frame.
Args:
......@@ -421,9 +447,9 @@ class View:
ValueError: Key already exists
"""
label = tk.Label(frame, text=text)
label.grid(column=column, row=row)
label.grid(column=column, row=row, sticky=sticky)
if pad:
label.grid(column=column, row=row, pady=10, padx=10)
label.grid(column=column, row=row, pady=10, padx=10, sticky=sticky)
add_to_dict(key, label, self._elements)
def button_status(self, key, mode):
......@@ -447,20 +473,12 @@ class View:
Returns:
Selected directory
"""
dir = fd.askdirectory()
if dir:
return dir
selection = fd.askdirectory()
if selection:
return selection
else:
self.update_message('no_dir')
@property
def actionlistener(self):
return self._actionlistener
@actionlistener.setter
def actionlistener(self, value):
self._actionlistener = value
def selected(self, type_code):
"""Get selected files.
......@@ -476,9 +494,21 @@ class View:
else:
return selected
def display_review(self, task_names):
"""Displays a pop-up window for reviewing the results of
the analysis."""
self._review = ReviewWindow(self._window, task_names)
self._review.action_listener = self.action_listener
# Create Treeview
self._review._create_tree()
def close_review(self):
"""Close review window."""
self._review.withdraw()
class SplashView(tk.Toplevel, View):
"""A splash screen while the rest of the application is loading. """
"""A splash screen while the rest of the application is loading."""
def __init__(self, parent):
tk.Toplevel.__init__(self, parent)
......@@ -496,3 +526,135 @@ class SplashView(tk.Toplevel, View):
def terminate(self):
"""Close the splash window."""
self.destroy()
class ReviewWindow(tk.Toplevel, View):
def __init__(self, parent, names):
tk.Toplevel.__init__(self, parent)
self.resizable(0, 0)
self.title("Review results")
set_icon(self, get_path('resources/logo.ico'))
self._entries = names
self._elements = {} # Dict to avoid large number of attributes
self._action_listener = None
# Create frames/window
self._file_frame = tk.Frame(self)
self._file_frame.grid(row=1, column=0, padx=10, sticky='W')
self._msg_frame = tk.Frame(self)
self._msg_frame.grid(row=1, column=1, padx=10, sticky='E')
self._bt_frame = tk.Frame(self)
self._create_message_frame()
self._center_window(self)
# Create labels
self._create_label('review_title', self, "Review marked mistakes by " +
"clicking on a mistake" +
"\n and selecting the new mistake" +
"in the dropdown.",
0, 0, False, 'N')
self._elements['review_title'].grid(columnspan=2)
self._create_label('mistake_label', self._file_frame,
'Mistake: ', 1, 0, True, 'W')
# Create mistake combobox
self._create_combobox('mistake_box', self._file_frame,
1, 1)
self.update_mistake_combobox()
# Create buttons
self._create_button('save_review', self._bt_frame, 'Save', 3, 0, 'W')
self._create_button('cancel_review', self._bt_frame, 'Quit', 3, 1,
'W')
self._bt_frame.grid(row=3, column=0, padx=10, sticky='W')
def _perform_action(self, event, key=None):
"""Perform action for an event, based on key.
Args:
event: Event by which the function is called (unused, but
framework requires it)
key: Identifier for exact event.
"""
controller_keys = [
'review',
'build_tree',
'save_review',
'combobox_review',
'cancel_review'
]
if key in controller_keys:
self._action_listener(key)
def update_mistake_combobox(self):
"""Update elements in mistake Combobox."""
action_function = partial(self._perform_action, key="combobox_review")
self._elements['mistake_box'].bind("<<ComboboxSelected>>",
action_function)
mistakes = ["CORRECT","FORM","PHONETIC", "RANDOM", "REPETITION",
"SEMANTIC", "SKIPPED"
]
self._set_entries_combobox(self._elements['mistake_box'], mistakes)
def _create_tree(self):
"""Create a Treeview"""
wordlist_frame = tk.Frame(self)
add_to_dict("frame", wordlist_frame, self._elements)
tree = ttk.Treeview(wordlist_frame)
add_to_dict("tree", tree, self._elements)
self._build_tree(2, 0)
def _build_tree(self, row, column):
"""Further build a Treeview"""
tree = self._elements['tree']
wordlist_frame = self._elements['frame']
ysb = ttk.Scrollbar(wordlist_frame, orient='vertical',
command=tree.yview)
tree.configure(yscroll=ysb.set)
names = ["Participant", "Source", "Onset", "Shadow", "Mistake",
"Review"]
headers = ("#0", "ptc", "src", "ons", "shd", "mis", "rev")
tree["columns"] = headers
for i in range(len(names)):
tree.heading(headers[i], text=names[i], anchor=tk.W)
self.action_listener('build_tree')
ysb.pack(side='right', fill='y')
tree.pack(side=tk.TOP, fill=tk.X)
wordlist_frame.grid(row=row, column=column, columnspan=2, padx=10)
def _create_message_frame(self):
"""Create the frame in which to display messages."""
frame = tk.LabelFrame(self._msg_frame, text='Messages', width=300,
height=30)
frame.grid(column=1, row=1, padx=15, pady=10,
columnspan=2, sticky='E')
self._create_label('review_msg', frame,
'\t\t\t\t\t', 1, 0, True, 'W')
add_to_dict('review_msg_frame', frame, self._elements)
def update_message(self, context=None):
"""Update message text element.
Args:
context: Context that the message should reflect
"""
if context == 'review saved':
message = "Review saved successfully"
self._elements['review_msg']['text'] = message
@property
def action_listener(self):
return self._action_listener
@action_listener.setter
def action_listener(self, value):
self._action_listener = value
@property
def action_listener(self):
return self._action_listener
@action_listener.setter
def action_listener(self, value):
self._action_listener = value
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment