示例#1
0
    def engine_server_setup(self, server_ip_host=None):
        server_ip, server_name = server_ip_host or (None, None)
        old_ip = None
        old_hostname = None
        if hasattr(self, 'server_ip'):
            old_ip = self.server_ip
            old_hostname = self.server_hostname

        self.server_ip = str(server_ip)
        self.server_hostname = str(server_name)
        ctx = None
        if server_ip is None:
            self.server_ip = '127.0.0.1'
            self.server_hostname = socket.gethostname()

        if hasattr(self, 'engine'):
            client = self.engine.client
            ctx = client.ctx
        else:
            self.engine = None

        if old_ip != self.server_ip and old_hostname != self.server_hostname:

            if self.engine is not None:
                self.engine.cleanup()
                self._disconnect_signals()

                # self.engine.deleteLater()
                sip.delete(self.engine)
                del self.engine
                self.engine = None


            client = obci_script.client_server_prep(server_ip=self.server_ip,
                                                    zmq_ctx=ctx,
                                                    start_srv=True)
            if client is None:
                self.quit()
            self.exp_states = {}
            self.engine = OBCILauncherEngine(client, self.server_ip)
            self._connect_signals()

        if self.server_ip and self.server_hostname != socket.gethostname():
            self.setWindowTitle(self.basic_title + ' - ' + 'remote connection ' + \
                                ' (' +self.server_ip + ' - ' + self.server_hostname + ')')
        else:
            self.setWindowTitle(self.basic_title + ' - ' + 'local connection (' +\
                                                self.server_hostname + ')')

        if old_ip is not None:
            self.engine.update_ui.emit(None)
示例#2
0
    def engine_server_setup(self, server_ip_host=None):
        server_ip, server_name = server_ip_host or (None, None)
        old_ip = None
        old_hostname = None
        if hasattr(self, 'server_ip'):
            old_ip = self.server_ip
            old_hostname = self.server_hostname

        self.server_ip = str(server_ip)
        self.server_hostname = str(server_name)
        ctx = None
        if server_ip is None:
            self.server_ip = '127.0.0.1'
            self.server_hostname = socket.gethostname()

        if hasattr(self, 'engine'):
            client = self.engine.client
            ctx = client.ctx
        else:
            self.engine = None

        if old_ip != self.server_ip and old_hostname != self.server_hostname:

            if self.engine is not None:
                self.engine.cleanup()
                self._disconnect_signals()

                # self.engine.deleteLater()
                sip.delete(self.engine)
                del self.engine
                self.engine = None


            client = obci_script.client_server_prep(server_ip=self.server_ip,
                                                    zmq_ctx=ctx,
                                                    start_srv=True)
            if client is None:
                self.quit()
            self.exp_states = {}
            self.engine = OBCILauncherEngine(client, self.server_ip)
            self._connect_signals()

        if self.server_ip and self.server_hostname != socket.gethostname():
            self.setWindowTitle(self.basic_title + ' - ' + 'remote connection ' + \
                                ' (' +self.server_ip + ' - ' + self.server_hostname + ')')
        else:
            self.setWindowTitle(self.basic_title + ' - ' + 'local connection (' +\
                                                self.server_hostname + ')')

        if old_ip is not None:
            self.engine.update_ui.emit(None)
示例#3
0
class ObciLauncherWindow(QMainWindow, Ui_OBCILauncher):
    '''
    classdocs
    '''
    start = pyqtSignal(str, object)
    stop = pyqtSignal(str, bool)
    reset = pyqtSignal(str)

    save_as = pyqtSignal(object)
    remove_user_preset = pyqtSignal(object)
    import_scenario = pyqtSignal(str)

    engine_reinit = pyqtSignal(object)

    def __init__(self):
        '''
        Constructor
        '''
        QMainWindow.__init__(self)

        self.setupUi(self)
        self.basic_title = self.windowTitle()

        self.exp_states = {}
        self.exp_widgets = {}

        self.scenarioTab.setTabText(0, "Scenario")
        self.scenarioTab.setTabsClosable(True)
        self.log_engine = obci_log_engine.LogEngine(self.scenarioTab)

        self.engine_server_setup()

        self._nearby_machines = self.engine.nearby_machines()

        self.scenarios.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.scenarios.setColumnCount(2)
        self.scenarios.setHeaderLabels(["Scenario", "Status"])
        self.scenarios.setColumnWidth(0, 300)

        self.scenarios.itemClicked.connect(self._setInfo)
        self.scenarios.currentItemChanged.connect(self._setInfo)

        self.details_mode.currentIndexChanged.connect(self.update_user_interface)

        self.parameters.setHeaderLabels(["Name", 'Value', 'Info'])
        self.parameters.itemClicked.connect(self._itemClicked)
        self.parameters.itemChanged.connect(self._itemChanged)
        self.parameters.itemDoubleClicked.connect(self._itemDoubleClicked)
        self.parameters.setColumnWidth(0, 200)
        self.parameters.setColumnWidth(1, 400)

        self.machines_dialog = ConnectToMachine(self)

        self.start_button.clicked.connect(self._start)
        self.stop_button.clicked.connect(self._stop)
        #self.reset_button.clicked.connect(self._reset)
        self.store_container.hide()
        self.store_checkBox.stateChanged.connect(self._update_store)
        self.store_dir_chooser.clicked.connect(self._choose_dir)

        self._params = []
        self._scenarios = []

        self.details_mode.addItems(MODES)

        self.engine_reinit.connect(self.engine_server_setup)

        self.setupMenus()
        self.setupActions()
        self.update_user_interface(None)
        self.showMaximized()
        if os.environ.get('OBCI_INSTALL_DIR') != None:
            PyQt4.QtGui.QMessageBox.information(self, "Non standard OpenBCI directory",
                                                "OpenBCI is launched from local directory: "+os.environ.get('OBCI_INSTALL_DIR')+', to start default package version launch "obci_local_remove" in terminal.')



    def closeEvent(self, e):
        progress = QProgressDialog(self,Qt.WindowStaysOnTopHint | Qt.FramelessWindowHint)
        progress.setLabelText("Finishing...")
        progress.setRange(0,5)
        progress.setCancelButton(None)
        progress.show()

        self.stop_logs()

        for i in range(5):
            time.sleep(0.4)
            progress.setValue(i+1)
        e.accept()

    def stop_logs(self):
        for i, st in self.exp_states.iteritems():
            st.log_model.stop_running()

    def engine_server_setup(self, server_ip_host=None):
        server_ip, server_name = server_ip_host or (None, None)
        old_ip = None
        old_hostname = None
        if hasattr(self, 'server_ip'):
            old_ip = self.server_ip
            old_hostname = self.server_hostname

        self.server_ip = str(server_ip)
        self.server_hostname = str(server_name)
        ctx = None
        if server_ip is None:
            self.server_ip = '127.0.0.1'
            self.server_hostname = socket.gethostname()

        if hasattr(self, 'engine'):
            client = self.engine.client
            ctx = client.ctx
        else:
            self.engine = None

        if old_ip != self.server_ip and old_hostname != self.server_hostname:

            if self.engine is not None:
                self.engine.cleanup()
                self._disconnect_signals()

                # self.engine.deleteLater()
                sip.delete(self.engine)
                del self.engine
                self.engine = None


            client = obci_script.client_server_prep(server_ip=self.server_ip,
                                                    zmq_ctx=ctx,
                                                    start_srv=True)
            if client is None:
                self.quit()
            self.exp_states = {}
            self.engine = OBCILauncherEngine(client, self.server_ip)
            self._connect_signals()

        if self.server_ip and self.server_hostname != socket.gethostname():
            self.setWindowTitle(self.basic_title + ' - ' + 'remote connection ' + \
                                ' (' +self.server_ip + ' - ' + self.server_hostname + ')')
        else:
            self.setWindowTitle(self.basic_title + ' - ' + 'local connection (' +\
                                                self.server_hostname + ')')

        if old_ip is not None:
            self.engine.update_ui.emit(None)

    def _connect_signals(self):
        self.engine.update_ui.connect(self.update_user_interface)
        self.engine.update_ui.connect(self.log_engine.update_user_interface)
        self.engine.rq_error.connect(self.launcher_error)
        self.engine.saver_msg.connect(self._saver_msg)
        self.reset.connect(self.engine.reset_launcher)
        self.start.connect(self.engine.start_experiment)
        self.start.connect(self.log_engine.experiment_started)
        self.stop.connect(self.engine.stop_experiment)
        self.stop.connect(self.log_engine.experiment_stopped)
        self.save_as.connect(self.engine.save_scenario_as)
        self.import_scenario.connect(self.engine.import_scenario)
        self.remove_user_preset.connect(self.engine.remove_preset)

    def _disconnect_signals(self):
        self.engine.update_ui.disconnect()
        self.engine.rq_error.disconnect()
        self.engine.obci_state_change.disconnect()
        self.engine.saver_msg.disconnect()
        self.reset.disconnect()
        self.start.disconnect()
        self.stop.disconnect()
        self.save_as.disconnect()
        self.import_scenario.disconnect()
        self.remove_user_preset.disconnect()

    def setupMenus(self):
        self.menuMenu.addAction(self.actionOpen)
        self.menuMenu.addAction(self.actionSave_as)
        self.menuMenu.addAction(self.actionRemove_from_sidebar)
        self.menuMenu.addSeparator()
        self.menuMenu.addAction(self.actionConnect)
        self.menuMenu.addSeparator()
        self.menuMenu.addAction(self.actionExit)


    def setupActions(self):
        self.actionExit.triggered.connect(PyQt4.QtGui.qApp.quit)
        self.actionConnect.triggered.connect(self._connect_to_machine)
        self.actionSave_as.triggered.connect(self._save_current_as)
        self.actionOpen.triggered.connect(self._import)
        self.actionRemove_from_sidebar.triggered.connect(self._remove_from_sidebar)


    def setScenarios(self, scenarios):
        scenarios.sort(cmp=lambda a,b: str(a.name) > str(b.name))
        self._scenarios = scenarios
        self.scenarios.setSortingEnabled(True)
        self.scenarios.clear()

        self.categories = []
        self.exp_widgets = {}
        for i, s in enumerate(scenarios):
            cat = s.category
            treecat = None
            names = [unicode(c.text(0)) for c in self.categories]
            if cat not in names:
                treecat = ObciTreeWidgetItem([QString(str(cat))], None)

                treecat.setText(0, QString(str(cat)))
                self.categories.append(treecat)
                self.scenarios.addTopLevelItem(treecat)
                treecat.setExpanded(False)
            else:
                treecat = self.categories[names.index(cat)]
            name = ObciTreeWidgetItem([s.name, s.status.status_name], s.uuid)
            self.exp_widgets[s.uuid] = name

            if s.status.status_name:
                name.setBackground(0, QColor(STATUS_COLORS[s.status.status_name]))
                name.setBackground(1, QColor(STATUS_COLORS[s.status.status_name]))
            treecat.addChild(name)
            name.setToolTip(0, QString(s.launch_file))
        self.scenarios.sortItems(0, Qt.AscendingOrder)

    def getScenarios(self):
        return self._scenarios

    def _setParams(self, experiment):
        expanded = self.exp_states[experiment.exp.uuid].expanded_peers

        self.parameters.clear()
        self._params = experiment
        experiment = experiment.exp
        for peer_id, peer in experiment.exp_config.peers.iteritems():
            st = experiment.status.peer_status(peer_id).status_name
            mch = str(peer.machine) or str(experiment.origin_machine)
            parent = QTreeWidgetItem([peer_id, st, mch])
            parent.setFirstColumnSpanned(True)

            parent.setBackground(0, QBrush(QColor(STATUS_COLORS[st])))
            parent.setBackground(1, QBrush(QColor(STATUS_COLORS[st])))
            parent.setBackground(2, QBrush(QColor(STATUS_COLORS[st])))
            parent.setToolTip(0, unicode(peer.path))

            self.parameters.addTopLevelItem(parent)

            if parent.text(0) in expanded:
                parent.setExpanded(True)
                parent.setToolTip(0, peer.path)

            params = experiment.parameters(peer_id, self.details_mode.currentText())
            for param, (value, src) in params.iteritems():
                val = unicode(value) #if not src else value + "  ["+src + ']'
                src = src if src else ''
                child = QTreeWidgetItem([param, val, src ])
                if src:
                    child.setDisabled(True)
                parent.addChild(child)

    def _getParams(self):
        uid = self._params.exp.exp_config.uuid
        old_uid = self._params.exp.old_uid
        if uid not in self.exp_states and old_uid not in self.exp_states:
            print "stale experiment descriptor"
            return self._params
        state = self.exp_states.get(uid, self.exp_states.get(old_uid, None))
        if state is None:
            print "_getParams - experiment not found"
            return self._params
        expanded = set()
        for i, peer in enumerate(self._params.exp.exp_config.peers.values()):
            parent = self.parameters.topLevelItem(i)
            if parent is None:
                print "*****   _getParams:   ", i, peer, "parent none"
                continue
            if parent.isExpanded(): expanded.add(parent.text(0))

            for j, param in enumerate(peer.config.local_params.keys()):
                child = parent.child(j)

        state.expanded_peers = expanded

        return self._params

    def _itemDoubleClicked(self, item, column):
        if item.parent() is None:
            uid = str(self.scenarios.currentItem().uuid)
            #self.exp_states[uid].exp.exp_config.peers[
            self.log_engine.show_log(item.text(0), uid)

    def _itemClicked(self, item, column):
        if item.columnCount() > 1 and column > 0:
            if not item.isDisabled():
                item.setFlags(item.flags() | Qt.ItemIsEditable)
            else:
                item.setFlags(Qt.ItemIsSelectable)
        else:
            if not item.isDisabled():
                item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable)
            else:
                item.setFlags(Qt.ItemIsSelectable)

    def _itemChanged(self, item, column):
        if item.parent() is None:
            return
        exp_state = self._params
        peer_id = unicode(item.parent().text(0))
        param = unicode(item.text(0))
        val = unicode(item.text(1))

        old_val = exp_state.exp.exp_config.param_value(peer_id, param)
        if old_val != unicode(item.text(1)):
            exp_state.exp.update_peer_param(peer_id, param, val)
            self._getParams()
            self._setParams(self._params)

    def _setInfo(self, scenario_item, column):
        if scenario_item is None:
            pass
        elif scenario_item.uuid not in self.exp_states:
            self._params = None
            self.parameters.clear()
            self.parameters_of.setTitle("Parameters")
        else:
            self.info.setText(self.exp_states[scenario_item.uuid].exp.info)
            if self._params:
                self._getParams()
            self._setParams(self.exp_states[scenario_item.uuid])
            self.parameters_of.setTitle("Parameters of " + self.exp_states[scenario_item.uuid].exp.name)
            self._store_update_info(self.exp_states[scenario_item.uuid].store_options)
            self.log_engine.show(self.exp_states[scenario_item.uuid])

        self._manage_actions(scenario_item)

    def _start(self):
        uid = str(self.scenarios.currentItem().uuid)
        if self.store_checkBox.isChecked():
            store_options = {u'save_file_name': unicode(self.store_file.text().toUtf8(), 'utf-8'),
                             u'save_file_path': unicode(self.store_dir.text().toUtf8(), 'utf-8'),
                             u'append_timestamp':  unicode(1 if self.store_ts_checkBox.isChecked() else 0),
                             u'store_locally': 1 if self.store_local_checkBox.isChecked() else 0
                             }
            self.exp_states[uid].store_options = store_options
        else:
            store_options = None
        self.log_engine.on_experiment_start(self.exp_states[uid].exp)
        print "obciLauncher - _start(), exp:  ", self.exp_states[uid].exp 
        self.start.emit(uid, store_options)

    def _stop(self):
        uid = str(self.scenarios.currentItem().uuid)
        self.exp_states[uid].log_model.stop_running()
        self.stop.emit(uid, self.exp_states[uid].store_options is not None)
        self.exp_states[uid].store_options = None

    def _saver_msg(self, killer_proc):
        print "GUI SAVER MSG"
        reply = QMessageBox.question(self, 'Signal saving',
            "Signal saving is taking quite some time. This is normal for longer EEG sessions.\n"
            "Continue saving?", 
            QMessageBox.Yes | QMessageBox.No, 
            QMessageBox.Yes)
        killer_proc.poll()
        if reply == QMessageBox.No and killer_proc.returncode is None:
            print "KILLING"
            killer_proc.kill()
            killer_proc.wait()
            print killer_proc.returncode, "^^^"


    def _update_store(self, state):
        if int(state):
            self.store_container.show()
        else:
            self.store_container.hide()


    def _reset(self):
        self.reset.emit(str(self.server_ip))

    def _connect_to_machine(self):
        self.machines_dialog.set_nearby_machines(self._nearby_machines,
                                    self.server_hostname, self.server_ip)
        if self.machines_dialog.exec_():
            self.stop_logs()
            new_ip, new_name = self.machines_dialog.chosen_machine
            self.engine_reinit.emit((new_ip, new_name))
            self.update_user_interface(None)

    def _save_current_as(self):
        filename = QFileDialog.getSaveFileName(self, "Save scenario as..,",
                            os.path.join(settings.DEFAULT_SCENARIO_DIR),
                            'INI files (*.ini)')
        if not filename:
            return
        filename = str(filename)
        if not filename.endswith('.ini'):
            filename += '.ini'

        uid = self.scenarios.currentItem().uuid
        exp = self.exp_states[uid].exp
        self.save_as.emit((filename, exp))

    def _choose_dir(self):
        curr = str(self.store_dir.text())
        if len(curr) == 0:
            curr = '~' 
        curr = os.path.expanduser(curr)
        if self.server_hostname != socket.gethostname():
            PyQt4.QtGui.QMessageBox.information(self, "This is a remote connection",
                                                "Enter the directory path by hand.")
            direc = curr
        else:
            direc = QFileDialog.getExistingDirectory(self, "Choose directory..,", curr)
                      
        if not direc:
            return

        self.store_dir.setText(direc)

    def _import(self):
        filename = QFileDialog.getOpenFileName(self, "Import scenario...",
                            os.path.join(settings.DEFAULT_SCENARIO_DIR),
                            'INI files (*.ini)')
        if not filename:
            return
        self.import_scenario.emit(filename)

    def _remove_from_sidebar(self):
        uid = self.scenarios.currentItem().uuid
        exp = self.exp_states[uid].exp
        self.remove_user_preset.emit(exp)

    def exp_destroyed(self, *args, **kwargs):
        print args, kwargs
        print "DESTROYED"

    def launcher_error(self, error_msg):
        if isinstance(error_msg.details, dict):
            str_details = str(json.dumps(error_msg.details, sort_keys=True, indent=4))
        else:
            str_details = str(error_msg.details)
        QMessageBox.critical(self, "Request error", "Error: " +str(error_msg.err_code) +\
                                    "\nDetails: " + str_details,
                                    QMessageBox.Ok)

    def update_user_interface(self, update_msg):
        user_imported_uuid = None
        if isinstance(update_msg, LauncherMessage):
            if update_msg.type == 'nearby_machines':
                self._nearby_machines = self.engine.nearby_machines()
            elif update_msg.type == '_user_set_scenario':
                user_imported_uuid = update_msg.uuid

        scenarios = self.engine.list_experiments()

        current_sc = self.scenarios.currentItem()
        curr_uid = current_sc.uuid if current_sc is not None else None
        curr_exp_state = self.exp_states.get(curr_uid, None)
        curr_exp = curr_exp_state.exp if curr_exp_state is not None else None
        if curr_exp is not None:
            if curr_exp in scenarios:
                curr_uid = curr_exp.exp_config.uuid
        else:
            print "not found", curr_uid, curr_exp
            current_sc = None

        new_states = {}
        for exp in scenarios:
            if exp.uuid not in self.exp_states:
                st = new_states[exp.uuid] = ExperimentGuiState(
                    self.engine.client,
                    exp, 
                    self.log_engine,
                    self.exp_states.get(exp.old_uid, None)
                    )
                st.exp.destroyed.connect(self.exp_destroyed)
            else:
                new_states[exp.uuid] = self.exp_states[exp.uuid]

        self.exp_states = new_states

        mode = self.details_mode.currentText()
        if mode not in MODES:
            mode = MODE_ADVANCED
        self.engine.details_mode = mode

        self.setScenarios(scenarios)

        if user_imported_uuid is not None:
            current_sc = self.exp_widgets.get(user_imported_uuid,
                            self._first_exp(self.scenarios))
        elif current_sc is None:
            current_sc = self._first_exp(self.scenarios)
        else:
            uid = curr_exp.exp_config.uuid
            old_uid = curr_exp.old_uid
            current_sc = self.exp_widgets.get(uid,
                            self.exp_widgets.get(old_uid, self._first_exp(self.scenarios)))

        self.scenarios.setCurrentItem(current_sc)
        self._manage_actions(current_sc)

    def _first_exp(self, scenarios):
        exp = None
        for index in range(scenarios.topLevelItemCount()):
            item = scenarios.topLevelItem(index)
            for ich in range(item.childCount()):
                exp = item.child(ich)
                break
            if exp:
                break
        return exp

    def _manage_actions(self, current_sc):
        if current_sc is None:
            self.start_button.setEnabled(False)
            self._store_set_enabled(False)
            self.stop_button.setEnabled(False)
            return
        if current_sc.uuid not in self.exp_states:
            self.start_button.setEnabled(False)
            self._store_set_enabled(False)
            self.stop_button.setEnabled(False)
            return
        current_exp = self.exp_states[current_sc.uuid].exp

        if current_exp.launcher_data is not None:
            self.start_button.setEnabled(False)
            self._store_set_enabled(False)
            self.stop_button.setEnabled(True)
            self.parameters.setEditTriggers(QAbstractItemView.NoEditTriggers)
        else:
            enable = (current_exp.status.status_name == READY_TO_LAUNCH)
            self.start_button.setEnabled(enable)
            self._store_set_enabled(enable and "amplifier" in current_exp.exp_config.peers)
            self.stop_button.setEnabled(False)
            self.parameters.setEditTriggers(QAbstractItemView.DoubleClicked |\
                                         QAbstractItemView.EditKeyPressed)


        launched = current_exp.status.status_name not in [LAUNCHING, RUNNING, FAILED, TERMINATED]
        self.actionOpen.setEnabled(True)
        self.actionSave_as.setEnabled(launched)
        if current_exp.preset_data is not None:
            remove_enabled = current_exp.preset_data["category"] == USER_CATEGORY
            self.actionRemove_from_sidebar.setEnabled(remove_enabled)
        self.actionExit.setEnabled(True)

        self.actionConnect.setEnabled(self._nearby_machines != {})

    def _store_set_enabled(self, enable):
        self.store_checkBox.setEnabled(enable)
        self.store_file.setEnabled(enable)
        self.store_dir.setEnabled(enable)
        self.store_dir_chooser.setEnabled(enable)
        self.store_ts_checkBox.setEnabled(enable)
        # self.store_local_checkBox.setEnabled(enable)

    def _store_update_info(self, store_options):
        if store_options is not None:
            self.store_file.setText(store_options[u'save_file_name'])
            self.store_dir.setText(store_options[u'save_file_path'])
            self.store_ts_checkBox.setChecked(int(store_options[u'append_timestamp']))
            # self.store_local_checkBox.setChecked(store_options[u'store_locally'])
            self.store_checkBox.setChecked(True)
            self.store_container.show()
        else:
            self.store_checkBox.setChecked(False)
            self.store_container.hide()
示例#4
0
class ObciLauncherWindow(QMainWindow, Ui_OBCILauncher):
    '''
    classdocs
    '''
    start = pyqtSignal(str, object)
    #amp_select = pyqtSignal(object)
    stop = pyqtSignal(str, bool)
    reset = pyqtSignal(str)

    save_as = pyqtSignal(object)
    remove_user_preset = pyqtSignal(object)
    import_scenario = pyqtSignal(str)

    engine_reinit = pyqtSignal(object)

    @log_crash
    def __init__(self):
        '''
        Constructor
        '''
        QMainWindow.__init__(self)

        self.logger = get_logger('launcherGUI', obci_peer=self)

        self.setupUi(self)
        self.basic_title = self.windowTitle()

        self.exp_states = {}
        self.exp_widgets = {}

        self.scenarioTab.setTabText(0, "Scenario")
        self.scenarioTab.setTabsClosable(True)
        self.log_engine = obci_log_engine.LogEngine(self.scenarioTab)

        self.engine_server_setup()

        self._nearby_machines = self.engine.nearby_machines()

        self.scenarios.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.scenarios.setColumnCount(2)
        self.scenarios.setHeaderLabels(["Scenario", "Status"])
        self.scenarios.setColumnWidth(0, 300)

        self.scenarios.itemClicked.connect(self._setInfo)
        self.scenarios.currentItemChanged.connect(self._setInfo)

        self.details_mode.currentIndexChanged.connect(self.update_user_interface)

        self.parameters.setHeaderLabels(["Name", 'Value', 'Info'])
        self.parameters.itemClicked.connect(self._itemClicked)
        self.parameters.itemChanged.connect(self._itemChanged)
        self.parameters.itemDoubleClicked.connect(self._itemDoubleClicked)
        self.parameters.setColumnWidth(0, 200)
        self.parameters.setColumnWidth(1, 400)

        self.machines_dialog = ConnectToMachine(self)
        self.select_amplifier_dialog = SelectAmplifierDialog(self)

        self.start_button.clicked.connect(self._start)
        self.stop_button.clicked.connect(self._stop)
        #self.reset_button.clicked.connect(self._reset)
        #self.ampselect_pushButton.clicked.connect(self._amp_select)
        self.store_container.hide()
        self.store_checkBox.stateChanged.connect(self._update_store)
        self.store_dir_chooser.clicked.connect(self._choose_dir)

        self._params = []
        self._scenarios = []

        self.details_mode.addItems(MODES)

        self.engine_reinit.connect(self.engine_server_setup)

        self.setupMenus()
        self.setupActions()
        self.update_user_interface(None)
        self.showMaximized()
        if os.environ.get('OBCI_INSTALL_DIR') != None:
            PyQt4.QtGui.QMessageBox.information(self, "Non standard OpenBCI directory",
                                                "OpenBCI is launched from local directory: "+os.environ.get('OBCI_INSTALL_DIR')+', to start default package version launch "obci_local_remove" in terminal.')


    @log_crash
    def closeEvent(self, e):
        progress = QProgressDialog(self,Qt.WindowStaysOnTopHint | Qt.FramelessWindowHint)
        progress.setLabelText("Finishing...")
        progress.setRange(0,5)
        progress.setCancelButton(None)
        progress.show()
        self.stop_logs()
        removepidfile('gui.pid')

        for i in range(5):
            time.sleep(0.4)
            progress.setValue(i+1)
        e.accept()

    def _crash_extra_tags(self, exception=None):
        return {'obci_part' : 'launcher'}

    def stop_logs(self):
        for i, st in self.exp_states.iteritems():
            st.log_model.stop_running()

    @log_crash
    def engine_server_setup(self, server_ip_host=None):
        server_ip, server_name = server_ip_host or (None, None)
        old_ip = None
        old_hostname = None
        if hasattr(self, 'server_ip'):
            old_ip = self.server_ip
            old_hostname = self.server_hostname

        self.server_ip = str(server_ip)
        self.server_hostname = str(server_name)
        ctx = None
        if server_ip is None:
            self.server_ip = '127.0.0.1'
            self.server_hostname = socket.gethostname()

        if hasattr(self, 'engine'):
            client = self.engine.client
            ctx = client.ctx
        else:
            self.engine = None

        if old_ip != self.server_ip and old_hostname != self.server_hostname:

            if self.engine is not None:
                self.engine.cleanup()
                self._disconnect_signals()

                # self.engine.deleteLater()
                sip.delete(self.engine)
                del self.engine
                self.engine = None


            client = obci_script.client_server_prep(server_ip=self.server_ip,
                                                    zmq_ctx=ctx,
                                                    start_srv=True)
            if client is None:
                self.quit()
            self.exp_states = {}
            self.engine = OBCILauncherEngine(client, self.server_ip)
            self._connect_signals()

        if self.server_ip and self.server_hostname != socket.gethostname():
            self.setWindowTitle(self.basic_title + ' - ' + 'remote connection ' + \
                                ' (' +self.server_ip + ' - ' + self.server_hostname + ')')
        else:
            self.setWindowTitle(self.basic_title + ' - ' + 'local connection (' +\
                                                self.server_hostname + ')')

        if old_ip is not None:
            self.engine.update_ui.emit(None)

    def _connect_signals(self):
        self.engine.update_ui.connect(self.update_user_interface)
        self.engine.update_ui.connect(self.log_engine.update_user_interface)
        self.engine.rq_error.connect(self.launcher_error)
        self.engine.saver_msg.connect(self._saver_msg)
        self.reset.connect(self.engine.reset_launcher)
        self.start.connect(self.engine.start_experiment)
        self.start.connect(self.log_engine.experiment_started)
        self.stop.connect(self.engine.stop_experiment)
        self.stop.connect(self.log_engine.experiment_stopped)
        self.save_as.connect(self.engine.save_scenario_as)
        self.import_scenario.connect(self.engine.import_scenario)
        self.remove_user_preset.connect(self.engine.remove_preset)

    def _disconnect_signals(self):
        self.engine.update_ui.disconnect()
        self.engine.rq_error.disconnect()
        self.engine.obci_state_change.disconnect()
        self.engine.saver_msg.disconnect()
        self.reset.disconnect()
        self.start.disconnect()
        self.stop.disconnect()
        self.save_as.disconnect()
        self.import_scenario.disconnect()
        self.remove_user_preset.disconnect()

    def setupMenus(self):
        self.menuMenu.addAction(self.actionOpen)
        self.menuMenu.addAction(self.actionSave_as)
        self.menuMenu.addAction(self.actionRemove_from_sidebar)
        self.menuMenu.addSeparator()
        self.menuMenu.addAction(self.actionConnect)
        self.menuMenu.addSeparator()
        self.menuMenu.addAction(self.actionExit)


    def setupActions(self):
        self.actionExit.triggered.connect(PyQt4.QtGui.qApp.quit)
        self.actionConnect.triggered.connect(self._connect_to_machine)
        self.actionSelectAmplifier.triggered.connect(self._select_amplifier)
        self.actionSave_as.triggered.connect(self._save_current_as)
        self.actionOpen.triggered.connect(self._import)
        self.actionRemove_from_sidebar.triggered.connect(self._remove_from_sidebar)

    @log_crash
    def setScenarios(self, scenarios):
        scenarios.sort(cmp=lambda a,b: str(a.name) > str(b.name))
        self._scenarios = scenarios
        self.scenarios.setSortingEnabled(True)
        self.scenarios.clear()

        self.categories = []
        self.exp_widgets = {}
        for i, s in enumerate(scenarios):
            cat = s.category
            treecat = None
            names = [unicode(c.text(0)) for c in self.categories]
            if cat not in names:
                treecat = ObciTreeWidgetItem([QString(str(cat))], None)

                treecat.setText(0, QString(str(cat)))
                self.categories.append(treecat)
                self.scenarios.addTopLevelItem(treecat)
                treecat.setExpanded(False)
            else:
                treecat = self.categories[names.index(cat)]
            name = ObciTreeWidgetItem([s.name, s.status.status_name], s.uuid)
            self.exp_widgets[s.uuid] = name

            if s.status.status_name:
                name.setBackground(0, QColor(STATUS_COLORS[s.status.status_name]))
                name.setBackground(1, QColor(STATUS_COLORS[s.status.status_name]))
            treecat.addChild(name)
            name.setToolTip(0, QString(s.launch_file))
        self.scenarios.sortItems(0, Qt.AscendingOrder)

    def getScenarios(self):
        return self._scenarios

    @log_crash
    def _setParams(self, experiment):
        expanded = self.exp_states[experiment.exp.uuid].expanded_peers

        self.parameters.clear()
        self._params = experiment
        experiment = experiment.exp
        print("********************")
        print("Machine/peer from current experiment "+str(experiment.uuid)+":")
        for peer_id, peer in experiment.exp_config.peers.iteritems():
            st = experiment.status.peer_status(peer_id).status_name
            mch = str(peer.machine)
            if mch not in self._nearby_machines.values():
                mch = self.server_hostname
            print mch, peer_id

            parent = QTreeWidgetItem([peer_id, st])
            parent.setFirstColumnSpanned(True)
            parent.setBackground(0, QBrush(QColor(STATUS_COLORS[st])))
            parent.setBackground(1, QBrush(QColor(STATUS_COLORS[st])))
            parent.setBackground(2, QBrush(QColor(STATUS_COLORS[st])))
            parent.setToolTip(0, unicode(peer.path))

            combo = QComboBox()
            combo.addItems(self._nearby_machines.values())
            if mch in self._nearby_machines.values():
                index = self._nearby_machines.values().index(mch)
                combo.setCurrentIndex(index)

            self.parameters.addTopLevelItem(parent)
            self.parameters.setItemWidget(parent, 2, combo)

            if peer_id == 'mx':
                combo.setDisabled(True)

            if parent is not None:
                combo.currentIndexChanged['QString'].connect(self.makeComboHandler(parent, 2))

            if parent.text(0) in expanded:
                parent.setExpanded(True)
                parent.setToolTip(0, peer.path)

            params = experiment.parameters(peer_id, self.details_mode.currentText())
            for param, (value, src) in params.iteritems():
                val = unicode(value) #if not src else value + "  ["+src + ']'
                src = src if src else ''
                child = QTreeWidgetItem([param, val, src])
                if src:
                    child.setDisabled(True)
                parent.addChild(child)
        print("********************")

    def makeComboHandler(self, item, column):
        def handler(string):
            self.parameters.itemChanged.emit(item, column)
        return handler

    @log_crash
    def _getParams(self):
        uid = self._params.exp.exp_config.uuid
        old_uid = self._params.exp.old_uid
        if uid not in self.exp_states and old_uid not in self.exp_states:
            print "stale experiment descriptor"
            return self._params
        state = self.exp_states.get(uid, self.exp_states.get(old_uid, None))
        if state is None:
            print "_getParams - experiment not found"
            return self._params
        expanded = set()
        for i, peer in enumerate(self._params.exp.exp_config.peers.values()):
            parent = self.parameters.topLevelItem(i)
            if parent is None:
                print "*****   _getParams:   ", i, peer, "parent none"
                continue
            if parent.isExpanded(): expanded.add(parent.text(0))

            for j, param in enumerate(peer.config.local_params.keys()):
                child = parent.child(j)

        state.expanded_peers = expanded
        return self._params

    def _itemDoubleClicked(self, item, column):
        if item.parent() is None and column != 2:
            uid = str(self.scenarios.currentItem().uuid)
            #self.exp_states[uid].exp.exp_config.peers[
            self.log_engine.show_log(item.text(0), uid)

    def _itemClicked(self, item, column):
        if item.columnCount() > 1 and column > 0:
            if not item.isDisabled():
                item.setFlags(item.flags() | Qt.ItemIsEditable)
            else:
                item.setFlags(Qt.ItemIsSelectable)
        else:
            if not item.isDisabled():
                item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable)
            else:
                item.setFlags(Qt.ItemIsSelectable)

    def _itemChanged(self, item, column):
        changed = False
        if item.parent() is None:
            peer_id = unicode(item.text(0))
            if column == 2:
                combo_box = self.parameters.itemWidget(item, column)
                machine = unicode(combo_box.currentText())
                old_ma = self._params.exp.exp_config.peer_machine(peer_id)
                if old_ma != machine:
                    self._params.exp.exp_config.update_peer_machine(peer_id, machine)
                    changed = True
        else:
            exp_state = self._params
            peer_id = unicode(item.parent().text(0))
            param = unicode(item.text(0))
            val = unicode(item.text(1))

            old_val = exp_state.exp.exp_config.param_value(peer_id, param)
            if old_val != unicode(item.text(1)):
                exp_state.exp.update_peer_param(peer_id, param, val)
                changed = True

        if changed:
            self._getParams()
            self._setParams(self._params)

    @log_crash
    def _setInfo(self, scenario_item, column):
        if scenario_item is None:
            pass
        elif scenario_item.uuid not in self.exp_states:
            self._params = None
            self.parameters.clear()
            self.parameters_of.setTitle("Parameters")
        else:
            self.info.setText(self.exp_states[scenario_item.uuid].exp.info)
            if self._params:
                self._getParams()
            self._setParams(self.exp_states[scenario_item.uuid])
            self.parameters_of.setTitle("Parameters of " + self.exp_states[scenario_item.uuid].exp.name)
            self._store_update_info(self.exp_states[scenario_item.uuid].store_options)
            self.log_engine.show(self.exp_states[scenario_item.uuid])

        self._manage_actions(scenario_item)

    # @log_crash
    def _start(self):
        uid = str(self.scenarios.currentItem().uuid)
        if self.store_checkBox.isChecked():
            store_options = {u'save_file_name': unicode(self.store_file.text().toUtf8(), 'utf-8'),
                             u'save_file_path': unicode(self.store_dir.text().toUtf8(), 'utf-8'),
                             u'append_timestamp':  unicode(1 if self.store_ts_checkBox.isChecked() else 0),
                             u'store_locally': 1 if self.store_local_checkBox.isChecked() else 0
                             }
            self.exp_states[uid].store_options = store_options
        else:
            store_options = None
        self.log_engine.on_experiment_start(self.exp_states[uid].exp)
        print "obciLauncher - _start(), exp:  ", self.exp_states[uid].exp
        self.start.emit(uid, store_options)

    def _stop(self):
        uid = str(self.scenarios.currentItem().uuid)
        print "obciLauncher._stop - begin uid: "+str(uid)
        state = self.exp_states[uid]
        if state.stopping:
            print "Warning!!! - tried to perform stop action again on the same experiment ................................... Ignore"
            return
        state.stopping = True
        state.log_model.stop_running()
        self.stop.emit(uid, state.store_options is not None)
        state.store_options = None
        print "obciLauncher._stop - end"

    def _amp_select(self):
        p = {'path': 'drivers/eeg/cpp_amplifiers/amplifier_tmsi.py'}
        d = {'usb_device': '/dev/tmsi0', 'driver_executable':'drivers/eeg/cpp_amplifiers/tmsi_amplifier'}
        uuid = str(self.scenarios.currentItem().uuid)
        si = self.scenarios.currentItem()
        #exp = self.exp_states[uuid].exp
        exp = self.exp_states[uuid]
        peers = exp.exp.exp_config.peers
        print(exp)
        print(dir(exp))
        print(exp.exp)
        print(dir(exp.exp))
        print(exp.exp.exp_config)
        print(dir(exp.exp.exp_config))
        cfg = exp.exp.exp_config
        #cfg.get_pee
        a = peers['amplifier']
        print(peers['amplifier'])
        print(dir(peers['amplifier']))
        print(a.public_params)
        peers['amplifier'].path = p['path']
        for k, v in d.iteritems():
            print k, v
            a.config.update_local_param(k, v)
        self._setParams(exp)

        print "amp select"
    @log_crash
    def _saver_msg(self, killer_proc):
        print "GUI SAVER MSG"
        reply = QMessageBox.question(self, 'Signal saving',
            "Signal saving is taking quite some time. This is normal for longer EEG sessions.\n"
            "Continue saving?",
            QMessageBox.Yes | QMessageBox.No,
            QMessageBox.Yes)
        killer_proc.poll()
        if reply == QMessageBox.No and killer_proc.returncode is None:
            print "KILLING"
            killer_proc.kill()
            killer_proc.wait()
            print killer_proc.returncode, "^^^"

    def _update_store(self, state):
        if int(state):
            self.store_container.show()
        else:
            self.store_container.hide()

    def _reset(self):
        self.reset.emit(str(self.server_ip))

    def _select_amplifier(self):
        
        if self.select_amplifier_dialog.exec_() is None:
            return
        path = self.select_amplifier_dialog.path
        params = self.select_amplifier_dialog.params

        uuid = str(self.scenarios.currentItem().uuid)
        exp = self.exp_states[uuid]
        peers = exp.exp.exp_config.peers
        amp = peers['amplifier']

        amp.path = path
        for k, v in params.iteritems():
            print k, v
            amp.config.update_local_param(k, v)
        #self._setParams(exp)
        self._setInfo(self.scenarios.currentItem(), 1)
        print "AMPLIFIER SELECTED"

    def _connect_to_machine(self):
        self.machines_dialog.set_nearby_machines(self._nearby_machines,
                                    self.server_hostname, self.server_ip)
        if self.machines_dialog.exec_():
            self.stop_logs()
            new_ip, new_name = self.machines_dialog.chosen_machine
            self.engine_reinit.emit((new_ip, new_name))
            self.update_user_interface(None)

    def _save_current_as(self):
        filename = QFileDialog.getSaveFileName(self, "Save scenario as..,",
                            os.path.join(settings.DEFAULT_SCENARIO_DIR),
                            'INI files (*.ini)')
        if not filename:
            return
        filename = str(filename)
        if not filename.endswith('.ini'):
            filename += '.ini'

        uid = self.scenarios.currentItem().uuid
        exp = self.exp_states[uid].exp
        self.save_as.emit((filename, exp))

    def _choose_dir(self):
        curr = str(self.store_dir.text())
        if len(curr) == 0:
            curr = '~'
        curr = os.path.expanduser(curr)
        if self.server_hostname != socket.gethostname():
            PyQt4.QtGui.QMessageBox.information(self, "This is a remote connection",
                                                "Enter the directory path by hand.")
            direc = curr
        else:
            direc = QFileDialog.getExistingDirectory(self, "Choose directory..,", curr)

        if not direc:
            return

        self.store_dir.setText(direc)

    def _import(self):
        filename = QFileDialog.getOpenFileName(self, "Import scenario...",
                            os.path.join(settings.DEFAULT_SCENARIO_DIR),
                            'INI files (*.ini)')
        if not filename:
            return
        self.import_scenario.emit(filename)

    def _remove_from_sidebar(self):
        uid = self.scenarios.currentItem().uuid
        exp = self.exp_states[uid].exp
        self.remove_user_preset.emit(exp)

    def exp_destroyed(self, *args, **kwargs):
        print args, kwargs
        print "DESTROYED"

    def launcher_error(self, error_msg):
        if isinstance(error_msg.details, dict):
            str_details = str(json.dumps(error_msg.details, sort_keys=True, indent=4))
        else:
            str_details = str(error_msg.details)
        QMessageBox.critical(self, "Request error", "Error: " +str(error_msg.err_code) +\
                                    "\nDetails: " + str_details,
                                    QMessageBox.Ok)

    @log_crash
    def update_user_interface(self, update_msg):
        user_imported_uuid = None
        if isinstance(update_msg, LauncherMessage):
            if update_msg.type == 'nearby_machines':
                self._nearby_machines = self.engine.nearby_machines()
            elif update_msg.type == '_user_set_scenario':
                user_imported_uuid = update_msg.uuid

        scenarios = self.engine.list_experiments()

        current_sc = self.scenarios.currentItem()
        curr_uid = current_sc.uuid if current_sc is not None else None
        curr_exp_state = self.exp_states.get(curr_uid, None)
        curr_exp = curr_exp_state.exp if curr_exp_state is not None else None
        if curr_exp is not None:
            if curr_exp in scenarios:
                curr_uid = curr_exp.exp_config.uuid
        else:
            print "not found", curr_uid, curr_exp
            current_sc = None

        new_states = {}
        for exp in scenarios:
            if exp.uuid not in self.exp_states:
                st = new_states[exp.uuid] = ExperimentGuiState(
                    self.engine.client,
                    exp,
                    self.log_engine,
                    self.exp_states.get(exp.old_uid, None)
                    )
                st.exp.destroyed.connect(self.exp_destroyed)
            else:
                new_states[exp.uuid] = self.exp_states[exp.uuid]

        self.exp_states = new_states

        mode = self.details_mode.currentText()
        if mode not in MODES:
            mode = MODE_ADVANCED
        self.engine.details_mode = mode

        self.setScenarios(scenarios)

        if user_imported_uuid is not None:
            current_sc = self.exp_widgets.get(user_imported_uuid,
                            self._first_exp(self.scenarios))
        elif current_sc is None:
            current_sc = self._first_exp(self.scenarios)
        else:
            uid = curr_exp.exp_config.uuid
            old_uid = curr_exp.old_uid
            current_sc = self.exp_widgets.get(uid,
                            self.exp_widgets.get(old_uid, self._first_exp(self.scenarios)))

        self.scenarios.setCurrentItem(current_sc)
        self._manage_actions(current_sc)

    def _first_exp(self, scenarios):
        exp = None
        for index in range(scenarios.topLevelItemCount()):
            item = scenarios.topLevelItem(index)
            for ich in range(item.childCount()):
                exp = item.child(ich)
                break
            if exp:
                break
        return exp

    def _manage_actions(self, current_sc):
        if current_sc is None:
            self.start_button.setEnabled(False)
            self._store_set_enabled(False)
            self.actionSelectAmplifier.setEnabled(False)
            self.stop_button.setEnabled(False)
            return
        if current_sc.uuid not in self.exp_states:
            self.start_button.setEnabled(False)
            self._store_set_enabled(False)
            self.actionSelectAmplifier.setEnabled(False)
            self.stop_button.setEnabled(False)
            return
        current_exp = self.exp_states[current_sc.uuid].exp

        if current_exp.launcher_data is not None:
            self.start_button.setEnabled(False)
            self._store_set_enabled(False)
            self.actionSelectAmplifier.setEnabled(False)
            self.stop_button.setEnabled(True)
            self.parameters.setEditTriggers(QAbstractItemView.NoEditTriggers)
        else:
            enable = (current_exp.status.status_name == READY_TO_LAUNCH)
            self.start_button.setEnabled(enable)
            is_amp = enable and "amplifier" in current_exp.exp_config.peers
            self._store_set_enabled(is_amp)
            self.actionSelectAmplifier.setEnabled(is_amp)
            self.stop_button.setEnabled(False)
            self.parameters.setEditTriggers(QAbstractItemView.DoubleClicked |\
                                         QAbstractItemView.EditKeyPressed)


        launched = current_exp.status.status_name not in [LAUNCHING, RUNNING, FAILED, TERMINATED]
        self.actionOpen.setEnabled(True)
        self.actionSave_as.setEnabled(launched)
        if current_exp.preset_data is not None:
            remove_enabled = current_exp.preset_data["category"] == USER_CATEGORY
            self.actionRemove_from_sidebar.setEnabled(remove_enabled)
        self.actionExit.setEnabled(True)

        self.actionConnect.setEnabled(self._nearby_machines != {})

    def _store_set_enabled(self, enable):
        self.store_checkBox.setEnabled(enable)
        self.store_file.setEnabled(enable)
        self.store_dir.setEnabled(enable)
        self.store_dir_chooser.setEnabled(enable)
        self.store_ts_checkBox.setEnabled(enable)
        # self.store_local_checkBox.setEnabled(enable)
        #self.ampselect_pushButton.setEnabled(enable)

    def _store_update_info(self, store_options):
        if store_options is not None:
            self.store_file.setText(store_options[u'save_file_name'])
            self.store_dir.setText(store_options[u'save_file_path'])
            self.store_ts_checkBox.setChecked(int(store_options[u'append_timestamp']))
            # self.store_local_checkBox.setChecked(store_options[u'store_locally'])
            self.store_checkBox.setChecked(True)
            self.store_container.show()
        else:
            self.store_checkBox.setChecked(False)
            self.store_container.hide()