def open_output_settings_popup(self):

        "Check to see if an output has been chosen"

        if self.model.get_selected_output_flag() == True:

            self.output_settings_popup_view = OutputSettingsPopupView(self.model, self.main_ctrl, self)

            self.output_settings_popup_view.show()
class MainView(QtGui.QMainWindow):
    def __init__(self, model, main_ctrl):

        super(MainView, self).__init__()

        self.model = model
        self.main_ctrl = main_ctrl

        self._scroll_container_widget = None

        self.build_ui()

    def build_ui(self):

        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

        # MIDI CHAIN GUI Widgets

        self.midi_chain_container = MidiChainContainer(self.model, self, self.main_ctrl)

        # Input Source

        "Set the input options for the input"

        input_options = self.model.get_input_options()

        for item in input_options:

            self.ui.midi_source_combo_box.addItem(item)

        # Output

        output_options = self.model.get_output_options()

        for item in output_options:

            self.ui.output_combo_box.addItem(item)

        "tempoView"

        self.tempo_view = TempoView(self.model, self.main_ctrl)

        self.ui.tempo_layout.addWidget(self.tempo_view)

        # KEYBOARD GUI

        self.keyboard = KeyBoard(self.model, self.main_ctrl, self)

        self.ui.keyboard_total_layout.addWidget(self.keyboard)

        "Metronome view"

        self.metronome_view = MetronomeWidget(self.model, self.main_ctrl, self)

        self.ui.metronome_layout.addWidget(self.metronome_view)

        "Input File Widget"

        self.ui.midi_source_settings_button.clicked.connect(self.open_input_settings_popup)

        "Output File Widget"

        self.ui.output_settings_button.clicked.connect(self.open_output_settings_popup)

        self.ui.record_file_button.setEnabled(False)

        self.ui.record_file_button.clicked.connect(self.record_save_midi_file)

        # Set additional properties for self.ui widgets

        self.ui.midi_chain_scroll_area.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        self.ui.midi_chain_scroll_area.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
        self.ui.midi_chain_scroll_area.setWidgetResizable(True)

        self.ui.midi_chain_scroll_area.setWidget(self.midi_chain_container)

        # layout.addStretch()

        # Connect the signals to the required methods

        "File Menu Signals"
        self.ui.load_scales_action.triggered.connect(self.open_scale_file)

        self.ui.exit_action.triggered.connect(self.close)

        self.ui.scale_type_combo_box.currentIndexChanged.connect(self.scale_type_selected)

        self.ui.scale_root_combo_box.currentIndexChanged.connect(self.root_note_selected)

        "Configuration Menu Signals"

        self.ui.Undo_action.triggered.connect(self.undo_action)

        "Input Source Signals"

        self.ui.midi_source_combo_box.currentIndexChanged.connect(self.input_selected)

        "Output Source Signals"

        self.ui.output_combo_box.currentIndexChanged.connect(self.output_selected)

        "Midi Chain Signals"
        # Add block button
        self.ui.add_block_button.clicked.connect(self.midi_chain_container.add_block_to_chain)

        "MIDI INPUT FILE"

        self.ui.play_midi_file_button.setEnabled(False)

        self.ui.open_midi_file_button.clicked.connect(self.open_midi_file)

        self.ui.play_midi_file_button.clicked.connect(self.play_midi_file)

        "CONFIGURATION FILES"

        self.ui.new_config_button.clicked.connect(self.new_configuration)

        self.ui.save_config_button.clicked.connect(self.save_configuration)

        self.ui.load_config_button.clicked.connect(self.load_configuration)

    # File Menu Methods
    def open_scale_file(self):

        file_path_q_string = QtGui.QFileDialog.getOpenFileName(self, "Open Scales File")

        file_path = str(file_path_q_string[0])

        self.main_ctrl.read_in_scale_file(file_path)

        # if successful
        if len(self.model.get_scale_types()) > 0:

            "Scale types list has things loaded into it. There successful"
            self.add_scale_types_to_scale_type_combo_box()

    # Scale Combo Box Methods

    def add_scale_types_to_scale_type_combo_box(self):

        for scale_type in self.model.get_scale_types():

            self.ui.scale_type_combo_box.addItem(scale_type)

    def add_root_notes_to_scale_root_combo_box(self):

        self.ui.scale_root_combo_box.clear()

        for root_note in self.model.get_root_notes():

            self.ui.scale_root_combo_box.addItem(root_note)

    def scale_type_selected(self):

        self.model.select_scale_type(self.ui.scale_type_combo_box.currentText())

        if len(self.model.get_root_notes()) > 0:

            "Root notes have been loaded"
            self.add_root_notes_to_scale_root_combo_box()

    def root_note_selected(self):

        self.model.select_root_note(self.ui.scale_root_combo_box.currentText())

        # Need to change the keyboard keys

        self.keyboard.update_keyboard_key_text()
        self.keyboard.update_keyboard_key_palette()
        "If the output is to a USB Device, then send the new button config"
        if self.model.get_selected_output() == "USB Device":
            if self.main_ctrl.output_ctrl.serial is not None:
                self.main_ctrl.output_ctrl.output_button_configuration()

    # INPUT SOURCE FUNCTIONS

    def input_selected(self):

        if self.model.get_seletected_scale_flag() == False:

            if self.ui.midi_source_combo_box.currentIndex() != 0:

                "Create a QMessage Box"
                QtGui.QMessageBox.about(
                    self,
                    "Choose A Scale",
                    "Please Load and Choose a Scale.\nTo load scales from a file: File - load scales.",
                )
                self.ui.midi_source_combo_box.setCurrentIndex(0)

        elif self.model.get_selected_output_flag() == False:
            if self.ui.midi_source_combo_box.currentIndex() != 0:

                self.ui.midi_source_combo_box.setCurrentIndex(0)
                "Create a QMessage Box"
                QtGui.QMessageBox.about(self, "Choose An Ouput", "Please Choose an Output")

        elif (
            self.model.get_selected_output() == "Output File" and self.model.get_output_file_paths_set_satus() == False
        ):

            if self.ui.midi_source_combo_box.currentIndex() != 0:

                self.ui.midi_source_combo_box.setCurrentIndex(0)

                "Create a QMessage Box"
                QtGui.QMessageBox.about(self, "Choose Output File Settings", "Please set the ouput file settings")

        else:

            index = self.ui.midi_source_combo_box.currentIndex()

            "Add to the undo actions list"
            "Action Tuple = ('Set Input Source', index)"
            self.model.add_to_undo_actions(("Set Input Source", self.model.get_most_recent_input_action_index()))

            self.model.add_to_input_action_index(index)

            self.model.set_selected_input(self.ui.midi_source_combo_box.currentText())

            self.keyboard.update_keyboard_availability()

            "keyboard view updated in update_keyboard_availability"

            "Update the availabilit of the record file button"
            self.update_record_file_button_status()

            "Update the midi source input"
            self.main_ctrl.midi_source_ctrl.update_receive_input()

        "If the metronome is on, turn it off"
        self.main_ctrl.tempo_thread_ctrl.stopTimer()

    def undo_input_selected(self, index):

        "set the combox box to the previous index"
        self.ui.midi_source_combo_box.setCurrentIndex(index)

        self.model.set_selected_input(self.ui.midi_source_combo_box.currentText())

        self.keyboard.update_keyboard_availability()

    # OUTPUT FUNCTIONS

    def output_selected(self):

        if self.model.get_seletected_scale_flag() == False:

            if self.ui.output_combo_box.currentIndex() != 0:

                "Create a QMessage Box"
                QtGui.QMessageBox.about(
                    self,
                    "Choose A Scale",
                    "Please Load and Choose a Scale.\nTo load scales from a file: File - load scales.",
                )
                self.ui.output_combo_box.setCurrentIndex(0)

        self.model.set_selected_output(self.ui.output_combo_box.currentText())

        self.main_ctrl.output_ctrl.select_output()

        "Update the availabilit of the record file button"
        self.update_record_file_button_status()

        "If the metronome is on, turn it off"
        self.main_ctrl.tempo_thread_ctrl.stopTimer()

    "Undo Actions functions"

    def undo_action(self):

        "Get the most recent action"

        if self.model.get_undo_actions_length() > 0:

            "Have an action to undo"

            action_types = self.model.get_action_types()

            action = self.model.get_most_recent_action()

            if action[0] == action_types[0]:

                "Action Tuple = ('Add Block')"

                "Add Block action occured"

                "get the last index"
                index = self.model.get_block_widget_list_length() - 1
                "Remove this block at that index"

                self.midi_chain_container.delete_block_at_index(index)

                self.model.remove_most_recent_action()

            elif action[0] == action_types[1]:

                "Action Tuple = ('Remove Block', index, block, proc_block)"

                "Remove Block action occured"

                self.midi_chain_container.add_block_at_index(action[1], action[2], action[3])

                self.model.remove_most_recent_action()

            elif action[0] == action_types[2]:

                "Action Tuple = ('Clear Chain', model.block_widget_list,  model._midi_block_chain)"
                "Clear Chain action occured"

                block_widget_list = action[1]
                midi_block_chain = action[2]

                for i in range(len(block_widget_list)):

                    self.midi_chain_container.add_block_at_index(i, block_widget_list[i], midi_block_chain[i])

                self.model.remove_most_recent_action()

            elif action[0] == action_types[3]:

                "Action Tuple = ('Change Block Type', index, block, proc_block)"
                "Change Block Type action occured"

                self.midi_chain_container.undo_change_block_type_at_index(action[1], action[2], action[3])

                self.model.remove_most_recent_action()

            elif action[0] == action_types[4]:

                "Action Tuple = ('Change Pitch Shift Parameter', index, proc_block, value)"

                "Change Block Parameter action occured"

                self.main_ctrl.midi_chain_ctrl.remove_block_from_chain(action[1])

                action[2].set_shift(action[3])

                self.main_ctrl.midi_chain_ctrl.add_block_to_chain_at_index(action[1], action[2])

                self.model.remove_most_recent_action()

            elif action[0] == action_types[5]:

                "Action Tuple = ('Change Arpeggiator Parameter', index, proc_block, pattern_index)"

                "Change Block Parameter action occured"

                self.main_ctrl.midi_chain_ctrl.remove_block_from_chain(action[1])

                action[2].set_selected_pattern_index(action[3])

                self.main_ctrl.midi_chain_ctrl.add_block_to_chain_at_index(action[1], action[2])

                self.model.remove_most_recent_action()

            elif action[0] == action_types[6]:

                "Action Tuple = ('Move Block Left', index)"

                "Moved the block left"

                self.midi_chain_container.undo_move_block_at_index_left(action[1])

                self.model.remove_most_recent_action()

            elif action[0] == action_types[7]:

                "Action Tuple = ('Move Block Right', index)"

                "Moved the block right"
                self.midi_chain_container.undo_move_block_at_index_right(action[1])

                self.model.remove_most_recent_action()

            elif action[0] == action_types[8]:

                "Action Tuple = ('Set Input Source', index)"

                "Changed the input source"
                self.undo_input_selected(action[1])

                self.model.remove_most_recent_action()

                self.model.remove_most_recent_action()

            else:

                print "Unable to undo action"

    "MIDI FILE INPUT FUNCTIONS"

    def open_midi_file(self):

        file_path_q_string = QtGui.QFileDialog.getOpenFileName(self, "Open Midi File")

        file_path = str(file_path_q_string[0])

        self.main_ctrl.play_midi_file_ctrl.open_midi_file(file_path)

        "Check to see if midi file was opened"

        if self.model.get_midi_file_opened() == True:

            "Enable the play button"

            self.ui.play_midi_file_button.setEnabled(True)

        else:

            self.ui.play_midi_file_button.setEnabled(False)

    def play_midi_file(self):

        self.main_ctrl.play_midi_file_ctrl.play_midi_file()

        "Check to see if the file is playing"

        if self.model.get_midi_file_playing() == True:

            self.ui.play_midi_file_button.setText("Stop")

        else:

            self.ui.play_midi_file_button.setText("Play")

    "OUTPUT FUNCTIONS"

    def open_output_settings_popup(self):

        "Check to see if an output has been chosen"

        if self.model.get_selected_output_flag() == True:

            self.output_settings_popup_view = OutputSettingsPopupView(self.model, self.main_ctrl, self)

            self.output_settings_popup_view.show()

    def update_record_file_button_status(self):
        """
        For the record_file_button to be enabled, the output needs to be 'Output File' and
        there needs to be a file_path and the input needs to be the virtual piano
        """

        if (
            self.model.get_selected_output() == "Output File"
            and self.model.get_output_file_paths_set_satus() == True
            and (self.model.get_selected_input() == "Virtual Piano" or self.model.get_selected_input() == "Midi Source")
        ):

            self.ui.record_file_button.setEnabled(True)

        else:

            self.ui.record_file_button.setEnabled(False)

    "Record button function"

    def record_save_midi_file(self):

        if self.model.get_record_output_file_commands() == False:

            self.main_ctrl.output_ctrl.record_save_midi_file()

            self.ui.record_file_button.setText("Save")

        else:

            self.main_ctrl.output_ctrl.record_save_midi_file()

            self.ui.record_file_button.setText("Record")

    "INPUT POPUP FUNCTIONS"

    def open_input_settings_popup(self):

        "Check to see if an input has been chosen"

        if self.model.get_selected_input() != "":

            self.input_settings_popup_view = InputSettingsPopupView(self.model, self.main_ctrl, self)

            self.input_settings_popup_view.show()

    "CONFIGURATION FUNCTIONS"

    def new_configuration(self):

        "To create a new configuration, clear the chosen input and the midi chain"

        "1. Clear the chosen input, the midi chain and the undo list"

        "Clear the input"
        self.ui.midi_source_combo_box.setCurrentIndex(0)

        self.keyboard.update_keyboard_availability()

        "keyboard view updated in update_keyboard_availability"

        "Update the availabilit of the record file button"
        self.update_record_file_button_status()

        "Clear the midi chain"
        self.midi_chain_container.clear_chain()

        "clear the undo list"
        self.model.set_undo_actions([])

        "Reset the metronome and the timer"
        self.main_ctrl.tempo_thread_ctrl.stopTimer()
        self.model.set_tempo_timer_started(False)

        "reset the metronome beats"

        self.model.set_tempo_beat_number(0)

    def save_configuration(self):

        "Get the location to save the configuration file to."
        config_file_path_string = QtGui.QFileDialog.getSaveFileName(self, "Save Configuration", os.getcwd(), "*.csv")

        if config_file_path_string:

            config_file_path = str(config_file_path_string[0])

            self.save_config_file(config_file_path)

    def save_config_file(self, file_path):

        with open(file_path, "wb") as csvfile:

            config_file_writer = csv.writer(csvfile, delimiter=",")

            "Save the input first"
            input_config_list = self.main_ctrl.config_file_input_list()
            config_file_writer.writerow(input_config_list)

            "Save the midi chain in order"

            "Get the length of the midi block chain"
            for i in range(self.model.get_midi_chain_length()):

                block_config_list = self.main_ctrl.config_file_midi_block_list_at_index(i)

                config_file_writer.writerow(block_config_list)

    def load_configuration(self):

        "Load a configuration file"

        "1. Get the filepath from the user"

        if self.model.get_seletected_scale_flag() == False:

            if self.ui.midi_source_combo_box.currentIndex() != 0:

                "Create a QMessage Box"
                QtGui.QMessageBox.about(
                    self,
                    "Choose A Scale",
                    "Please Load and Choose a Scale.\nTo load scales from a file: File - load scales.",
                )
                self.ui.midi_source_combo_box.setCurrentIndex(0)

        elif self.model.get_selected_output_flag() == False:
            if self.ui.midi_source_combo_box.currentIndex() != 0:

                self.ui.midi_source_combo_box.setCurrentIndex(0)
                "Create a QMessage Box"
                QtGui.QMessageBox.about(self, "Choose An Ouput", "Please Choose an Output")

        else:

            file_path_string = QtGui.QFileDialog.getOpenFileName(self, "Open Configuration File")

            if file_path_string:

                file_path = str(file_path_string[0])

                "Check that the file is a valid configuration"
                if self.validate_config_file(file_path) == False:
                    QtGui.QMessageBox.about(self, "Invalid Configuration", "Invalid Configuration File")

                else:

                    "Valid configuration file"

                    "Clear the previous configuration"

                    "Clear the input"
                    self.ui.midi_source_combo_box.setCurrentIndex(0)

                    "Clear the midi chain"
                    self.midi_chain_container.clear_chain()

                    "clear the undo list"
                    self.model.set_undo_actions([])

                    "Load the new configuration"

                    "Load the input"
                    self.load_config_file(file_path)

                    self.keyboard.update_keyboard_availability()

                    "keyboard view updated in update_keyboard_availability"

                    "Update the availabilit of the record file button"
                    self.update_record_file_button_status()

                    "Reset the metronome and the timer"
                    self.main_ctrl.tempo_thread_ctrl.stopTimer()
                    self.model.set_tempo_timer_started(False)

                    "reset the metronome beats"

                    self.model.set_tempo_beat_number(0)

    def load_config_file(self, file_path):

        config_file_reader = csv.reader(open(file_path, "rb"))

        row_num = 0

        for row in config_file_reader:

            if row[0] == "Input":

                "This is the input configuration"

                "Set the in_port"

                self.model.set_selected_in_port(row[2])

                "Get the index of the required input"

                input_options = self.model.get_input_options()

                input_index = input_options.index(row[1])

                self.ui.midi_source_combo_box.setCurrentIndex(input_index)

            else:

                "The row should be for a processing block"
                block_types = self.model.get_block_types()

                if row[0] == block_types[0]:

                    "Pitch Shift Block"
                    "add a Pitch Shift processing block"
                    pitch_shift_type_index = block_types.index(row[0])
                    self.midi_chain_container.add_config_block_to_chain(pitch_shift_type_index)

                    "Set the shift value"
                    shift_value = int(row[1])
                    pitch_shift_block = self.model.get_block_at_midi_chain_index(row_num)
                    pitch_shift_block.set_shift(shift_value)

                elif row[0] == block_types[1]:

                    "Arpeggiator"

                    "add a block widget and"
                    arpeggiator_type_index = block_types.index(row[0])
                    self.midi_chain_container.add_config_block_to_chain(arpeggiator_type_index)

                    "set the pattern index"
                    pattern_index = int(row[1])
                    arpeggiator_block = self.model.get_block_at_midi_chain_index(row_num)
                    arpeggiator_block.set_selected_pattern_index(pattern_index)

                elif row[0] == block_types[2]:

                    "Monophonic"
                    monophonic_type_index = block_types.index(row[0])
                    self.midi_chain_container.add_config_block_to_chain(monophonic_type_index)

                elif row[0] == block_types[3]:

                    "Chordify"

                    chordify_type_index = block_types.index(row[0])
                    self.midi_chain_container.add_config_block_to_chain(chordify_type_index)

                row_num += 1

    def validate_config_file(self, file_path):
        """
        Checks the file path and the file
        """

        "1. Check that it is a .csv file"

        if file_path[-4:] != ".csv":

            return False

        else:
            "The file path is to a .csv file"

            config_file_reader = csv.reader(open(file_path, "rb"))

            for row in config_file_reader:

                if row[0] == "Input":

                    "This is the input configuration"

                    input_options = self.model.get_input_options()

                    if row[1] not in input_options:

                        "not a valid input option"
                        return False

                else:

                    "The row should be for a processing block"
                    block_types = self.model.get_block_types()

                    if row[0] == block_types[0]:

                        "Pitch Shift Block"
                        if row[1].isdigit() == False:

                            return False
                    elif row[0] == block_types[1]:

                        "Arpeggiator"

                        valid_patterns = ["Ascending", "Descending", "Ping Pong", "Random"]

                        pattern_index = int(row[1])

                        if pattern_index < 0 or pattern_index > (len(valid_patterns) - 1):

                            return False

                    elif row[0] == block_types[2]:

                        "Monophonic"
                        continue

                    elif row[0] == block_types[3]:

                        "Chordify"
                        continue

                    else:
                        "Don't recognise this block type"
                        return False

        return True