Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • msdt/team1920-speechcomparison
1 result
Show changes
Commits on Source (37)
......@@ -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')
......@@ -113,8 +113,8 @@ class DutchPhonetics:
"""
word1_phon = PhonRep(word1)
word2_phon = PhonRep(word2)
self._mmetaphone(word1_phon)
self._mmetaphone(word2_phon)
self._convert_to_phone(word1_phon)
self._convert_to_phone(word2_phon)
combinations = list(itertools.product(word1_phon.representation,
word2_phon.representation))
for comb in combinations:
......@@ -144,7 +144,7 @@ class DutchPhonetics:
and word1 != word2 and len(word1) > 3
and len(word2) > 3)
def _mmetaphone(self, word):
def _convert_to_phone(self, word):
"""Convert raw phonetic representation to correct one.
Args:
......
......@@ -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 ""
umbra/resources/splash.png

18.4 KiB | W: 0px | H: 0px

umbra/resources/splash.png

17 KiB | W: 0px | H: 0px

umbra/resources/splash.png
umbra/resources/splash.png
umbra/resources/splash.png
umbra/resources/splash.png
  • 2-up
  • Swipe
  • Onion skin
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))
......@@ -2,7 +2,7 @@ import copy
import time
from anchor_algorithm import AnchorAlgorithm
from dutch_mmetaphone import DutchPhonetics
from dutch_phonenetics import DutchPhonetics
from form_checker import FormChecker
from mistake_counter import MistakeCounter
from mistake_finder import MistakeFinder
......
......@@ -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