def val_changed(self, val_str):
        # Backup form state
        old_ma_unit, old_table = self._save_ma_unit_and_table_state(
                                table = self.two_by_two_table,
                                ma_unit = self.ma_unit, 
                                use_old_value=False)
        old_prevalence = self.current_prevalence
        
        new_text = self._get_txt_from_val_str(val_str)
        
        no_errors, display_scale_val = self._text_box_value_is_between_bounds(val_str, new_text)
        if no_errors is False: # There are errors
            self.restore_ma_unit_and_table(old_ma_unit,old_table, old_prevalence)
            calc_fncs.block_signals(self.entry_widgets, True)
            if val_str == "est":
                self.effect_txt_box.setFocus()
            elif val_str == "lower":
                self.low_txt_box.setFocus()
            elif val_str == "upper":
                self.high_txt_box.setFocus()
            elif val_str == "prevalence":
                self.prevalence_txt_box.setFocus()
            calc_fncs.block_signals(self.entry_widgets, False)
            return
        
        # If we got to this point it means everything is ok so far        
        try:
            if display_scale_val not in EMPTY_VALS:
                display_scale_val = float(display_scale_val)
            else:
                display_scale_val = None
        except ValueError:
            # a number wasn't entered; ignore
            # should probably clear out the box here, too.
            print "fail."
            return None
        

        calc_scale_val = meta_py_r.diagnostic_convert_scale(display_scale_val,
                                        self.cur_effect, convert_to="calc.scale")
        
        if val_str == "est":
            self.ma_unit.set_effect(self.cur_effect, self.group_str, calc_scale_val)
        elif val_str == "lower":
            self.ma_unit.set_lower(self.cur_effect, self.group_str, calc_scale_val)
        elif val_str == "upper":
            self.ma_unit.set_upper(self.cur_effect, self.group_str, calc_scale_val)
        elif val_str == "prevalence":
            pass

        new_ma_unit, new_table = self._save_ma_unit_and_table_state(
                table = self.two_by_two_table, ma_unit = self.ma_unit,
                use_old_value=False)
        new_prevalence = self._get_prevalence_str()
        restore_old_f = lambda: self.restore_ma_unit_and_table(old_ma_unit, old_table, old_prevalence)
        restore_new_f = lambda: self.restore_ma_unit_and_table(new_ma_unit, new_table, new_prevalence)
        command = calc_fncs.CommandFieldChanged(restore_new_f=restore_new_f, restore_old_f=restore_old_f, parent=self)
        self.undoStack.push(command)
        
        self.current_prevalence = new_prevalence
    def clear_form(self):
        # For undo/redo
        old_ma_unit, old_table = self._save_ma_unit_and_table_state(
                                table = self.two_by_two_table,
                                ma_unit = self.ma_unit, 
                                use_old_value=False)
        old_prevalence = self._get_prevalence_str()
        
        keys = ["c11", "c12", "r1sum", "c21", "c22", "r2sum", "c1sum", "c2sum", "total"]
        blank_vals = dict( zip(keys, [""]*len(keys)) )

        self._set_vals(blank_vals)
        self._update_ma_unit()
        
        # clear out effects stuff
        for metric in DIAGNOSTIC_METRICS:
            self.ma_unit.set_effect_and_ci(metric, self.group_str, None, None, None, mult=self.mult)
            
        # clear line edits
        self.set_current_effect()
        self.prevalence_txt_box.blockSignals(True)
        self.prevalence_txt_box.setText("")
        self.prevalence_txt_box.blockSignals(False)

        calc_fncs.reset_table_item_flags(self.two_by_two_table)
        #self.enable_txt_box_input()
        
        new_ma_unit, new_table = self._save_ma_unit_and_table_state(
                        table = self.two_by_two_table, ma_unit = self.ma_unit,
                        use_old_value=False)
        new_prevalence = self._get_prevalence_str()
        restore_old_f = lambda: self.restore_ma_unit_and_table(old_ma_unit, old_table, old_prevalence)
        restore_new_f = lambda: self.restore_ma_unit_and_table(new_ma_unit, new_table, new_prevalence)
        command = calc_fncs.CommandFieldChanged(restore_new_f=restore_new_f, restore_old_f=restore_old_f, parent=self)
        self.undoStack.push(command)
    def clear_form(self):
        # For undo/redo
        old_ma_unit, old_table = self._save_ma_unit_and_table_state(
            table=self.raw_data_table,
            ma_unit=self.ma_unit,
            use_old_value=False)

        blank_vals = {
            "c11": "",
            "c12": "",
            "r1sum": "",
            "c21": "",
            "c22": "",
            "r2sum": "",
            "c1sum": "",
            "c2sum": "",
            "total": ""
        }

        self._set_vals(blank_vals)
        self._update_ma_unit()

        # clear out effects stuff
        for metric in BINARY_ONE_ARM_METRICS + BINARY_TWO_ARM_METRICS:
            if ((self.cur_effect in BINARY_TWO_ARM_METRICS
                 and metric in BINARY_TWO_ARM_METRICS)
                    or (self.cur_effect in BINARY_ONE_ARM_METRICS
                        and metric in BINARY_ONE_ARM_METRICS)):
                self.ma_unit.set_effect_and_ci(metric,
                                               self.group_str,
                                               None,
                                               None,
                                               None,
                                               mult=self.mult)
            else:
                # TODO: Do nothing for now..... treat the case where we have to switch group strings down the line
                pass

        # clear line edits
        self.set_current_effect()
        calc_fncs.reset_table_item_flags(self.raw_data_table)
        ####self.enable_txt_box_input()

        new_ma_unit, new_table = self._save_ma_unit_and_table_state(
            table=self.raw_data_table,
            ma_unit=self.ma_unit,
            use_old_value=False)
        restore_old_f = lambda: self.restore_ma_unit_and_table(
            old_ma_unit, old_table)
        restore_new_f = lambda: self.restore_ma_unit_and_table(
            new_ma_unit, new_table)
        command = calc_fncs.CommandFieldChanged(restore_new_f=restore_new_f,
                                                restore_old_f=restore_old_f,
                                                parent=self)
        self.undoStack.push(command)
    def cell_changed(self, row, col):
        # tries to make sense of user input before passing
        # on to the R routine

        old_ma_unit, old_table = self._save_ma_unit_and_table_state(
            table=self.raw_data_table,
            ma_unit=self.ma_unit,
            old_value=self.current_item_data,
            row=row,
            col=col,
            use_old_value=True)

        try:
            # Test if entered data is valid (a number)
            warning_msg = self._cell_data_not_valid(
                self.raw_data_table.item(row, col).text())
            if warning_msg:
                raise Exception("Invalid Cell Data")

            self._update_data_table(
            )  # calculate rest of table (provisionally) based on new entry
            warning_msg = self.check_table_consistency.run()
            if warning_msg:
                raise Exception("Table no longer consistent.")
        except Exception as e:
            msg = e.args[0]
            QMessageBox.warning(self.parent(), "whoops", msg)  # popup warning
            self.restore_ma_unit_and_table(
                old_ma_unit,
                old_table)  # brings things back to the way they were
            return  # and leave

        self._update_ma_unit()  # table widget --> ma_unit
        self.try_to_update_cur_outcome(
        )  # update metric in ma_unit and in table

        new_ma_unit, new_table = self._save_ma_unit_and_table_state(
            table=self.raw_data_table,
            ma_unit=self.ma_unit,
            row=row,
            col=col,
            use_old_value=False)
        #restore_f = self.restore_ma_unit_and_table
        #command = calc_fncs.CommandFieldChanged(old_ma_unit, new_ma_unit, old_table, new_table, restore_f=restore_f, parent=self)
        restore_old_f = lambda: self.restore_ma_unit_and_table(
            old_ma_unit, old_table)
        restore_new_f = lambda: self.restore_ma_unit_and_table(
            new_ma_unit, new_table)
        command = calc_fncs.CommandFieldChanged(restore_new_f=restore_new_f,
                                                restore_old_f=restore_old_f,
                                                parent=self)
        self.undoStack.push(command)
    def _cell_changed(self, row, col):

        old_ma_unit, old_tables_data = self._save_ma_unit_and_table_states(
            tables=self.tables,
            ma_unit=self.ma_unit,
            table=self.simple_table,
            row=row,
            col=col,
            old_value=self.current_item_data[self.simple_table],
            use_old_value=True)
        old_correlation = self._get_correlation_str()

        # Just for simple_table for now
        column_headers = self.get_column_header_strs()
        try:
            warning_msg = self._cell_data_not_valid(
                self.simple_table.item(row, col).text(), column_headers[col])
            if warning_msg:
                raise Exception("Invalid Cell Data")
            self.impute_data()
        except Exception as e:
            msg = e.args[0]
            QMessageBox.warning(self.parent(), "whoops", msg)
            self.restore_ma_unit_and_tables(old_ma_unit, old_tables_data,
                                            old_correlation)
            return

        self._copy_raw_data_from_table_to_ma_unit()  # table --> ma_unit
        self.try_to_update_cur_outcome()

        new_ma_unit, new_tables_data = self._save_ma_unit_and_table_states(
            tables=self.tables,
            ma_unit=self.ma_unit,
            table=self.simple_table,
            row=row,
            col=col,
            use_old_value=False)
        new_correlation = self._get_correlation_str()
        restore_old_f = lambda: self.restore_ma_unit_and_tables(
            old_ma_unit, old_tables_data, old_correlation)
        restore_new_f = lambda: self.restore_ma_unit_and_tables(
            new_ma_unit, new_tables_data, new_correlation)
        command = calc_fncs.CommandFieldChanged(restore_new_f=restore_new_f,
                                                restore_old_f=restore_old_f,
                                                parent=self)
        self.undoStack.push(command)
 def cell_changed(self, row, col):
     old_ma_unit, old_table = self._save_ma_unit_and_table_state(
                                     table = self.two_by_two_table,
                                     ma_unit = self.ma_unit, 
                                     old_value = self.current_item_data,
                                     row = row, col = col, use_old_value=True)
     old_prevalence = self._get_prevalence_str()
     
     try:
         # Test if entered data is valid (a number)
         warning_msg = self.cell_data_invalid(self.two_by_two_table.item(row, col).text())
         if warning_msg:
             raise Exception("Invalid Cell Data")
 
         self._update_data_table() # calculate rest of table (provisionally) based on new entry
         warning_msg = self.check_table_consistency.run()
         if warning_msg:
             raise Exception("Table no longer consistent.")
     except Exception as e:
         msg = e.args[0]
         QMessageBox.warning(self.parent(), "whoops", msg) #popup warning
         self.restore_ma_unit_and_table(old_ma_unit,old_table, old_prevalence) # brings things back to the way they were
         return                    # and leave
     
     # if we got here, everything seems ok
     self._update_ma_unit()           # 2x2 table --> ma_unit
     self.impute_effects_in_ma_unit() # effects   --> ma_unit
     self.set_current_effect()        # ma_unit   --> effects
 
     new_ma_unit, new_table = self._save_ma_unit_and_table_state(
                                 table = self.two_by_two_table,
                                 ma_unit = self.ma_unit, 
                                 row = row, col = col,
                                 use_old_value = False)
     new_prevalence = self._get_prevalence_str()
     restore_old_f = lambda: self.restore_ma_unit_and_table(old_ma_unit, old_table, old_prevalence)
     restore_new_f = lambda: self.restore_ma_unit_and_table(new_ma_unit, new_table, new_prevalence)
     command = calc_fncs.CommandFieldChanged(restore_new_f=restore_new_f, restore_old_f=restore_old_f, parent=self)
     self.undoStack.push(command)
    def enable_back_calculation_btn(self, engage = False):
        # For undo/redo
        old_ma_unit, old_table = self._save_ma_unit_and_table_state(
                                table = self.two_by_two_table,
                                ma_unit = self.ma_unit, 
                                use_old_value=False)
        old_prevalence = self._get_prevalence_str()
        
        def build_dict():
            d = {}

            for effect in BACK_CALCULATABLE_DIAGNOSTIC_EFFECTS:    
                est,lower,upper = self.ma_unit.get_effect_and_ci(effect,
                                                                 self.group_str,
                                                                 self.mult)
                conv_to_disp_scale = lambda x: meta_py_r.diagnostic_convert_scale(x, effect, convert_to="display.scale")
                d_est,d_lower,d_upper = [conv_to_disp_scale(x) for x in [est,lower,upper]]
                for i,Rsubkey in enumerate(["",".lb",".ub"]):
                    try:
                        d["%s%s" % (effect.lower(), Rsubkey)] = float([d_est,d_lower,d_upper][i])
                    except:
                        pass
            
            x = self.getTotalSubjects()
            d["total"] = float(x) if is_a_float(x) else None

            x = self.prevalence_txt_box.text()
            d["prev"] = float(x) if is_a_float(x) else None

            d["conf.level"] = self.global_conf_level
    
            # now grab the raw data, if available
            d.update(self.get_raw_diag_data())
            
            return d
        
        def new_data(diag_data, imputed):
            new_data = (imputed["TP"],
                        imputed["FP"],
                        imputed["FN"],
                        imputed["TN"])
            old_data = (self._get_int(0,0),
                        self._get_int(0,1),
                        self._get_int(1,0),
                        self._get_int(1,1),
                        )
            isBlank = lambda x: x in EMPTY_VALS
            new_item_available = lambda old, new: isBlank(old) and not isBlank(new)
            comparison = [new_item_available(old_data[i], new_data[i]) for i in range(len(new_data))]
            print("Comparison:", comparison)
            if any(comparison):
                changed = True
            else:
                changed = False
            return changed
            
        diag_data = build_dict()
        print("Diagnostic Data for back-calculation: ", diag_data)

        #if diag_data is not None:
            
        imputed = meta_py_r.impute_diag_data(diag_data)
        print "imputed data: %s" % imputed
        
        # Leave if nothing was imputed
        if not (imputed["TP"] or imputed["TN"] or imputed["FP"] or imputed["FN"]):
            print("Nothing could be imputed")
            self.back_calc_Btn.setEnabled(False)
            return None
    
        if new_data(diag_data, imputed):
            self.back_calc_Btn.setEnabled(True)
        else:
            self.back_calc_Btn.setEnabled(False)
        #self.set_clear_btn_color()
            
        if not engage:
            return None
        ########################################################################
        # Actually do stuff with imputed data here if we are 'engaged'
        ########################################################################
        self.update_2x2_table(imputed)
        self._update_data_table()
        self._update_ma_unit()
        #self.set_clear_btn_color()
        
        # For undo/redo
        new_ma_unit, new_table = self._save_ma_unit_and_table_state(
                table = self.two_by_two_table, ma_unit = self.ma_unit,
                use_old_value=False)
        new_prevalence = self._get_prevalence_str()
        restore_old_f = lambda: self.restore_ma_unit_and_table(old_ma_unit, old_table, old_prevalence)
        restore_new_f = lambda: self.restore_ma_unit_and_table(new_ma_unit, new_table, new_prevalence)
        command = calc_fncs.CommandFieldChanged(restore_new_f=restore_new_f, restore_old_f=restore_old_f, parent=self)
        self.undoStack.push(command)
    def enable_back_calculation_btn(self, engage=False):
        # For undo/redo
        old_ma_unit, old_table = self._save_ma_unit_and_table_state(
            table=self.raw_data_table,
            ma_unit=self.ma_unit,
            use_old_value=False)

        def build_back_calc_args_dict():

            d = {}
            d["metric"] = str(self.cur_effect)

            est, lower, upper = self.ma_unit.get_effect_and_ci(
                self.cur_effect, self.group_str, self.mult)
            conv_to_disp_scale = lambda x: meta_py_r.binary_convert_scale(
                x, self.cur_effect, convert_to="display.scale")
            d_est, d_lower, d_upper = [
                conv_to_disp_scale(x) for x in [est, lower, upper]
            ]
            for i, R_key in enumerate(["estimate", "lower", "upper"]):
                try:
                    d["%s" % R_key] = float([d_est, d_lower, d_upper][i])
                except:
                    d["%s" % R_key] = None

            d["conf.level"] = self.global_conf_level

            d["Ev_A"] = float(self._get_int(
                0, 0)) if not self._is_empty(0, 0) else None
            d["N_A"] = float(self._get_int(
                0, 2)) if not self._is_empty(0, 2) else None
            d["Ev_B"] = float(self._get_int(
                1, 0)) if not self._is_empty(1, 0) else None
            d["N_B"] = float(self._get_int(
                1, 2)) if not self._is_empty(1, 2) else None

            return d

        def new_data(bin_data, imputed):
            changed = False
            old_data = (bin_data["Ev_A"], bin_data["N_A"], bin_data["Ev_B"],
                        bin_data["N_B"])
            new_data = []
            new_data.append((
                int(round(imputed["op1"]["a"])),
                int(round(imputed["op1"]["b"])),
                int(round(imputed["op1"]["c"])),
                int(round(imputed["op1"]["d"])),
            ))
            if "op2" in imputed:
                new_data.append((
                    int(round(imputed["op2"]["a"])),
                    int(round(imputed["op2"]["b"])),
                    int(round(imputed["op2"]["c"])),
                    int(round(imputed["op2"]["d"])),
                ))

            def new_item_available(old, new):
                isBlank = lambda x: x in EMPTY_VALS
                no_longer_blank = isBlank(old) and not isBlank(new)
                return no_longer_blank

            comparison0 = [
                new_item_available(old_data[i], new_data[0][i])
                for i in range(len(old_data))
            ]
            new_data_in_op1 = any(comparison0)
            print("Comparison0:", comparison0)

            if new_data_in_op1:
                changed = True
                if "op2" in imputed:
                    comparison1 = [
                        new_item_available(old_data[i], new_data[1][i])
                        for i in range(len(old_data))
                    ]
                    print("Comparison1:", comparison1)
                    new_data_in_op2 = any(comparison1)
                    if not new_data_in_op2:
                        changed = False
            else:
                changed = False

            return changed

        ### end of new_data() definition ####

        # Makes no sense to show the button on a form where the back
        # calculation is not implemented
        if not self.cur_effect in ["OR", "RR", "RD"]:
            self.back_calc_btn.setVisible(False)
            return None
        else:
            self.back_calc_btn.setVisible(True)

        bin_data = build_back_calc_args_dict()
        print("Binary data for back-calculation:", bin_data)

        imputed = meta_py_r.impute_bin_data(bin_data.copy())
        print("Imputed data: %s", imputed)

        # Leave if nothing was imputed
        if "FAIL" in imputed:
            print("Fail to impute")
            self.back_calc_btn.setEnabled(False)
            return None

        if new_data(bin_data, imputed):
            self.back_calc_btn.setEnabled(True)
        else:
            self.back_calc_btn.setEnabled(False)

        #self.set_clear_btn_color()

        if not engage:
            return None
        ########################################################################
        # Actually do stuff with imputed data here if we are 'engaged'
        ########################################################################
        for x in range(3):
            self.clear_column(x)  # clear out the table

        if len(imputed.keys()) > 1:
            dialog = ChooseBackCalcResultForm(imputed, parent=self)
            if dialog.exec_():
                choice = dialog.getChoice()
            else:  # don't do anything if cancelled
                return None
        else:  # only one option
            choice = "op1"

        # set values in table & save in ma_unit
        self.raw_data_table.blockSignals(True)
        self._set_val(0, 0, int(round(imputed[choice]["a"])))
        self._set_val(0, 2, int(round(imputed[choice]["b"])))
        self._set_val(1, 0, int(round(imputed[choice]["c"])))
        self._set_val(1, 2, int(round(imputed[choice]["d"])))
        self.raw_data_table.blockSignals(False)

        self._update_data_table()
        self._update_ma_unit()  # save in ma_unit
        #self.set_clear_btn_color()

        # for undo/redo
        new_ma_unit, new_table = self._save_ma_unit_and_table_state(
            table=self.raw_data_table,
            ma_unit=self.ma_unit,
            use_old_value=False)
        restore_old_f = lambda: self.restore_ma_unit_and_table(
            old_ma_unit, old_table)
        restore_new_f = lambda: self.restore_ma_unit_and_table(
            new_ma_unit, new_table)
        command = calc_fncs.CommandFieldChanged(restore_new_f=restore_new_f,
                                                restore_old_f=restore_old_f,
                                                parent=self)
        self.undoStack.push(command)
    def enable_back_calculation_btn(self, engage=False):
        # For undo/redo
        old_ma_unit, old_tables_data = self._save_ma_unit_and_table_states(
            tables=[
                self.simple_table, self.g1_pre_post_table,
                self.g2_pre_post_table
            ],
            ma_unit=self.ma_unit,
            use_old_value=False)
        old_correlation = self._get_correlation_str()

        # Choose metric parameter if not already chosen
        if (self.metric_parameter is None) and self.cur_effect in [
                "MD", "SMD"
        ]:
            print("need to choose metric parameter because it is %s" %
                  str(self.metric_parameter))
            if self.cur_effect == "MD":
                info = "In order to perform back-calculation most accurately, we need to know something about the assumptions about the two population standard deviations.\n*Are we assuming that both of the population standard deviations are the same (as in most parametric data analysis techniques)"
                option0_txt = "yes (default)."
                option1_txt = "no"
                dialog = ChooseBackCalcResultForm(info, option0_txt,
                                                  option1_txt)
                dialog.setWindowTitle("Population SD Assumptions")
                if dialog.exec_():
                    self.metric_parameter = True if dialog.getChoice(
                    ) == 0 else False
            elif self.cur_effect == "SMD":
                info = "In order to perform back-calculation most accurately, we need to know if the the bias in the SMD been corrected i.e. should we use Hedge's g or Cohen's d when performing the back calculation?"
                option0_txt = "Hedges' g (default)"
                option1_txt = "Cohen's d"
                dialog = ChooseBackCalcResultForm(info, option0_txt,
                                                  option1_txt)
                dialog.setWindowTitle("SMD bias correction")
                if dialog.exec_():
                    self.metric_parameter = True if dialog.getChoice(
                    ) == 0 else False
            print("metric_parameter is now %s" % str(self.metric_parameter))

        def build_data_dicts():
            var_names = self.get_column_header_strs()
            tmp = []
            for row_index in range(2):
                value = lambda x: self._get_float(row_index, x)
                tmp.append([(var_name, value(i))
                            for i, var_name in enumerate(var_names)
                            if value(i) is not None])
            group1_data = dict(tmp[0])
            group2_data = dict(tmp[1])

            tmp = self.ma_unit.get_effect_and_ci(self.cur_effect,
                                                 self.group_str, self.mult)
            effect_data = {
                "est": tmp[0],
                "low": tmp[1],
                "high": tmp[2],
                "metric": self.cur_effect,
                "met.param": self.metric_parameter
            }

            #print("Group 1 Data: ", group1_data)
            #print("Group 2 Data: ", group2_data)
            #print("Effect Data: ", effect_data)

            return (group1_data, group2_data, effect_data)

        def new_data(g1_data, g2_data, imputed):
            changed = False

            new_data = (imputed["n1"], imputed["sd1"], imputed["mean1"],
                        imputed["n2"], imputed["sd2"], imputed["mean2"])
            old_data = (
                g1_data["n"] if "n" in g1_data else None,
                g1_data["sd"] if "sd" in g1_data else None,
                g1_data["mean"] if "mean" in g1_data else None,
                g2_data["n"] if "n" in g2_data else None,
                g2_data["sd"] if "sd" in g2_data else None,
                g2_data["mean"] if "mean" in g2_data else None,
            )
            new_item_available = lambda old, new: (old is None) and (new is
                                                                     not None)
            comparison = [
                new_item_available(old_data[i], new_data[i])
                for i in range(len(new_data))
            ]
            print("Comparison:", comparison)
            if any(comparison):
                changed = True
            else:
                changed = False
            return changed

        if self.cur_effect not in ["MD", "SMD"]:
            self.back_calc_btn.setVisible(False)
            return None
        else:
            self.back_calc_btn.setVisible(True)

        (group1_data, group2_data, effect_data) = build_data_dicts()
        imputed = meta_py_r.back_calc_cont_data(group1_data, group2_data,
                                                effect_data, self.conf_level)
        print("Imputed data: ", imputed)

        # Leave if there was a failure
        if "FAIL" in imputed:
            print("Failure to impute")
            self.back_calc_btn.setEnabled(False)
            return None

        if new_data(group1_data, group2_data, imputed):
            self.back_calc_btn.setEnabled(True)
        else:
            self.back_calc_btn.setEnabled(False)
        self.set_clear_btn_color()

        if not engage:
            return None

        ########################################################################
        # Actually do stuff with imputed data here if we are 'engaged'
        ########################################################################
        # Choose one of the values if multiple ones were returned in the output
        keys_to_names = {
            "n1": "group 1 sample size",
            "n2": "group 2 sample size",
            "sd1": "group 1 standard deviation",
            "sd2": "group 2 standard deviation",
            "mean1": "group 1 mean",
            "mean2": "group 2 mean"
        }
        for key, value in imputed.iteritems():
            # TODO: (maybe).....: The R code which generates results can
            # POTENTIALLY yield a maximum of 4 numbers for n1 and n2. However,
            # empirical testing has shown that this doesn't really happen.
            # However, for completeness in the future the number of
            # ChooseBackCalcResultForm options should be generated dynamically

            if is_list(value):
                info = (
                    "The back calculation has resulted in multiple results for "
                    + keys_to_names[key] +
                    "\n\nPlease choose one of the following:")
                option0_txt = keys_to_names[key] + " = " + str(value[0])
                option1_txt = keys_to_names[key] + " = " + str(value[1])
                print("Options (0,1)", value[0], value[1])

                dialog = ChooseBackCalcResultForm(info, option0_txt,
                                                  option1_txt)
                if dialog.exec_():
                    imputed[key] = value[0] if dialog.getChoice(
                    ) == 0 else value[1]
                else:  # pressed cancel
                    return None  # do nothing and leave

        # Write the data to the table
        var_names = self.get_column_header_strs()
        group1_data = {
            "n": imputed["n1"],
            "sd": imputed["sd1"],
            "mean": imputed["mean1"]
        }
        group2_data = {
            "n": imputed["n2"],
            "sd": imputed["sd2"],
            "mean": imputed["mean2"]
        }
        for row in range(len(self.cur_groups)):
            for var_index, var_name in enumerate(var_names):
                if var_name not in ["n", "sd", "mean"]:
                    continue
                val = group1_data[var_name] if row == 0 else group2_data[
                    var_name]
                if var_name == 'n' and val not in EMPTY_VALS:
                    val = int(round(val))  # convert float to integer
                self._set_val(row, var_index, val, self.simple_table)

        self.impute_data()
        self._copy_raw_data_from_table_to_ma_unit()
        #self.set_clear_btn_color()

        # For undo/redo
        self.enable_back_calculation_btn()
        new_ma_unit, new_tables_data = self._save_ma_unit_and_table_states(
            tables=[
                self.simple_table, self.g1_pre_post_table,
                self.g2_pre_post_table
            ],
            ma_unit=self.ma_unit,
            use_old_value=False)
        new_correlation = self._get_correlation_str()
        restore_old_f = lambda: self.restore_ma_unit_and_tables(
            old_ma_unit, old_tables_data, old_correlation)
        restore_new_f = lambda: self.restore_ma_unit_and_tables(
            new_ma_unit, new_tables_data, new_correlation)
        command = calc_fncs.CommandFieldChanged(restore_new_f=restore_new_f,
                                                restore_old_f=restore_old_f,
                                                parent=self)
        self.undoStack.push(command)
    def impute_pre_post_data(self, table, group_index, row=None, col=None):
        ''' 
        The row index corresponds to the group that will be
        affected by the data edits. E.g., a row index of 0 will result
        in the data for the first group (row 0 in the simple_table)
        being modified.
        '''

        if not (row, col) == (
                None, None
        ):  # means this was called through user interaction, not programmatically
            old_ma_unit, old_tables_data = self._save_ma_unit_and_table_states(
                tables=self.tables,
                ma_unit=self.ma_unit,
                table=table,
                row=row,
                col=col,
                old_value=self.current_item_data[table],
                use_old_value=True)
            old_correlation = self._get_correlation_str()

        group_name = self.cur_groups[group_index]
        var_names = self.get_column_header_strs_pre_post()
        params_dict = {}
        # A, B correspond to pre, post
        for a_b_index, a_b_name in enumerate(["A", "B"]):
            # assemble the fields in a dictionary; pass off to meta_py_r
            for var_index, var_name in enumerate(var_names):
                var_value = self._get_float(a_b_index, var_index, table)
                if var_value is not None:
                    params_dict["%s.%s" % (var_name, a_b_name)] = var_value
        params_dict['metric'] = ("'%s'" % self.cur_effect)

        # now pass off what we have for this study to the
        # imputation routine
        results_from_r = meta_py_r.impute_pre_post_cont_data(
            params_dict, float(self.correlation_pre_post.text()),
            self.conf_level_to_alpha())

        print "imputation results from R: %s" % results_from_r

        if not results_from_r["succeeded"]:
            return None

        print("Prepost-imputation succeeded")

        ###
        # first update the simple table
        computed_vals = results_from_r["output"]

        for var_index, var_name in enumerate(self.get_column_header_strs()):
            val = computed_vals[var_name]
            self._set_val(group_index, var_index, val)

            # update the raw data for N, mean and SD fields (this is all that is actually stored)
            if var_index < 3:
                self.ma_unit.get_raw_data_for_group(
                    group_name)[var_index] = computed_vals[var_name]  #

        self.try_to_update_cur_outcome()

        ###
        # also update the pre/post tables
        pre_vals = results_from_r["pre"]
        post_vals = results_from_r["post"]
        for var_index, var_name in enumerate(var_names):
            pre_val = pre_vals[var_name]
            post_val = post_vals[var_name]
            self._set_val(0, var_index, pre_val, table)
            self._set_val(1, var_index, post_val, table)

        self._copy_raw_data_from_table_to_ma_unit()
        self.set_clear_btn_color()

        # function was invoked as a result of user interaction, not
        # programmatically
        if not (row, col) == (None, None):
            new_ma_unit, new_tables_data = self._save_ma_unit_and_table_states(
                tables=self.tables,
                ma_unit=self.ma_unit,
                table=table,
                row=row,
                col=col,
                use_old_value=False)
            new_correlation = self._get_correlation_str()
            restore_old_f = lambda: self.restore_ma_unit_and_tables(
                old_ma_unit, old_tables_data, old_correlation)
            restore_new_f = lambda: self.restore_ma_unit_and_tables(
                new_ma_unit, new_tables_data, new_correlation)
            command = calc_fncs.CommandFieldChanged(
                restore_new_f=restore_new_f,
                restore_old_f=restore_old_f,
                parent=self)
            self.undoStack.push(command)
    def val_changed(self, val_str):
        # Backup form state
        old_ma_unit, old_tables_data = self._save_ma_unit_and_table_states(
            tables=[
                self.simple_table, self.g1_pre_post_table,
                self.g2_pre_post_table
            ],
            ma_unit=self.ma_unit,
            use_old_value=False)
        old_correlation = self.current_correlation

        new_text = self._get_txt_from_val_str(val_str)

        no_errors, display_scale_val = self._text_box_value_is_between_bounds(
            val_str, new_text)
        if no_errors is False:
            print("There was an error while in val_changed")
            self.restore_ma_unit_and_tables(old_ma_unit, old_tables_data,
                                            old_correlation)
            calc_fncs.block_signals(self.entry_widgets, True)
            if val_str == "est":
                self.effect_txt_box.setFocus()
            elif val_str == "lower":
                self.low_txt_box.setFocus()
            elif val_str == "upper":
                self.high_txt_box.setFocus()
            elif val_str == "correlation_pre_post":
                self.correlation_pre_post.setFocus()
            calc_fncs.block_signals(self.entry_widgets, False)
            return

        # If we got to this point it means everything is ok so far
        try:
            if display_scale_val not in EMPTY_VALS:
                display_scale_val = float(display_scale_val)
            else:
                display_scale_val = None
        except ValueError:
            # a number wasn't entered; ignore
            # should probably clear out the box here, too.
            print "fail."
            return None

        calc_scale_val = meta_py_r.continuous_convert_scale(
            display_scale_val, self.cur_effect, convert_to="calc.scale")

        if val_str == "est":
            self.ma_unit.set_effect(self.cur_effect, self.group_str,
                                    calc_scale_val)
        elif val_str == "lower":
            self.ma_unit.set_lower(self.cur_effect, self.group_str,
                                   calc_scale_val)
        elif val_str == "upper":
            self.ma_unit.set_upper(self.cur_effect, self.group_str,
                                   calc_scale_val)
        elif val_str == "correlation_pre_post":
            print "ok -- correlation set to %s" % self.correlation_pre_post.text(
            )
            # Recompute the estimates
            self.impute_pre_post_data(self.g1_pre_post_table, 0)
            self.impute_pre_post_data(self.g2_pre_post_table, 1)

        self.impute_data()  #### experimental

        new_ma_unit, new_tables_data = self._save_ma_unit_and_table_states(
            tables=[
                self.simple_table, self.g1_pre_post_table,
                self.g2_pre_post_table
            ],
            ma_unit=self.ma_unit,
            use_old_value=False)
        new_correlation = self._get_correlation_str()
        restore_old_f = lambda: self.restore_ma_unit_and_tables(
            old_ma_unit, old_tables_data, old_correlation)
        restore_new_f = lambda: self.restore_ma_unit_and_tables(
            new_ma_unit, new_tables_data, new_correlation)
        command = calc_fncs.CommandFieldChanged(restore_new_f=restore_new_f,
                                                restore_old_f=restore_old_f,
                                                parent=self)
        self.undoStack.push(command)

        self.current_correlation = new_correlation
    def clear_form(self):
        # For undo/redo
        old_ma_unit, old_tables_data = self._save_ma_unit_and_table_states(
            tables=[
                self.simple_table, self.g1_pre_post_table,
                self.g2_pre_post_table
            ],
            ma_unit=self.ma_unit,
            use_old_value=False)
        old_correlation = self._get_correlation_str()

        self.metric_parameter = None  # } these two should go together
        self.enable_txt_box_input()  # }

        calc_fncs.block_signals(self.entry_widgets, True)
        # reset tables
        for table in self.tables:
            for row_index in range(len(self.cur_groups)):
                for var_index in range(table.columnCount()):
                    self._set_val(row_index, var_index, "", table=table)
        calc_fncs.block_signals(self.entry_widgets, False)

        self._copy_raw_data_from_table_to_ma_unit()

        # clear out effects stuff
        for metric in CONTINUOUS_ONE_ARM_METRICS + CONTINUOUS_TWO_ARM_METRICS:
            if ((self.cur_effect in CONTINUOUS_TWO_ARM_METRICS
                 and metric in CONTINUOUS_TWO_ARM_METRICS)
                    or (self.cur_effect in CONTINUOUS_ONE_ARM_METRICS
                        and metric in CONTINUOUS_ONE_ARM_METRICS)):
                self.ma_unit.set_effect_and_ci(metric,
                                               self.group_str,
                                               None,
                                               None,
                                               None,
                                               mult=self.mult)
            else:
                # TODO: Do nothing for now..... treat the case where we have to switch group strings down the line
                pass

        # clear line edits
        self.set_current_effect()
        calc_fncs.block_signals(self.entry_widgets, True)
        self.correlation_pre_post.setText("0.0")
        calc_fncs.block_signals(self.entry_widgets, False)

        # For undo/redo
        self.enable_back_calculation_btn()
        new_ma_unit, new_tables_data = self._save_ma_unit_and_table_states(
            tables=[
                self.simple_table, self.g1_pre_post_table,
                self.g2_pre_post_table
            ],
            ma_unit=self.ma_unit,
            use_old_value=False)
        new_correlation = self._get_correlation_str()
        restore_old_f = lambda: self.restore_ma_unit_and_tables(
            old_ma_unit, old_tables_data, old_correlation)
        restore_new_f = lambda: self.restore_ma_unit_and_tables(
            new_ma_unit, new_tables_data, new_correlation)
        command = calc_fncs.CommandFieldChanged(restore_new_f=restore_new_f,
                                                restore_old_f=restore_old_f,
                                                parent=self)
        self.undoStack.push(command)