Example #1
0
    def __init__(self):
        self.window = gtk.Window(gtk.WindowType.TOPLEVEL)
        self.window.set_title('Data Viewer')
        self.window.connect('destroy', lambda w: self._clean_up())
        self.window.set_border_width(10)
        self.window.set_default_size(800, 600)
        self.logger = logging.getLogger(__name__)
                                         
        self.csv_filename = UIUtils.open_file('Select csv file', filters=[UIUtils.CSV_FILE_FILTER, UIUtils.ALL_FILE_FILTER])
        self.window.set_title('%s - %s' % (self.window.get_title(), os.path.basename(self.csv_filename)))
        if not self.csv_filename:
            exit(0)

        self.wav_filename = self._get_wav_filename()
        self.wav_parser = None
        if self.wav_filename:
            self.wav_parser = WavParser(self.wav_filename)
        # if not self.wav_filename:
        #     exit(0)

        self.sound_col_fcns = None

        col_datatypes, col_headers = self._get_col_info()

        db = self._build_db(col_datatypes, col_headers)
        
        model = self._get_model(col_datatypes)
        treeview = self._get_treeview(model, col_headers, db)

        self.filters = []
        self.where_cond = None
        self.where_params = []
        self.sort_order = None
        self.sort_col = None
        self._populate_model(db, model)

        toolbar = self._build_toolbar(db, treeview, col_headers)
        scrolled_win = gtk.ScrolledWindow()
        scrolled_win.set_policy(gtk.PolicyType.AUTOMATIC, gtk.PolicyType.AUTOMATIC)
        scrolled_win.add(treeview)

        vbox = gtk.VBox()
        vbox.pack_start(toolbar, False, False, 0)
        vbox.pack_start(scrolled_win, True, True, 0)
        self.window.add(vbox)

        self.window.show_all()
Example #2
0
    def __init__(self, check):
        self.logger = logging.getLogger(__name__)

        self.check = check

        self.wav_parser = WavParser(self.check.wav_filename)

        self.window = gtk.Window(gtk.WindowType.TOPLEVEL)
        self.window.set_title('Testing')
        self.window.connect(
            'destroy',
            lambda w: self._exit())  #will save current input and exit
        self.window.set_border_width(10)
        self.window.set_default_size(350, 200)

        self.wo_form = Form()
        self.wo_form.handler_man = HandlerManager()
        self.w_form = Form()
        self.w_form.handler_man = HandlerManager()

        self.button_form = Form()
        self.button_form.handler_man = HandlerManager()

        self.progress_bar = self._build_progress_bar()
        self.w_context_frame = self._build_w_context_frame()
        button_box = self._build_button_box()

        self.wo_context_frame = self._build_wo_context_frame()
        self.wo_context_checkbox = self._build_wo_context_checkbox(
            self.wo_context_frame)

        vbox = gtk.VBox()
        vbox.pack_start(self.progress_bar, True, True, 0)
        vbox.pack_start(self.wo_context_checkbox, True, True, 0)
        vbox.pack_start(self.wo_context_frame, True, True, 0)
        vbox.pack_start(self.w_context_frame, True, True, 0)
        vbox.pack_end(button_box, True, True, 0)

        self.window.add(vbox)
        self.window.show_all()

        self._update_progress_bar()
        self._toggle_wo_context_frame(self.wo_context_checkbox.get_active())
        self._set_ui_to_cur_test()
Example #3
0
    def play_selected_seg(self):
        (model, it) = self.treeview.get_selection().get_selected()
        if it:
            #if they've selected an error row, find the top level parent (the segment) and use it instead
            parent = model.iter_parent(it)
            while parent:
                it = parent
                parent = model.iter_parent(it)

            seg_num = model.get_value(it, 0) if it else None
            seg = self.trs_parser.parse()[seg_num]

            if not self.wav_parser:
                dialog = gtk.FileChooserDialog(
                    title='Select WAV File',
                    action=gtk.FileChooserAction.OPEN,
                    buttons=(gtk.STOCK_CANCEL, gtk.ResponseType.CANCEL,
                             gtk.STOCK_OPEN, gtk.ResponseType.OK))
                dialog.set_default_response(gtk.ResponseType.OK)

                for filter_opt in (('wav Files', '*.wav'), ('All Files', '*')):
                    file_filter = gtk.FileFilter()
                    file_filter.set_name(filter_opt[0])
                    file_filter.add_pattern(filter_opt[1])
                    dialog.add_filter(file_filter)

                response = dialog.run()
                if response == gtk.ResponseType.OK:
                    filename = dialog.get_filename()
                    self.wav_parser = WavParser(filename)

                dialog.destroy()

            if self.wav_parser:
                self.wav_parser.play_seg(seg)

            else:
                UIUtils.show_no_sel_dialog()
        else:
            UIUtils.show_no_sel_dialog()
Example #4
0
    def _play_clip(self, db):
        map(lambda button: button.set_sensitive(False), self.scale_buttons)
        while gtk.events_pending():
            gtk.main_iteration()

        time.sleep(self.props.inter_clip_sound_del)

        self._toggle_playing_icon(True)

        wav_parser = WavParser(self.filename)
        wav_parser.play_clip(0, wav_parser.get_sound_len())
        wav_parser.close()

        map(lambda button: button.set_sensitive(True), self.scale_buttons)
        self._toggle_playing_icon(False)
Example #5
0
class MainWindow():
    #if none of these match, then string is used
    DATA_TYPE_REGEXS = {
        float: r'^-?\d+(\.\d+)?$',
        bool: r'^True|False$',
        }

    START_COL_NAMES = ['Start Time', 'Wav.Begin']
    END_COL_NAMES = ['End Time', 'Wav.End']
    DUR_COL_NAMES = ['Duration', 'Segment_Duration']
    EL_TIME_COL_NAMES = ['Elapsed_Time']
    
    def __init__(self):
        self.window = gtk.Window(gtk.WindowType.TOPLEVEL)
        self.window.set_title('Data Viewer')
        self.window.connect('destroy', lambda w: self._clean_up())
        self.window.set_border_width(10)
        self.window.set_default_size(800, 600)
        self.logger = logging.getLogger(__name__)
                                         
        self.csv_filename = UIUtils.open_file('Select csv file', filters=[UIUtils.CSV_FILE_FILTER, UIUtils.ALL_FILE_FILTER])
        self.window.set_title('%s - %s' % (self.window.get_title(), os.path.basename(self.csv_filename)))
        if not self.csv_filename:
            exit(0)

        self.wav_filename = self._get_wav_filename()
        self.wav_parser = None
        if self.wav_filename:
            self.wav_parser = WavParser(self.wav_filename)
        # if not self.wav_filename:
        #     exit(0)

        self.sound_col_fcns = None

        col_datatypes, col_headers = self._get_col_info()

        db = self._build_db(col_datatypes, col_headers)
        
        model = self._get_model(col_datatypes)
        treeview = self._get_treeview(model, col_headers, db)

        self.filters = []
        self.where_cond = None
        self.where_params = []
        self.sort_order = None
        self.sort_col = None
        self._populate_model(db, model)

        toolbar = self._build_toolbar(db, treeview, col_headers)
        scrolled_win = gtk.ScrolledWindow()
        scrolled_win.set_policy(gtk.PolicyType.AUTOMATIC, gtk.PolicyType.AUTOMATIC)
        scrolled_win.add(treeview)

        vbox = gtk.VBox()
        vbox.pack_start(toolbar, False, False, 0)
        vbox.pack_start(scrolled_win, True, True, 0)
        self.window.add(vbox)

        self.window.show_all()

    def _get_wav_filename(self):
        wav_filename = None
        
        #try to find a wav file with the same name as the csv file
        if self.csv_filename.lower().endswith('.csv'):
            default_filename = self.csv_filename[:-3] + 'wav'
            if os.path.exists(default_filename):
                wav_filename = default_filename

        #if the above didn't succeed, prompt the user for a filename
        if not wav_filename:
            wav_filename = UIUtils.open_file(title='Select wav file', filters=[UIUtils.WAV_FILE_FILTER, UIUtils.ALL_FILE_FILTER])

        return wav_filename

    def _get_csv_delim(self, csv_file):
        pos = csv_file.tell()
        delim = ',' if csv_file.next().find(',') > -1 else '\t'
        csv_file.seek(pos)

        return delim

    def _get_col_info(self):
        csv_file = open(self.csv_filename, 'rb')
        delim = self._get_csv_delim(csv_file)
        reader = csv.reader(csv_file, delimiter=delim)
        headers = reader.next()

        line = reader.next()
        datatypes = [int] #for hidden id column
        headers = ['id'] + headers
        for col in line:
            datatypes.append(self._get_datatype(col))

        csv_file.close()

        #append marked column, if not already present
        if datatypes and not datatypes[-1] == bool:
            datatypes.append(bool)
            headers.append('Marked')

        return datatypes, headers

    def _get_datatype(self, col_val):
        col_type = None
        i = 0
        regex_keys = MainWindow.DATA_TYPE_REGEXS.keys()
        while not col_type and i < len(regex_keys):
            if re.match(MainWindow.DATA_TYPE_REGEXS[regex_keys[i]], col_val):
                col_type = regex_keys[i]
            i += 1

        if not col_type:
            col_type = str

        return col_type

    def _get_model(self, col_datatypes):
        #tell the treeview that all columns are strings (even those that are really floats). This way, floats and numbers will appear exactly as they do in the input spreadsheet, and not using gtk's default precision (i.e. we don't have to do any number formatting / rounding). Since the db has the actual types for its columns, and it is in change of filtering and sorting (NOT the UI), this works out fine.
        #The exception is the last column ('marked'); it requires type bool for the checkboxes to function properly in the ui

        col_types = [int] + [str] * (len(col_datatypes) - 2) + [bool]

        return gtk.ListStore(*col_types)

    def _get_treeview(self, model, headers, db):
        treeview = gtk.TreeView(model)

        #force the row selection colours to stay the same even when the treeview is deselected (otherwise the selection is hard to see - and seeing this at all times is important in this app's use cases)
        treeview.modify_base(gtk.StateFlags.ACTIVE, gdk.Color.parse('#3399FF')[1])
        treeview.modify_text(gtk.StateFlags.ACTIVE, gdk.Color.parse('#FFFFFF')[1])

        col_index = 0
        col = gtk.TreeViewColumn(headers[0], gtk.CellRendererText(), text=col_index)
        col.set_visible(False)
        treeview.append_column(col)
        
        for col_index in range(1, len(headers) - 1):
            col = gtk.TreeViewColumn(headers[col_index], gtk.CellRendererText(), text=col_index)
            col.set_resizable(True)
            col.set_reorderable(True)
            col.set_visible(col.get_title() != '' and col.get_title() != '# segments')
            if col.get_visible():
                col.set_clickable(True)
                col.connect('clicked', self._toggle_sort_column, treeview, db, col_index)
            treeview.append_column(col)

        toggleRenderer = gtk.CellRendererToggle()
        toggleRenderer.connect('toggled', self._toggle_renderer_callback, model, col_index + 1, db)
        mark_col = gtk.TreeViewColumn(headers[col_index + 1], toggleRenderer, active=col_index + 1)
        mark_col.set_resizable(True)
        mark_col.set_reorderable(True)
        mark_col.set_clickable(True)
        mark_col.connect('clicked', self._toggle_sort_column, treeview, db, col_index + 1)
        mark_col.set_alignment(0.5) #position the title in the middle, since this column's wider than the others (since it's the last visible column)
        treeview.append_column(mark_col)
        
        return treeview

    def _toggle_sort_column(self, treeview_col, treeview, db, treeview_col_index):
        #clear any previous sort indicators on all columns except the one we care about
        for col in treeview.get_columns():
            if col != treeview_col:
                col.set_sort_indicator(False)

        if treeview_col.get_sort_indicator():
            sort_order = treeview_col.get_sort_order()

            if sort_order == gtk.SortType.ASCENDING:
                self.sort_order = CSVDatabase.ORDER_DIRS.DESC
                self.sort_col = treeview_col_index

                treeview_col.set_sort_order(gtk.SortType.DESCENDING)
                
            elif sort_order == gtk.SortType.DESCENDING:
                self.sort_order = None
                self.sort_col = None

                treeview_col.set_sort_indicator(False)
        else:
            self.sort_order = CSVDatabase.ORDER_DIRS.ASC
            self.sort_col = treeview_col_index

            treeview_col.set_sort_indicator(True)
            treeview_col.set_sort_order(gtk.SortType.ASCENDING)

        self._populate_model(db, treeview.get_model())

    #for debugging
    def _print_model(self, model):
        for row in model:
            s = ''
            for item in row:
                s += str(item) + ' '
            print s

    def _toggle_renderer_callback(self, renderer, path, model, col_index, db):
        if path is not None:
            model[path][col_index] = not model[path][col_index]
            db.csv_update_by_index([model.get_n_columns() - 1], where_cond='id=?', params=[int(model[path][col_index]), model[path][0]])

    def _build_db(self, col_datatypes, col_headers):
        csv_file = open(self.csv_filename, 'rb')
        delim = self._get_csv_delim(csv_file)
        lines = csv_file.readlines()
        reader = csv.reader(lines, delimiter=delim)
        header_row = reader.next() #skip headers row

        db = CSVDatabase(col_headers, col_datatypes)

        progress_dialog = ProgressDialog(title='Loading file', phases=['Loading file...'])
        progress_dialog.show()
        
        num_rows = len(lines) - 1 #subtract one for header

        done = False
        i = 0
        while i < num_rows and not done:
            row = reader.next()

            #if this file already contains counts at the end (has already been exported), skip those rows
            done = row and (row[0].startswith('File Stats') or row[0].startswith('Count of'))
            if not done:
                #if we have a marked column, use it - otherwise, append one
                if re.match(MainWindow.DATA_TYPE_REGEXS[bool], row[-1]):
                    row = row[:-1] + [int(bool(row[-1] == 'True'))]
                else:
                    row = row + [0]

                db.csv_insert([i + 1] + row)

            if (i % 10):
                progress_dialog.set_fraction(float(i + 1) / float(num_rows))

            i += 1

        progress_dialog.ensure_finish()
        csv_file.close()
        
        return db

    def _populate_model(self, db, model):
        model.clear()

        #order_by_indices = None
        #order_by_dirs = None
        order_by = None
        if self.sort_col != None and self.sort_order != None:
            order_by = '%s%s %s' % (CSVDatabase.COL_PREFIX, self.sort_col, self.sort_order)
            # order_by_indices = [self.sort_col]
            # order_by_dirs = [self.sort_order]

        #print self.where_cond, self.where_params, model.get_n_columns()
        rows = db.select(
            CSVDatabase.TABLE_NAME,
            [('%s%d' % (CSVDatabase.COL_PREFIX, i)) for i in range(model.get_n_columns())],
            where_cond=self.where_cond,
            params=self.where_params,
            order_by=order_by
        )
        
        #rows = db.csv_select_by_index(where_cond=self.where_cond, params=self.where_params, order_by_indices=order_by_indices, order_by_dirs=order_by_dirs)
        for cur_row in rows:
            cur_row = list(cur_row)
            
            for i in range(1, len(cur_row) - 1):
                cur_row[i] = str(cur_row[i])
            cur_row[-1] = bool(cur_row[-1])
            
            model.append(cur_row)

    def _filter_callback(self, filters, db, model, search_entry, col_headers):
        #Filters will be None if the user clicked cancel in the FilterWindow
        if filters != None:
            self.filters = filters
            where_cond, where_params = FilterWindow.get_sql_where_cond(self.filters)
            self.where_cond = where_cond
            self.where_params = where_params
            self._populate_model(db, model)

            desc = FilterWindow.get_filters_desc(filters, col_headers)
            search_entry.set_text(desc)

    def _update_filters(self, db, model, col_headers, search_entry):
        FilterWindow(self.filters, col_headers, lambda filters: self._filter_callback(filters, db, model, search_entry, col_headers))

    def _clean_up(self):
        if self.wav_parser:
            self.wav_parser.close()
            
        gtk.main_quit()

    def _build_toolbar(self, db, treeview, col_headers):
        toolbar = gtk.Toolbar()

        clear_img_path = UIUtils.get_icon_path(UIUtils.BUTTON_ICONS.CLEAR, UIUtils.BUTTON_ICON_SIZES.PX16)
        #clear_pixbuf = gtk.gdk.pixbuf_new_from_file(clear_img_path)
        clear_pixbuf = GdkPixbuf.Pixbuf.new_from_file(clear_img_path)

        search_entry = gtk.Entry()
        search_entry.set_sensitive(False)
        search_entry.set_text(FilterWindow.get_filters_desc(self.filters, col_headers))
        
        filter_button = UIUtils.create_button('Filters', UIUtils.BUTTON_ICONS.FILTER, UIUtils.BUTTON_ICON_SIZES.PX16)
        filter_button.connect('clicked', lambda w: self._update_filters(db, treeview.get_model(), col_headers, search_entry))

        play_button = UIUtils.create_button('Play', UIUtils.BUTTON_ICONS.PLAY, UIUtils.BUTTON_ICON_SIZES.PX16)
        play_button.connect('clicked', lambda w: self._play_selected_row(col_headers, treeview))
        praat_button = UIUtils.create_button('Praat', UIUtils.BUTTON_ICONS.PRAAT, UIUtils.BUTTON_ICON_SIZES.PX16)
        praat_button.connect('clicked', lambda w: self._open_in_praat(col_headers, treeview))

        export_button = UIUtils.create_button('Export', UIUtils.BUTTON_ICONS.EXPORT, UIUtils.BUTTON_ICON_SIZES.PX16)
        export_button.connect('clicked', lambda w: self._export(treeview, col_headers, db))

        context_label = gtk.Label('Context')
        context_adj = gtk.Adjustment(value=0, lower=0, upper=99, step_increment=1)
        self.context_spinner = gtk.SpinButton()
        self.context_spinner.set_adjustment(context_adj)
        self.context_spinner.set_numeric(True)

        spacer = gtk.SeparatorToolItem()
        spacer.set_draw(False) #don't draw a vertical line
        spacer.set_expand(True)

        filter_label = gtk.Label('Filter state:')

        for widget in [filter_label, search_entry, filter_button, praat_button, play_button, self.context_spinner, context_label]:
            tool_item = gtk.ToolItem()
            tool_item.add(widget)
            if widget == search_entry:
                tool_item.set_expand(True)
            toolbar.insert(tool_item, -1)
        
        toolbar.insert(spacer, -1)

        tool_item = gtk.ToolItem()
        tool_item.add(export_button)
        toolbar.insert(tool_item, -1)
        
        return toolbar

    #Takes a time string in one of two formats, and returns a float set to the absolute number of seconds it represents.
    #time_str must be either a string in any of the formats accepted by BackendUtils.time_str_to_float(), or a stringified float.
    # (e.g. ''00:00:3.45 or '3.45')
    def _get_abs_time(self, time_str):
        sec = None
        
        if ':' in time_str:
            sec = BackendUtils.time_str_to_float(time_str)
        else:
            sec = float(time_str)

        return sec

    def _get_sel_row_clip_info(self, col_headers, treeview):
        start = None
        end = None
        
        model, it = treeview.get_selection().get_selected()
        if it:
            #find start and end column indices (if not already found)
            if self.sound_col_fcns == None:
                start_index, end_index, dur_index, el_time_index = self._get_sound_col_indices(col_headers)

                if start_index > -1 and end_index > -1:
                    self.sound_col_fcns = (
                        lambda model, it: self._get_abs_time(model.get_value(it, start_index)),
                        lambda model, it: self._get_abs_time(model.get_value(it, end_index)),
                        )
                    
                elif dur_index > -1 and el_time_index > -1:
                    self.sound_col_fcns = (
                        lambda model, it: self._get_abs_time(float(model.get_value(it, el_time_index))),
                        lambda model, it: self._get_abs_time(float(model.get_value(it, el_time_index)) + float(model.get_value(it, dur_index))),
                        )

                else:
                    error_msg = 'The program was unable to derive the sound clip start and end times from the columns.\n'
                    error_msg += '\nColumn headers must include:\n'
                    error_msg += '-One start name: %s\n' % (' or '.join(['"%s"' % (name) for name in MainWindow.START_COL_NAMES]))
                    error_msg += '-One end name: %s\n' % (' or '.join(['"%s"' % (name) for name in MainWindow.END_COL_NAMES]))
                    error_msg += '\nOr alternatively:\n'
                    error_msg += '-One duration name: %s\n' % (' or '.join(['"%s"' % (name) for name in MainWindow.DUR_COL_NAMES]))
                    error_msg += '-One elapsed time name: %s\n' % (' or '.join(['"%s"' % (name) for name in MainWindow.EL_TIME_COL_NAMES]))

                    error_msg += '\nPlease make sure your input spreadsheet contains one of these pairs.'

                    UIUtils.show_message_dialog(error_msg)

            if self.sound_col_fcns != None and self.wav_filename:
                try:
                    start = float( self.sound_col_fcns[0](treeview.get_model(), it) )
                    end = float( self.sound_col_fcns[1](treeview.get_model(), it) )
                except ValueError as err:
                    UIUtils.show_message_dialog('The program was unable to determine start and end times for this row.')
                    start = None
                    end = None
                    print err

        else:
            UIUtils.show_no_sel_dialog()

        return start, end

    def _open_in_praat(self, headers, treeview):
        start, end = self._get_sel_row_clip_info(headers, treeview)
        
        if self.wav_filename and start != None and end != None:
            PraatInterop.open_praat()
            PraatInterop.send_commands(PraatInterop.get_open_clip_script(start, end, self.wav_filename))

    def _play_selected_row(self, col_headers, treeview):
        #this populates self.wav_parser and self.wav_filename, if the file exists
        start, end = self._get_sel_row_clip_info(col_headers, treeview)
        
        if self.wav_parser and start != None and end != None:
            start = max(0, start - self.context_spinner.get_value())
            end = min(self.wav_parser.get_sound_len(), end + self.context_spinner.get_value())
            self.wav_parser.play_clip(start, end)

        #return the focus to the currenly selected row in the treeview, so the user can immediately press the down arrow to move on to the next row
        treeview.grab_focus()

    def _find_col(self, header_dict, key_list):
        index = -1
        i = 0
        
        while index < 0 and i < len(key_list):
            if key_list[i] in header_dict:
                index = header_dict[ key_list[i] ]
            i += 1

        return index

    def _get_sound_col_indices(self, headers):
        header_dict = dict( zip(headers, range(len(headers))) )
        
        start = self._find_col(header_dict, MainWindow.START_COL_NAMES)
        end = self._find_col(header_dict, MainWindow.END_COL_NAMES)
        dur = self._find_col(header_dict, MainWindow.DUR_COL_NAMES)
        el_time = self._find_col(header_dict, MainWindow.EL_TIME_COL_NAMES)

        return (start, end, dur, el_time)

    def _export(self, treeview, col_headers, db):
        write_filename, open_now = UIUtils.save_file(filters=[UIUtils.CSV_FILE_FILTER, UIUtils.ALL_FILE_FILTER], open_now_opt=True)
        if write_filename: #if they didn't click cancel
            #lag_time_cutoff = float( UIUtils.show_entry_dialog(None, 'Lag time cutoff for counts: ', default_text='2', validate_regex=r'^-?\d+(\.\d+)?$', invalid_msg='Please enter a number.') )
            lag_time_cutoff = 2.0

	if write_filename and lag_time_cutoff != None: #if user did not click cancel (note: lag time cutoff could be 0)
            if not write_filename.lower().endswith('.csv'):
                write_filename += '.csv'

            try:
                csv_file = open(write_filename, 'wb')
                writer = csv.writer(csv_file, quoting=csv.QUOTE_ALL)

                cols = treeview.get_columns()
                visible_col_indices = filter(lambda i: cols[i].get_visible(), range(len(cols)))

                filtered_headers = [col_headers[i] for i in visible_col_indices]
                writer.writerow(filtered_headers)

                progress_dialog = ProgressDialog(title='Exporting to file', phases=['Exporting...'])
                progress_dialog.show()

                num_rows = len(treeview.get_model())
                row_index = 1 #this is awkward, but there is no way to pull all the rows out of the model in one shot, so we have to use a non-indexed for loop and manually track the index
                for row in treeview.get_model():
                    filtered_row = [row[i] for i in visible_col_indices]
                    writer.writerow(filtered_row)

                    progress_dialog.set_fraction(float(row_index) / float(num_rows))
                    row_index += 1

                # export_stats = self._get_export_stats(lag_time_cutoff, db, col_headers)
                # if export_stats:
                #     for row in export_stats:
                #         writer.writerow(row)

                progress_dialog.ensure_finish()
                csv_file.close()

                if open_now:
                    subprocess.Popen(['%s' % DBConstants.SETTINGS.SPREADSHEET_PATH, write_filename])
                else:
                    UIUtils.show_message_dialog('Data exported successfully.')
                    

                #UIUtils.show_message_dialog('Data export completed.')
            except Exception as err:
                UIUtils.show_message_dialog('Unable to export data - please make sure the destination file is not already open in another program.')
                raise err

    def _get_export_stats(self, lag_time_cutoff, db, col_headers):
        stats = []
        
        cols = {
            'Speaker': None,
            'Sentence Type': None,
            'Lag Time': None,
            'Marked': None,
            }
        
        for i in range(1, len(col_headers)):
            if col_headers[i] in cols:
                cols[col_headers[i]] = '%s%d' % (CSVDatabase.COL_PREFIX, i - 1)

        have_all_cols = reduce(lambda accum, key: cols[key] != None and accum, cols, True)

        if have_all_cols:
            #Count of Marked MQ -> B
            #"SELECT count(d1.id) FROM data d1, data d2 WHERE d1.id = d2.id - 1 AND d1.sentence_type='Q' AND d2.speaker='B' AND d1.marked=1 AND d1.lag_time < cutoff"
            where_cond = "d1.id = d2.id - 1 AND d1.%s = 'M' AND d1.%s = 'Q' AND d2.%s = 'B' AND d1.%s = 1 AND d1.%s <= ?" % (cols['Speaker'], cols['Sentence Type'], cols['Speaker'], cols['Marked'], cols['Lag Time'])
            rows = db.select(
                '%s d1, %s d2' % (CSVDatabase.TABLE_NAME, CSVDatabase.TABLE_NAME),
                ['count(d1.id)'],
                where_cond=where_cond,
                params=[lag_time_cutoff],
                )
            if rows:
                stats.append( ['Count of "Marked MQ -> B": %d' % (int(rows[0][0]))] )

            #Avg lag time on mother utterance for (MQ or MD) -> B
            where_cond = "d1.id = d2.id - 1 AND d1.%s= 'M' AND (d1.%s = 'Q' OR d1.%s = 'D') AND d2.%s = 'B' AND d1.%s <= ?" % (cols['Speaker'], cols['Sentence Type'], cols['Sentence Type'], cols['Speaker'], cols['Lag Time'])
            rows = db.select(
                '%s d1, %s d2' % (CSVDatabase.TABLE_NAME, CSVDatabase.TABLE_NAME),
                ['avg(d1.%s)' % (cols['Lag Time'])],
                where_cond=where_cond,
                params=[lag_time_cutoff],
                )
            if rows:
                val = str(None)
                if len(rows[0]) and rows[0][0] != None:
                    val = '%0.3f' % (float(rows[0][0]))
                stats.append( ['Avg Lag Time for "(MQ or MD) -> B": %s' % (val)] )

            #Avg of lag time (first in pair) on mother utterance for MQ -> MQ
            where_cond = "d1.id = d2.id - 1 AND d1.%s= 'M' AND d1.%s = 'Q' AND d2.%s = 'M' AND d2.%s = 'Q' AND d1.%s <= ?" % (cols['Speaker'], cols['Sentence Type'], cols['Speaker'], cols['Sentence Type'], cols['Lag Time'])
            rows = db.select(
                '%s d1, %s d2' % (CSVDatabase.TABLE_NAME, CSVDatabase.TABLE_NAME),
                ['avg(d1.%s)' % (cols['Lag Time'])],
                where_cond=where_cond,
                params=[lag_time_cutoff],
                )
            if rows:
                val = str(None)
                if len(rows[0]) and rows[0][0] != None:
                    val = '%0.3f' % (float(rows[0][0]))
                stats.append( ['Avg Lag Time for "MQ -> MQ": %s' % (val)] )

            #Avg of lag time (first in pair) on mother utterance for MD -> MD
            where_cond = "d1.id = d2.id - 1 AND d1.%s= 'M' AND d1.%s = 'D' AND d2.%s = 'M' AND d2.%s = 'D' AND d1.%s <= ?" % (cols['Speaker'], cols['Sentence Type'], cols['Speaker'], cols['Sentence Type'], cols['Lag Time'])
            rows = db.select(
                '%s d1, %s d2' % (CSVDatabase.TABLE_NAME, CSVDatabase.TABLE_NAME),
                ['avg(d1.%s)' % (cols['Lag Time'])],
                where_cond=where_cond,
                params=[lag_time_cutoff],
                )
            if rows:
                val = str(None)
                if len(rows[0]) and rows[0][0] != None:
                    val = '%0.3f' % (float(rows[0][0]))
                stats.append( ['Avg Lag Time for "MD -> MD": %s' % (val)] )

            #Avg of lag time (first in pair) on baby utterance for B -> (MQ or MD)
            where_cond = "d1.id = d2.id - 1 AND d1.%s = 'B' AND d2.%s = 'M' AND d1.%s <= ?" % (cols['Speaker'], cols['Speaker'], cols['Lag Time'])
            rows = db.select(
                '%s d1, %s d2' % (CSVDatabase.TABLE_NAME, CSVDatabase.TABLE_NAME),
                ['avg(d1.%s)' % (cols['Lag Time'])],
                where_cond=where_cond,
                params=[lag_time_cutoff],
                )
            if rows:
                val = str(None)
                if len(rows[0]) and rows[0][0] != None:
                    val = '%0.3f' % (float(rows[0][0]))
                stats.append( ['Avg Lag Time for "B -> (MQ or MD)": %s' % (val)] )

            if stats:
                stats.insert(0, ['File Stats (lag time <= %0.3f)' % (lag_time_cutoff)])

        return stats
            
Example #6
0
 def _play_clip(self, start, end):
     wav_parser = WavParser(self.test2.wav_filename)
     wav_parser.play_clip(start, end)
     wav_parser.close()
Example #7
0
class VerificationWindow():
    ERROR_STATES = Enum(['NONE', 'WARNING', 'ERROR'])

    def __init__(self, filename, progress_dialog):
        self.logger = logging.getLogger(__name__)
        self.window = gtk.Window(gtk.WindowType.TOPLEVEL)
        self.window.set_title('Transcription Verifier')
        self.window.connect('destroy', lambda x: self.window.destroy())
        self.window.set_border_width(10)
        self.window.set_default_size(580, 500)

        self.trs_parser = TRSParser(filename)
        self.trs_parser.parse(
            progress_update_fcn=progress_dialog.set_fraction,
            progress_next_phase_fcn=progress_dialog.next_phase,
            remove_bad_trans_codes=False)
        self.wav_parser = None

        progress_dialog.next_phase()
        self.filter_errors = True
        self.toolbar = self.build_toolbar()
        self.treeview = self.build_treeview(progress_dialog.set_fraction)
        self.treeview.expand_all()

        scrolled_win = gtk.ScrolledWindow()
        scrolled_win.set_policy(gtk.PolicyType.AUTOMATIC,
                                gtk.PolicyType.AUTOMATIC)
        scrolled_win.add(self.treeview)

        vbox = gtk.VBox(False, 2)
        vbox.pack_start(self.toolbar, False, False, 0)
        vbox.pack_start(scrolled_win, True, True, 0)

        self.window.add(vbox)

        self.window.show_all()

    def build_toolbar(self):
        toolbar = gtk.Toolbar()
        toolbar.set_orientation(gtk.Orientation.HORIZONTAL)

        filter_errors_button = gtk.ToggleToolButton()
        filter_errors_button.set_active(
            True
        )  #set this before the connecting the clicked handler so it doesn't cause trouble
        filter_errors_button.connect(
            'toggled', lambda w: self.toggle_filter_errors(w.get_active()))
        filter_errors_icon = gtk.Image()
        filter_errors_icon.set_from_file(
            UIUtils.get_icon_path(UIUtils.BUTTON_ICONS.FLAG))
        filter_errors_button.set_label('Show Errors Only')
        filter_errors_button.set_icon_widget(filter_errors_icon)

        expand_button = gtk.ToolButton()
        expand_icon = gtk.Image()
        expand_icon.set_from_file(
            UIUtils.get_icon_path(UIUtils.BUTTON_ICONS.EXPAND))
        expand_button.set_label('Expand All')
        expand_button.set_icon_widget(expand_icon)
        expand_button.connect('clicked', lambda w: self.treeview.expand_all())

        collapse_button = gtk.ToolButton()
        collapse_icon = gtk.Image()
        collapse_icon.set_from_file(
            UIUtils.get_icon_path(UIUtils.BUTTON_ICONS.COLLAPSE))
        collapse_button.set_label('Collapse All')
        collapse_button.set_icon_widget(collapse_icon)
        collapse_button.connect('clicked',
                                lambda w: self.treeview.collapse_all())

        rescan_button = gtk.ToolButton()
        rescan_icon = gtk.Image()
        rescan_icon.set_from_file(
            UIUtils.get_icon_path(UIUtils.BUTTON_ICONS.REFRESH))
        rescan_button.set_label('Rescan File')
        rescan_button.set_icon_widget(rescan_icon)
        rescan_button.connect('clicked', lambda w: self._rescan_file())

        play_seg_button = gtk.ToolButton()
        play_icon = gtk.Image()
        play_icon.set_from_file(
            UIUtils.get_icon_path(UIUtils.BUTTON_ICONS.PLAY))
        play_seg_button.set_label('Play Seg')
        play_seg_button.set_icon_widget(play_icon)
        play_seg_button.connect('clicked', lambda w: self.play_selected_seg())

        close_button = gtk.ToolButton()
        close_icon = gtk.Image()
        close_icon.set_from_file(
            UIUtils.get_icon_path(UIUtils.BUTTON_ICONS.CLOSE))
        close_button.set_label('Close')
        close_button.set_icon_widget(close_icon)
        close_button.connect('clicked', lambda w: self.window.destroy())

        exit_button = gtk.ToolButton()
        exit_icon = gtk.Image()
        exit_icon.set_from_file(
            UIUtils.get_icon_path(UIUtils.BUTTON_ICONS.EXIT))
        exit_button.set_label('Exit')
        exit_button.set_icon_widget(exit_icon)
        exit_button.connect('clicked', lambda w: gtk.main_quit())

        toolbar.insert(filter_errors_button, -1)
        toolbar.insert(expand_button, -1)
        toolbar.insert(collapse_button, -1)
        toolbar.insert(gtk.SeparatorToolItem(), -1)
        toolbar.insert(play_seg_button, -1)
        toolbar.insert(rescan_button, -1)
        toolbar.insert(gtk.SeparatorToolItem(), -1)
        toolbar.insert(close_button, -1)
        toolbar.insert(exit_button, -1)

        return toolbar

    def _rescan_file(self):
        self.window.set_sensitive(False)

        progress_dialog = ProgressDialog(
            'Processing File...',
            ['Parsing trs file...', 'Validating data...', 'Building UI...'])
        progress_dialog.show()

        #this causes the parser to invalidate all cache, re-open and re-parse the file
        self.trs_parser.re_parse(
            progress_update_fcn=progress_dialog.set_fraction,
            progress_next_phase_fcn=progress_dialog.next_phase)

        #build a new treeview model based on the new data
        progress_dialog.next_phase()
        filter_model = self._build_tree_store(progress_dialog.set_fraction)
        self.treeview.set_model(filter_model)

        #Presumably the most common cause for rescanning is to check if errors have been fixed.
        #If the error filter is on, automatically expand all rows to show any remaining errors.
        if self.filter_errors:
            self.treeview.expand_all()

        self.window.set_sensitive(True)

    def _build_tree_store(self, progress_update_fcn):
        #segment/utter id, description, error_state (0 = none, 1 = warning, 2 = error)
        tree_store = gtk.TreeStore(gobject.TYPE_INT, gobject.TYPE_STRING,
                                   gobject.TYPE_INT)

        #note: these may be errors or warnings
        cur_utter = 0
        for seg in self.trs_parser.parse():
            seg_speakers = ''
            if seg.speakers:
                for i in range(len(seg.speakers)):
                    seg_speakers += seg.speakers[i].speaker_codeinfo.get_code()
                    if i < len(seg.speakers) - 1:
                        seg_speakers += ' + '
            else:
                seg_speakers = ' - '

            seg_iter = tree_store.append(None, [
                seg.num,
                '%s [%s - %s]' %
                (seg_speakers, BackendUtils.get_time_str(
                    seg.start), BackendUtils.get_time_str(seg.end)),
                VerificationWindow.ERROR_STATES.NONE
            ])

            for utter in seg.utters:
                speaker_cd = '?'  #question mark indicates an error occured - if we have utter.speaker, we should have an utter code. Errors occur if the utter code isn't in the DB lookup table (which means that utter.speaker != None, but utter.speaker.speaker_codeinfo == None. This is the condition that falls through the if-else blocks below).
                if utter.speaker:
                    if utter.speaker.speaker_codeinfo:
                        speaker_cd = utter.speaker.speaker_codeinfo.get_code()
                else:
                    speaker_cd = ' - '

                desc_str = '%s [%s - %s]' % (
                    speaker_cd, BackendUtils.get_time_str(
                        utter.start), BackendUtils.get_time_str(utter.end))
                if utter.lena_notes:
                    desc_str += ' %s' % (utter.lena_notes)
                if utter.trans_phrase:
                    desc_str += ' %s' % (utter.trans_phrase)
                if utter.lena_codes:
                    desc_str += ' |%s|' % ('|'.join(utter.lena_codes))
                if utter.trans_codes:
                    if not utter.lena_codes:
                        desc_str += ' |'
                    desc_str += '%s|' % ('|'.join(utter.trans_codes))

                utter_iter = tree_store.append(
                    seg_iter,
                    [utter.id, desc_str, VerificationWindow.ERROR_STATES.NONE])

                cur_utter += 1
                progress_update_fcn(
                    float(cur_utter) / float(self.trs_parser.total_utters))

                error_list = self.trs_parser.error_collector.get_errors_by_utter(
                    utter)
                for error in error_list:
                    error_type = VerificationWindow.ERROR_STATES.ERROR
                    if isinstance(error, ParserWarning):
                        error_type = VerificationWindow.ERROR_STATES.WARNING

                    error_iter = tree_store.append(
                        utter_iter, [-1, '%s' % (error.msg), error_type])

                    parent_it = utter_iter
                    while parent_it:
                        parent_error_type = tree_store.get_value(parent_it, 2)
                        if parent_error_type < error_type:
                            tree_store.set_value(parent_it, 2, error_type)

                        parent_it = tree_store.iter_parent(parent_it)

        filter_model = tree_store.filter_new()
        filter_model.set_visible_func(self.filter)

        return filter_model

    def build_treeview(self, progress_update_fcn):
        filter_model = self._build_tree_store(progress_update_fcn)
        treeview = gtk.TreeView(filter_model)

        col = gtk.TreeViewColumn('ID', gtk.CellRendererText(), text=0)
        col.set_visible(False)
        treeview.append_column(col)

        renderer = gtk.CellRendererText()
        col = gtk.TreeViewColumn('Description', renderer, text=1)
        col.set_cell_data_func(renderer, self.cell_render_fcn)
        treeview.append_column(col)

        col = gtk.TreeViewColumn('Error State', gtk.CellRendererText(), text=2)
        col.set_visible(False)
        treeview.append_column(col)

        return treeview

    def cell_render_fcn(self, col, cell_renderer, model, it, user_data=None):
        error_state = model.get_value(it, 2)
        if error_state == VerificationWindow.ERROR_STATES.WARNING:
            cell_renderer.set_property('foreground', 'orange')
        elif error_state == VerificationWindow.ERROR_STATES.ERROR:
            cell_renderer.set_property('foreground', 'red')
        else:
            cell_renderer.set_property('foreground', 'black')

        return

    #returns true if row pointed to by 'it' should be visible
    def filter(self, model, it, user_data):
        result = True
        if self.filter_errors:
            result = model.get_value(it,
                                     2) > VerificationWindow.ERROR_STATES.NONE

        return result

    def toggle_filter_errors(self, filter_errors):
        self.filter_errors = not self.filter_errors
        self.treeview.get_model().refilter()

    def play_selected_seg(self):
        (model, it) = self.treeview.get_selection().get_selected()
        if it:
            #if they've selected an error row, find the top level parent (the segment) and use it instead
            parent = model.iter_parent(it)
            while parent:
                it = parent
                parent = model.iter_parent(it)

            seg_num = model.get_value(it, 0) if it else None
            seg = self.trs_parser.parse()[seg_num]

            if not self.wav_parser:
                dialog = gtk.FileChooserDialog(
                    title='Select WAV File',
                    action=gtk.FileChooserAction.OPEN,
                    buttons=(gtk.STOCK_CANCEL, gtk.ResponseType.CANCEL,
                             gtk.STOCK_OPEN, gtk.ResponseType.OK))
                dialog.set_default_response(gtk.ResponseType.OK)

                for filter_opt in (('wav Files', '*.wav'), ('All Files', '*')):
                    file_filter = gtk.FileFilter()
                    file_filter.set_name(filter_opt[0])
                    file_filter.add_pattern(filter_opt[1])
                    dialog.add_filter(file_filter)

                response = dialog.run()
                if response == gtk.ResponseType.OK:
                    filename = dialog.get_filename()
                    self.wav_parser = WavParser(filename)

                dialog.destroy()

            if self.wav_parser:
                self.wav_parser.play_seg(seg)

            else:
                UIUtils.show_no_sel_dialog()
        else:
            UIUtils.show_no_sel_dialog()
Example #8
0
class TestWindow():
    def __init__(self, check):
        self.logger = logging.getLogger(__name__)

        self.check = check

        self.wav_parser = WavParser(self.check.wav_filename)

        self.window = gtk.Window(gtk.WindowType.TOPLEVEL)
        self.window.set_title('Testing')
        self.window.connect(
            'destroy',
            lambda w: self._exit())  #will save current input and exit
        self.window.set_border_width(10)
        self.window.set_default_size(350, 200)

        self.wo_form = Form()
        self.wo_form.handler_man = HandlerManager()
        self.w_form = Form()
        self.w_form.handler_man = HandlerManager()

        self.button_form = Form()
        self.button_form.handler_man = HandlerManager()

        self.progress_bar = self._build_progress_bar()
        self.w_context_frame = self._build_w_context_frame()
        button_box = self._build_button_box()

        self.wo_context_frame = self._build_wo_context_frame()
        self.wo_context_checkbox = self._build_wo_context_checkbox(
            self.wo_context_frame)

        vbox = gtk.VBox()
        vbox.pack_start(self.progress_bar, True, True, 0)
        vbox.pack_start(self.wo_context_checkbox, True, True, 0)
        vbox.pack_start(self.wo_context_frame, True, True, 0)
        vbox.pack_start(self.w_context_frame, True, True, 0)
        vbox.pack_end(button_box, True, True, 0)

        self.window.add(vbox)
        self.window.show_all()

        self._update_progress_bar()
        self._toggle_wo_context_frame(self.wo_context_checkbox.get_active())
        self._set_ui_to_cur_test()

    def _build_progress_bar(self):
        progress_bar = gtk.ProgressBar()
        progress_bar.set_orientation(gtk.Orientation.HORIZONTAL)

        return progress_bar

    def _set_ui_to_cur_test(self):
        cur_test = self.check.tests[self.check.test_index]

        cat_index = 0
        if cur_test.category_input != None:
            cat_index = cur_test.category_input - DBConstants.COMBO_OPTIONS[
                DBConstants.COMBO_GROUPS.RELIABILITY_CATEGORIES].EMPTY
        self.w_form.type_combo.set_active(cat_index)

        self.w_form.uncertain_checkbox.set_active(cur_test.is_uncertain
                                                  or False)

        self.w_form.context_pad_spinner.set_value(cur_test.context_padding)

        self.w_form.syllables_spinner.set_value(
            cur_test.
            syllables_w_context if cur_test.syllables_w_context != None else 0)

        #these boundaries are displayed without context, but opening in praat or listening will play with context
        seg_start, seg_end = self._get_bounds()
        self.w_form.user_start_entry.set_text(
            str(cur_test.seg.user_adj_start
                ) if cur_test.seg.user_adj_start != None else str(seg_start))
        self.w_form.user_end_entry.set_text(
            str(cur_test.seg.user_adj_end
                ) if cur_test.seg.user_adj_end != None else str(seg_end))

        self.wo_form.syllables_spinner.set_value(
            cur_test.syllables_wo_context
            if cur_test.syllables_wo_context != None else 0)
        self.wo_context_checkbox.set_active(
            cur_test.syllables_wo_context != None
        )  #this will toggle the visibility of the frame because of the handler attached to it

    def _build_w_context_frame(self):
        cur_test = self.check.tests[self.check.test_index]
        frame = gtk.Frame(label='With Context')

        #table = gtk.Table(6, 4, False)
        grid = gtk.Grid()

        play_button = UIUtils.create_button('', UIUtils.BUTTON_ICONS.PLAY,
                                            UIUtils.BUTTON_ICON_SIZES.PX32)
        #table.attach(play_button, 0, 1, 0, 1, gtk.EXPAND, gtk.EXPAND)
        grid.attach(play_button, 0, 0, 1, 1)

        type_label = gtk.Label('Seg Category:')
        #table.attach(type_label, 1, 2, 0, 1, gtk.EXPAND, gtk.EXPAND)
        grid.attach(type_label, 1, 0, 1, 1)

        self.w_form.type_combo = UIUtils.build_options_combo(
            DBConstants.COMBO_GROUPS.RELIABILITY_CATEGORIES)
        #table.attach(self.w_form.type_combo, 2, 3, 0, 1, gtk.EXPAND, gtk.EXPAND)
        grid.attach(self.w_form.type_combo, 2, 0, 1, 1)

        uncertain_label = gtk.Label('Other/Uncertain:')
        self.w_form.uncertain_checkbox = gtk.CheckButton()
        #table.attach(uncertain_label, 1, 2, 1, 2, gtk.EXPAND, gtk.EXPAND)
        grid.attach(uncertain_label, 1, 1, 1, 1)
        #table.attach(self.w_form.uncertain_checkbox, 2, 3, 1, 2, gtk.EXPAND|gtk.FILL, gtk.EXPAND|gtk.FILL)
        grid.attach(self.w_form.uncertain_checkbox, 2, 1, 1, 1)

        padding_label = gtk.Label('Padding:')
        context_pad_adj = gtk.Adjustment(value=0,
                                         lower=1,
                                         upper=1000,
                                         step_incr=1,
                                         page_incr=5)
        self.w_form.context_pad_spinner = gtk.SpinButton(context_pad_adj)
        #table.attach(padding_label, 1, 2, 2, 3, gtk.EXPAND, gtk.EXPAND)
        grid.attach(padding_label, 1, 2, 1, 1)
        #table.attach(self.w_form.context_pad_spinner, 2, 3, 2, 3, gtk.EXPAND, gtk.EXPAND)
        grid.attach(self.w_form.context_pad_spinner, 2, 2, 1, 1)

        self.w_form.handler_man.add_handler(
            play_button, 'clicked', lambda w: self.play_seg(
                int(self.w_form.context_pad_spinner.get_value())))

        syllables_label = gtk.Label('Syllables:')
        syllables_adj = gtk.Adjustment(value=0,
                                       lower=0,
                                       upper=1000,
                                       step_incr=1,
                                       page_incr=5)
        self.w_form.syllables_spinner = gtk.SpinButton(syllables_adj)
        #table.attach(syllables_label, 1, 2, 3, 4, gtk.EXPAND, gtk.EXPAND)
        grid.attach(syllables_label, 1, 3, 1, 1)
        #table.attach(self.w_form.syllables_spinner, 2, 3, 3, 4, gtk.EXPAND, gtk.EXPAND)
        grid.attach(self.w_form.syllables_spinner, 2, 3, 1, 1)

        user_start_label = gtk.Label('User Start:')
        self.w_form.user_start_entry = gtk.Entry()
        self.w_form.user_start_entry.set_width_chars(10)
        #table.attach(user_start_label, 1, 2, 4, 5, gtk.EXPAND, gtk.EXPAND)
        grid.attach(user_start_label, 1, 4, 1, 1)
        #table.attach(self.w_form.user_start_entry, 2, 3, 4, 5, gtk.EXPAND, gtk.EXPAND)
        grid.attach(self.w_form.user_start_entry, 2, 5, 1, 1)

        user_end_label = gtk.Label('User End:')
        self.w_form.user_end_entry = gtk.Entry()
        self.w_form.user_end_entry.set_width_chars(10)
        #table.attach(user_end_label, 1, 2, 5, 6, gtk.EXPAND, gtk.EXPAND)
        grid.attach(user_end_label, 1, 5, 1, 1)
        #table.attach(self.w_form.user_end_entry, 2, 3, 5, 6, gtk.EXPAND, gtk.EXPAND)
        grid.attach(self.w_form.user_end_entry, 2, 5, 1, 1)

        open_praat_button = gtk.Button('Open Praat')
        #table.attach(open_praat_button, 3, 4, 4, 5, gtk.EXPAND, gtk.EXPAND)
        grid.attach(open_praat_button, 3, 4, 1, 1)
        self.w_form.handler_man.add_handler(open_praat_button, 'clicked',
                                            lambda w: self._open_praat())

        close_praat_button = gtk.Button('Close Praat')
        #table.attach(close_praat_button, 3, 4, 5, 6, gtk.EXPAND, gtk.EXPAND)
        grid.attach(close_praat_button, 3, 5, 1, 1)
        self.w_form.handler_man.add_handler(close_praat_button, 'clicked',
                                            lambda w: self._close_praat())

        frame.add(grid)

        return frame

    def _build_wo_context_frame(self):
        cur_test = self.check.tests[self.check.test_index]
        frame = gtk.Frame(label='Without Context')

        #table = gtk.Table(1, 3, False)
        grid = gtk.Grid()

        self.wo_form.play_button = UIUtils.create_button(
            '', UIUtils.BUTTON_ICONS.PLAY, UIUtils.BUTTON_ICON_SIZES.PX32)
        self.wo_form.handler_man.add_handler(self.wo_form.play_button,
                                             'clicked',
                                             lambda w: self.play_seg(0))
        #table.attach(self.wo_form.play_button, 0, 1, 0, 1, gtk.EXPAND, gtk.EXPAND)
        grid.attach(self.wo_form.play_button, 0, 0, 1, 1)

        syllables_label = gtk.Label('Syllables:')
        syllables_adj = gtk.Adjustment(value=0,
                                       lower=0,
                                       upper=1000,
                                       step_incr=1,
                                       page_incr=5)
        self.wo_form.syllables_spinner = gtk.SpinButton(syllables_adj)
        #table.attach(syllables_label, 1, 2, 0, 1, gtk.EXPAND, gtk.EXPAND)
        grid.attach(syllables_label, 1, 0, 1, 1)
        #table.attach(self.wo_form.syllables_spinner, 2, 3, 0, 1, gtk.EXPAND, gtk.EXPAND)
        grid.attach(self.wo_form.syllables_spinner, 2, 0, 1, 1)

        frame.add(grid)

        return frame

    def _toggle_wo_context_frame(self, visible):
        if visible:
            self.wo_context_frame.show()
        else:
            self.wo_context_frame.hide()
            self.check.tests[self.check.test_index].syllables_wo_context = None
            self.wo_form.syllables_spinner.get_adjustment().set_value(1)

    def _build_wo_context_checkbox(self, wo_context_frame):
        cur_test = self.check.tests[self.check.test_index]
        checkbox = gtk.CheckButton(label='Test Without Context')
        checkbox.connect(
            'toggled', lambda w: self._toggle_wo_context_frame(w.get_active()))

        checkbox.set_active(cur_test.syllables_wo_context != None)

        return checkbox

    def _build_button_box(self):
        box = gtk.HButtonBox()
        box.set_layout(gtk.ButtonBoxStyle.EDGE)

        self.button_form.back_button = gtk.Button(stock=gtk.STOCK_GO_BACK)
        self.button_form.handler_man.add_handler(self.button_form.back_button,
                                                 'clicked',
                                                 lambda w: self._back())

        self.button_form.save_button = UIUtils.create_button(
            'Save & Exit', UIUtils.BUTTON_ICONS.SAVE,
            UIUtils.BUTTON_ICON_SIZES.PX32)
        self.button_form.handler_man.add_handler(self.button_form.save_button,
                                                 'clicked',
                                                 lambda w: self._exit())

        self.button_form.forward_button = gtk.Button(
            stock=gtk.STOCK_GO_FORWARD)
        self.button_form.handler_man.add_handler(
            self.button_form.forward_button, 'clicked',
            lambda w: self._forward())

        self._update_step_buttons()

        box.pack_start(self.button_form.back_button, False, False, 0)
        box.pack_start(self.button_form.save_button, False, False, 0)
        box.pack_end(self.button_form.forward_button, False, False, 0)

        return box

    def _update_progress_bar(self):
        if self.check.num_segs == 1:  #avoid dividing by zero
            self.progress_bar.set_fraction(1.0)
        else:
            #print self.check.test_index
            self.progress_bar.set_fraction(
                float(self.check.test_index) / float(self.check.num_segs - 1))

        self.progress_bar.set_text(
            'Segment %d of %d' %
            (self.check.test_index + 1, self.check.num_segs))

    def _set_step_button(self, button, stock, label_text, clicked_handler):
        img = gtk.Image()
        img.set_from_stock(stock, gtk.ICON_SIZE_BUTTON)
        button.set_label(label_text)
        button.set_image(img)

        self.button_form.handler_man.remove_handlers(button, ['clicked'])
        self.button_form.handler_man.add_handler(button, 'clicked',
                                                 clicked_handler)

    def _update_step_buttons(self):
        finish_text = 'Finish'
        forward_text = 'Forward'

        if self.check.test_index == self.check.num_segs - 1:
            self._set_step_button(self.button_form.forward_button,
                                  gtk.STOCK_OK, finish_text,
                                  lambda w: self._finish())

        elif self.button_form.forward_button.get_label() == finish_text:
            self._set_step_button(self.button_form.forward_button,
                                  gtk.STOCK_GO_FORWARD, forward_text,
                                  lambda w: self._forward())

        self.button_form.back_button.set_sensitive(self.check.test_index > 0)

    def _exit(self, save=True):
        if save:
            self.save_input(mark_last_run=True)

        self.wav_parser.close()
        self.window.destroy()

    def _finish(self):
        if self._validate_cur_test():
            self.save_input(mark_last_run=True, mark_as_completed=True)

            filename, check_results = UIUtils.save_file(
                filters=[UIUtils.CSV_FILE_FILTER],
                open_now_opt=True,
                save_last_location=True)

            if filename:
                exporter = ReliabilityExporter(self.check, filename)
                if exporter.export():
                    if check_results:
                        subprocess.Popen([
                            '%s' % DBConstants.SETTINGS.SPREADSHEET_PATH,
                            filename
                        ])
                    else:
                        UIUtils.show_message_dialog(
                            'Results exported successfully.')

                    self._exit(False)  #we have already saved above

                else:
                    UIUtils.show_message_dialog(
                        'An error occurred while exporting the results. These results are still saved in the database, and can be exported at a later time, pending the correction of this problem. Please bother the programmer until this happens.'
                    )

    def _validate_cur_test(self):
        #validate w_context form
        w_context_valid = (
            self.w_form.type_combo.get_active() != 0 and
            int(self.w_form.syllables_spinner.get_adjustment().get_value()) > 0
            and BackendUtils.is_float(self.w_form.user_start_entry.get_text())
            and BackendUtils.is_float(self.w_form.user_end_entry.get_text()))

        #print 'w_context_valid: %s' % str(w_context_valid)

        wo_context_valid = (not self.wo_context_checkbox.get_active() or int(
            self.wo_form.syllables_spinner.get_adjustment().get_value()) > 0)

        #print 'wo_context_valid: %s' % str(wo_context_valid)

        is_valid = w_context_valid and wo_context_valid

        if is_valid:
            #make sure user-boundaries have been updated
            #note: they are guarenteed to contain floats at this point because of the w_context_valid condition
            user_start = float(self.w_form.user_start_entry.get_text())
            user_end = float(self.w_form.user_end_entry.get_text())
            cur_test = self.check.tests[self.check.test_index]

            is_valid = (cur_test.seg.start != user_start
                        or cur_test.seg.end != user_end)
            if not is_valid:
                is_valid = UIUtils.show_confirm_dialog(
                    'Segment boundaries have not been adjusted. Continue anyway?'
                )

        else:
            UIUtils.show_message_dialog(
                'Please ensure that all of the inputs have a correct value.')

        return is_valid

    def _move(self, incr):
        self.save_input()
        self.check.test_index += incr
        self._update_progress_bar()
        self._update_step_buttons()
        self._set_ui_to_cur_test()

    def _forward(self):
        if self._validate_cur_test():
            self._move(1)

    def _back(self):
        self._move(-1)

    def _get_bounds(self, include_context=False):
        start_time = self.check.tests[self.check.test_index].seg.start
        end_time = self.check.tests[self.check.test_index].seg.end

        if include_context:
            context_len = self.w_form.context_pad_spinner.get_value_as_int(
            ) if include_context else 0
            start_time = max(start_time - context_len, 0)
            end_time = min(end_time + context_len,
                           self.wav_parser.get_sound_len())

        return start_time, end_time

    def _open_praat(self):
        start_time, end_time = self._get_bounds(include_context=True)
        PraatInterop.open_praat()
        PraatInterop.send_commands(
            PraatInterop.get_open_clip_script(start_time, end_time,
                                              self.check.wav_filename))

    def _close_praat(self):
        socket = PraatInterop.create_serversocket()
        PraatInterop.send_commands(
            PraatInterop.get_sel_bounds_script(self.check.wav_filename))
        start_time, end_time = PraatInterop.socket_receive(socket)
        socket.close()
        PraatInterop.close_praat()

        if start_time != end_time:  #make sure something was selected
            #update the inputs in the UI
            start_time = str(round(float(start_time), 3))
            end_time = str(round(float(end_time), 3))

            self.w_form.user_start_entry.set_text(start_time)
            self.w_form.user_end_entry.set_text(end_time)

    def play_seg(self, context_len):
        self.wav_parser.play_seg(self.check.tests[self.check.test_index].seg,
                                 context_len)

    def save_input(self, mark_last_run=False, mark_as_completed=False):
        cur_test = self.check.tests[self.check.test_index]

        cur_test.category_input = self.w_form.type_combo.get_model()[
            self.w_form.type_combo.get_active()][1]
        cur_test.is_uncertain = self.w_form.uncertain_checkbox.get_active()
        cur_test.context_padding = int(
            self.w_form.context_pad_spinner.get_adjustment().get_value())
        cur_test.syllables_w_context = int(
            self.w_form.syllables_spinner.get_adjustment().get_value())
        cur_test.seg.user_adj_start = float(
            self.w_form.user_start_entry.get_text())
        cur_test.seg.user_adj_end = float(
            self.w_form.user_end_entry.get_text())

        cur_test.syllables_wo_context = None
        if self.wo_context_checkbox.get_active():
            cur_test.syllables_wo_context = int(
                self.wo_form.syllables_spinner.get_adjustment().get_value())

        db = BLLDatabase()
        cur_test.db_update_user_inputs(db)
        self.check.db_update_test_index(db)

        if mark_last_run:
            self.check.mark_last_run(db)

        if mark_as_completed:
            self.check.mark_as_completed(db)

        db.close()