Ejemplo n.º 1
0
    def __init__(self):
        self.emitter = McMessageEmitter()
        self.view = McView()
        self.state = McGuiState(self.emitter)
    
        self.connectCallbacks()
        self.initDynamicView()
        
        self.state.init()
        
        # Print MCCODE version info in main window
        cmd = mccode_config.configuration["MCCODE"] + ' -v '
        process = subprocess.Popen(cmd, 
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE,
                                   shell=True)

        (stdoutdata, stderrdata) = process.communicate()
        self.emitter.message(stdoutdata.rstrip('\n'))
        self.emitter.message(stderrdata.rstrip('\n'))
        # Print MCCODE revision data if these exist
        comprev = os.path.join(mccode_config.configuration["MCCODE_LIB_DIR"], "revision")
        if os.path.exists(comprev):
            self.emitter.message(open(comprev).read())
        
        
        # load instrument file from command line pars
        for a in sys.argv:
            if os.path.isfile(a):
                if os.path.splitext(a)[1] == '.instr':
                    if self.state.getInstrumentFile() == '':
                        self.state.loadInstrument(a)
        
        self.view.showMainWindow()
Ejemplo n.º 2
0
    def __init__(self):
        self.emitter = McMessageEmitter()
        self.view = McView()
        self.state = McGuiState(self.emitter)
    
        self.connectCallbacks()
        self.initDynamicView()
        
        self.state.init()
        
        # Print MCCODE version info in main window
        cmd = mccode_config.configuration["MCCODE"] + ' -v '
        process = subprocess.Popen(cmd, 
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE,
                                   shell=True,
                                   universal_newlines=True)

        (stdoutdata, stderrdata) = process.communicate()
        self.emitter.message(stdoutdata.rstrip('\n'))
        self.emitter.message(stderrdata.rstrip('\n'))
        # Print MCCODE revision data if these exist
        comprev = os.path.join(mccode_config.configuration["MCCODE_LIB_DIR"], "revision")
        if os.path.exists(comprev):
            self.emitter.message(open(comprev).read(), gui=True)
        
        
        # load instrument file from command line pars, skipping scriptfile
        for a in sys.argv:
            if os.path.isfile(a):
                if not os.path.splitext(a)[1] == '.py':
                    if self.state.getInstrumentFile() == '':
                        self.state.loadInstrument(a)
                        
        # Shouldn't really be necessary, but otherwise App menu is inactive on macOS
        # (was initially put in message/status update mechanism, but that caused other side-effects, see
        #  https://github.com/McStasMcXtrace/McCode/issues/570 )
        QtWidgets.QApplication.processEvents()
        self.view.showMainWindow()
Ejemplo n.º 3
0
 def __init__(self):
     self.emitter = McMessageEmitter()
     self.view = McView()
     self.state = McGuiState(self.emitter)
 
     self.connectCallbacks()
     self.initDynamicView()
     
     self.state.init()
     
     # load instrument file from command line pars
     for a in sys.argv:
         if os.path.isfile(a):
             if os.path.splitext(a)[1] == '.instr':
                 if self.state.getInstrumentFile() == '':
                     self.state.loadInstrument(a)
     
     self.view.showMainWindow()
Ejemplo n.º 4
0
class McGuiAppController():
    def __init__(self):
        self.emitter = McMessageEmitter()
        self.view = McView()
        self.state = McGuiState(self.emitter)
    
        self.connectCallbacks()
        self.initDynamicView()
        
        self.state.init()
        
        # load instrument file from command line pars
        for a in sys.argv:
            if os.path.isfile(a):
                if os.path.splitext(a)[1] == '.instr':
                    if self.state.getInstrumentFile() == '':
                        self.state.loadInstrument(a)
        
        self.view.showMainWindow()
    
    def initDynamicView(self):
        # load installed mcstas instruments:
        # construct args = [site, instr_fullpath[], instr_path_lst[]]
        args = []
        files_instr, files_comp = McGuiUtils.getInstrumentAndComponentFiles(mccode_config.configuration["MCCODE_LIB_DIR"])
        
        # temporary list consisting of instrument files with site names: 
        files_instr_and_site = []
        for f in files_instr:
            files_instr_and_site.append([f, McGuiUtils.getInstrumentSite(f)])
        
        # order instrument files by site:
        sites = {s for s in map(lambda f: f[1], files_instr_and_site)}
        for s in sites:
            # extract instruments file paths of this site
            instr_path_lst = map(lambda f: f[0], filter(lambda f: f[1] in [s], files_instr_and_site))
            # sort instrument of this site by file name
            instr_path_lst.sort(key=lambda instrpath: os.path.splitext(os.path.basename(instrpath))[0])
            # extract file names
            instr_name_lst = map(lambda instrpath: os.path.splitext(os.path.basename(instrpath))[0], instr_path_lst)
            arg = []
            arg.append(s)
            arg.append(instr_name_lst)
            arg.append(instr_path_lst)
            args.append(arg)
        
        # sort sites 
        args.sort(key=lambda arg: arg[0])    
        
        # hand on for menu generation
        self.view.initMainWindowDynamicElements(args, self.handleNewFromTemplate)
        
        # load installed mcstas components:
        # args - [category, comp_names[], comp_parsers[]]
        args = []
        categories = {0 : 'Source', 1 : 'Optics', 2 : 'Sample', 3 : 'Monitor', 4 : 'Misc', 5 : 'Contrib', 6 : 'Obsolete'}
        dirnames = {0 : 'sources', 1 : 'optics', 2 : 'samples', 3 : 'monitors', 4 : 'misc', 5 : 'contrib', 6 : 'obsolete'}
        i = 0
        while i < 7:
            arg = [] # arg - category, comp_names[], comp_parsers[]
            compnames = []
            parsers = []
            
            for f in files_comp:
                if re.search(dirnames[i], f):
                    compnames.append(os.path.splitext(os.path.basename(f))[0]) # get filename without extension - this is the component name
                    parsers.append(McComponentParser(f)) # append a parser, for ease of parsing on-the-fly
            
            arg.append(categories[i])
            arg.append(compnames)
            arg.append(parsers)
            args.append(arg)
            
            i += 1
        
        # sort components in each category (using Python default string sort on filename)
        for arg in args:
            arg[1].sort()
            arg[2].sort(key=lambda parser: os.path.splitext(os.path.basename(parser.file))[0])
        
        # hand on for menu generation
        self.view.initCodeEditorComponentMenu(args)

    ''' UI callbacks
    '''
    def handleRunOrInterruptSim(self):
        if self.state.isSimRunning():
            self.state.interrupt()
        else:
            self.emitter.status("Getting instrument params...")
            
            instr_params = self.state.getInstrParams()
            fixed_params, new_instr_params = self.view.showStartSimDialog(instr_params)
            
            self.emitter.status("")
            
            if fixed_params != None:
                self.state.run(fixed_params, new_instr_params)
        
    def handleConfiguration(self):
        self.view.showConfigDialog()
        
    def handleChangeWorkDir(self):
        workDir = self.view.showChangeWorkDirDlg(self.state.getWorkDir())
        if workDir:
            self.state.setWorkDir(workDir)
    
    def handleExit(self):
        sys.exit()
    
    def handlePlotResults(self):
        self.emitter.status('')
        resultdir = self.state.getDataDir()
        cmd = mccode_config.configuration["MCPLOT"] + ' ' + resultdir
        subprocess.Popen(cmd, 
                         stdout=subprocess.PIPE,
                         stderr=subprocess.STDOUT,
                         shell=True)
        self.emitter.message(cmd, gui_msg=True)
        self.emitter.message('', gui_msg=True)
        
    def handleHelpWeb(self):
        # open the mcstas homepage
        mcurl = 'http://www.mcstas.org'
        webbrowser.open_new_tab(mcurl)
    
    def handleHelpPdf(self):
        # TODO: make it cross-platform (e.g. os.path.realpath(__file__) +  ..)
        mcman = os.path.join(mccode_config.configuration["MCCODE_LIB_DIR"], "doc", "manuals", "mcstas-manual.pdf")
        webbrowser.open_new_tab(mcman)
    
    def handleHelpPdfComponents(self):
        # TODO: make it cross-platform (e.g. os.path.realpath(__file__) +  ...)
        mcman = os.path.join(mccode_config.configuration["MCCODE_LIB_DIR"], "doc", "manuals", "mcstas-components.pdf")
        webbrowser.open_new_tab(mcman)
    
    def handleHelpAbout(self):
        # get mcstas version using 'mcstas/mcxtrace -v'
        process = subprocess.Popen(mccode_config.configuration["MCCODE"] +' -v', 
                                   stdout=subprocess.PIPE, 
                                   stderr=subprocess.STDOUT,
                                   shell=True)
        # synchronous
        (stdoutdata, stderrdata) = process.communicate()
        
        self.view.showAboutBox(stdoutdata)
        
    def handleEditInstrument(self):
        instr = self.state.getInstrumentFile()
        self.view.showCodeEditorWindow(instr)
        self.emitter.status("Editing instrument: " + os.path.basename(str(instr)))
        
    def handleCloseInstrument(self):
        if self.view.closeCodeEditorWindow():
            self.state.unloadInstrument()
            self.emitter.message("Instrument closed", gui_msg=True)
            self.emitter.status("Instrument closed")
    
    def handleSaveInstrument(self, text):
        result = self.state.saveInstrumentIfFileExists(text)
        if result:
            self.view.ew.assumeDataSaved()
            self.emitter.status("Instrument saved: " + os.path.basename(self.state.getInstrumentFile()))
            
    def handleSaveAs(self):
        oldinstr = self.state.getInstrumentFile()
        if oldinstr != '':
            newinstr = self.view.showSaveAsDialog(oldinstr)
        
        if newinstr != '':
            self.state.unloadInstrument()
            text = McGuiUtils.getFileContents(oldinstr)
            created_instr = McGuiUtils.saveInstrumentFile(newinstr, text)
            if created_instr != '':
                self.state.loadInstrument(created_instr)
                self.emitter.status("Instrument saved as: " + newinstr)
    
    def handleNewInstrument(self):
        new_instr_req = self.view.showNewInstrDialog(self.state.getWorkDir())
        if new_instr_req != '':
            template_text_header = open(os.path.join(mccode_config.configuration["MCCODE_LIB_DIR"], "examples", "template_header_simple.instr")).read()
            template_text_body = open(os.path.join(mccode_config.configuration["MCCODE_LIB_DIR"], "examples", "template_body_simple.instr")).read()
            new_instr = McGuiUtils.saveInstrumentFile(new_instr_req, template_text_header + template_text_body)
            if new_instr != '':
                self.state.unloadInstrument()
                self.state.loadInstrument(new_instr)
                self.view.showCodeEditorWindow(new_instr)
                self.emitter.status("Editing new instrument: " + os.path.basename(str(new_instr)))
        
    def handleNewFromTemplate(self, instr_templ=''):
        new_instr_req = self.view.showNewInstrFromTemplateDialog(os.path.join(self.state.getWorkDir(), os.path.basename(str(instr_templ))))
        if new_instr_req != '':
            text = McGuiUtils.getFileContents(instr_templ)
            new_instr = McGuiUtils.saveInstrumentFile(new_instr_req, text)
            self.state.loadInstrument(new_instr)
            self.emitter.status("Instrument created: " + os.path.basename(str(new_instr)))
        
    def handleOpenInstrument(self):
        instr = self.view.showOpenInstrumentDlg(self.state.getWorkDir())
        if instr:
            if self.view.closeCodeEditorWindow():
                self.state.unloadInstrument()
                self.state.loadInstrument(instr)
                self.emitter.message("Instrument opened: " + os.path.basename(str(instr)), gui_msg=True)
                self.emitter.status("Instrument: " + os.path.basename(str(instr)))
        
    def handleMcdoc(self):
        subprocess.Popen('mcdoc', shell=True)
    
    ''' Connect UI and state callbacks 
    '''
    def connectCallbacks(self):        
        # connect UI widget signals to our handlers/logics
        # NOTICE: This explicit widget access is an exception - all widget access is otherwise handled by the view classes
        
        mwui = self.view.mw.ui
        mwui.actionQuit.triggered.connect(self.handleExit)
        mwui.actionOpen_instrument.triggered.connect(self.handleOpenInstrument)
        mwui.actionClose_Instrument.triggered.connect(self.handleCloseInstrument)
        mwui.actionEdit_Instrument.triggered.connect(self.handleEditInstrument)
        mwui.actionSave_As.triggered.connect(self.handleSaveAs)
        mwui.actionNew_Instrument.triggered.connect(self.handleNewInstrument)
        mwui.actionConfiguration.triggered.connect(self.handleConfiguration)
        
        mwui.btnRun.clicked.connect(self.handleRunOrInterruptSim)
        mwui.btnPlot.clicked.connect(self.handlePlotResults)
        mwui.btnEdit.clicked.connect(self.handleEditInstrument)
        mwui.btnOpenInstrument.clicked.connect(self.handleOpenInstrument)
        
        mwui.actionCompile_Instrument.triggered.connect(self.state.compile)
        mwui.actionCompile_Instrument_MPI.triggered.connect(lambda: self.state.compile(mpi=True))
        mwui.actionRun_Simulation.triggered.connect(self.handleRunOrInterruptSim)
        mwui.actionPlot.triggered.connect(self.handlePlotResults)
        
        mwui.actionMcdoc.triggered.connect(self.handleMcdoc)
        mwui.actionMcstas_Web_Page.triggered.connect(self.handleHelpWeb)
        mwui.actionMcstas_User_Manual.triggered.connect(self.handleHelpPdf)
        mwui.actionMcstas_Component_Manual.triggered.connect(self.handleHelpPdfComponents)
        mwui.actionAbout.triggered.connect(self.handleHelpAbout)
        
        ew = self.view.ew
        ew.saveRequest.connect(self.handleSaveInstrument)
        
        st = self.state
        st.simStateUpdated.connect(self.view.updateSimState)
        st.instrumentUpdated.connect(self.view.updateInstrument)
        
        emitter = self.emitter
        emitter.statusUpdate.connect(self.view.updateStatus)
        emitter.logMessageUpdate.connect(self.view.updateLog)
Ejemplo n.º 5
0
class McGuiAppController():
    def __init__(self):
        self.emitter = McMessageEmitter()
        self.view = McView()
        self.state = McGuiState(self.emitter)
    
        self.connectCallbacks()
        self.initDynamicView()
        
        self.state.init()
        
        # Print MCCODE version info in main window
        cmd = mccode_config.configuration["MCCODE"] + ' -v '
        process = subprocess.Popen(cmd, 
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE,
                                   shell=True,
                                   universal_newlines=True)

        (stdoutdata, stderrdata) = process.communicate()
        self.emitter.message(stdoutdata.rstrip('\n'))
        self.emitter.message(stderrdata.rstrip('\n'))
        # Print MCCODE revision data if these exist
        comprev = os.path.join(mccode_config.configuration["MCCODE_LIB_DIR"], "revision")
        if os.path.exists(comprev):
            self.emitter.message(open(comprev).read(), gui=True)
        
        
        # load instrument file from command line pars, skipping scriptfile
        for a in sys.argv:
            if os.path.isfile(a):
                if not os.path.splitext(a)[1] == '.py':
                    if self.state.getInstrumentFile() == '':
                        self.state.loadInstrument(a)
                        
        # Shouldn't really be necessary, but otherwise App menu is inactive on macOS
        # (was initially put in message/status update mechanism, but that caused other side-effects, see
        #  https://github.com/McStasMcXtrace/McCode/issues/570 )
        QtWidgets.QApplication.processEvents()
        self.view.showMainWindow()
        
    def initDynamicView(self):
        # load installed mcstas instruments:
        # construct args = [site, instr_fullpath[], instr_path_lst[]]
        args = []
        files_instr, files_comp = get_instr_comp_files(mccode_config.configuration["MCCODE_LIB_DIR"])
        
        # temporary list consisting of instrument files with site names: 
        files_instr_and_site = []
        for f in files_instr:
            files_instr_and_site.append([f, get_instr_site(f)])
        
        # order instrument files by site:
        sites = {s for s in list(map(lambda f: f[1], files_instr_and_site))}
        for s in sites:
            # extract instruments file paths of this site
            instr_path_lst = list(map(lambda f: f[0], filter(lambda f: f[1] in [s], files_instr_and_site)))
            # sort instrument of this site by file name
            instr_path_lst.sort(key=lambda instrpath: os.path.splitext(os.path.basename(instrpath))[0])
            # extract file names
            instr_name_lst = list(map(lambda instrpath: os.path.splitext(os.path.basename(instrpath))[0], instr_path_lst))
            arg = []
            arg.append(s)
            arg.append(instr_name_lst)
            arg.append(instr_path_lst)
            args.append(arg)
        
        # sort sites 
        args.sort(key=lambda arg: arg[0])    
        
        # hand on for menu generation
        self.view.initMainWindowDynamicElements(args, self.handleNewFromTemplate)
        
        # load installed mcstas components:
        # args - [category, comp_names[], comp_parsers[]]
        args = []
        categories = {0 : 'Source', 1 : 'Optics', 2 : 'Sample', 3 : 'Monitor', 4 : 'Misc', 5 : 'Contrib', 6: 'Union', 7 : 'Obsolete'}
        dirnames = {0 : 'sources', 1 : 'optics', 2 : 'samples', 3 : 'monitors', 4 : 'misc', 5 : 'contrib', 6: 'contrib/union', 7 : 'obsolete'}
        i = 0
        while i < 8:
            arg = [] # arg - category, comp_names[], comp_parsers[]
            compnames = []
            parsers = []
            
            for f in files_comp:
                if i==6:
                    if re.search(dirnames[i], os.path.dirname(f)):
                        compnames.append(os.path.splitext(os.path.basename(f))[0]) # get filename without extension - this is the component name
                        parsers.append(ComponentParser(f)) # append a parser, for ease of parsing on-the-fly
                else:
                    if re.search(dirnames[i], os.path.basename(os.path.dirname(f))):
                        compnames.append(os.path.splitext(os.path.basename(f))[0]) # get filename without extension - this is the component name
                        parsers.append(ComponentParser(f)) # append a parser, for ease of parsing on-the-fly
            
            arg.append(categories[i])
            arg.append(compnames)
            arg.append(parsers)
            args.append(arg)
            
            i += 1
        
        # sort components in each category (using Python default string sort on filename)
        for arg in args:
            arg[1].sort()
            arg[2].sort(key=lambda parser: os.path.splitext(os.path.basename(parser.file))[0])
        
        # hand on for menu generation
        self.view.initCodeEditorComponentMenu(args)

    ''' UI callbacks
    '''
    def handleRunOrInterruptSim(self):
        if self.state.isSimRunning():
            self.state.interrupt()
        else:
            # ensure auto-save
            self.view.ew.save()

            self.emitter.status("Getting instrument params...")
            QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor)
            self.view.disableRunBtn()
            try:
                class ThreadInfoHandler():
                    def __init__(self, forwardmsg=None):
                        self.stdout_lst = []
                        self.finished = False
                        self.forwardmsg = forwardmsg
                    def stdout(self, msg):
                        self.stdout_lst.append(msg)
                        if self.forwardmsg:
                            self.forwardmsg(msg)
                    def finish(self):
                        self.finished = True
                def msg(msg, em=self.emitter):
                    em.message(msg)
                handler = ThreadInfoHandler(lambda msg, em=self.emitter: em.message(msg))

                somethread = McRunQThread()
                somethread.cmd = mccode_config.configuration["MCRUN"] + ' ' + os.path.basename(self.state.getInstrumentFile()) + " --info"
                somethread.cwd = os.path.dirname(self.state.getInstrumentFile())
                somethread.finished.connect(handler.finish)
                somethread.thread_exception.connect(handleExceptionMsg)
                somethread.error.connect(lambda msg: self.emitter.message(msg, err_msg=True))
                somethread.message.connect(handler.stdout)
                somethread.start()

                while not handler.finished:
                    time.sleep(0.2)
                    QtWidgets.QApplication.processEvents()

                params = []
                for l in handler.stdout_lst:
                    if 'Param:' in l:
                        s = l.split()
                        s[0]=""
                        s = ' '.join(s)
                        s = s.split('=')
                        params.append(s)

                instr_params = params

            except:
                self.emitter.status("Instrument compile error")
                raise
            finally:
                QtWidgets.QApplication.restoreOverrideCursor()
                self.view.enableRunBtn()

            def get_compnames(text):
                ''' return a list of compnames from an instrument definition code text '''
                comps = []
                pat = 'COMPONENT[ ]+([\w0-9]+)[ ]*='
                for l in text.splitlines():
                    m = re.search(pat, l)
                    if m:
                        comps.append(m.group(1))
                return comps

            comps = get_compnames(text=open(self.state.getInstrumentFile(), 'rb').read().decode())
            _a, mcplots, mcdisplays = mccode_config.get_options()
            fixed_params, new_instr_params, inspect, mcdisplay, autoplotter = self.view.showStartSimDialog(
                instr_params, comps, mcdisplays, mcplots)

            if mcdisplay != None:
                mccode_config.configuration["MCDISPLAY"] = mcdisplay
            if autoplotter != None:
                mccode_config.configuration["MCPLOT"] = autoplotter

            self.emitter.status("")

            if fixed_params != None:
                self.state.run(fixed_params, new_instr_params, inspect)

    def handleConfiguration(self):
        self.view.showConfigDialog()
        
    def handleChangeWorkDir(self):
        workDir = self.view.showChangeWorkDirDlg(self.state.getWorkDir())
        if workDir:
            self.state.setWorkDir(workDir)
    
    def handleExit(self):
        if self.view.closeCodeEditorWindow():
            sys.exit()
    
    def handlePlotResults(self):
        self.emitter.status('')
        resultdir = self.state.getDataDir()
        cmd = mccode_config.configuration["MCPLOT"] + ' ' + resultdir
        cwd = os.path.dirname(self.state.getInstrumentFile())

        self._runthread = McRunQThread()
        self._runthread.cmd = cmd
        self._runthread.cwd = cwd
        self._runthread.finished.connect(lambda: None)
        self._runthread.thread_exception.connect(handleExceptionMsg)
        self._runthread.error.connect(lambda msg: self.emitter.message(msg, err_msg=True))
        self._runthread.message.connect(lambda msg: self.emitter.message(msg))
        self._runthread.start()
        
        self.emitter.message(cmd, gui=True)
        self.emitter.status('Running plotter ...')

    def handlePlotOtherResults(self):
        self.emitter.status('')
        resultdir = self.view.showOpenPlotDirDlg(os.getcwd())
        if resultdir != "":
            cmd = mccode_config.configuration["MCPLOT"] + ' ' + resultdir
            cwd = os.path.dirname(os.path.dirname(resultdir))
            self._runthread = McRunQThread()
            self._runthread.cmd = cmd
            self._runthread.cwd = cwd
            self._runthread.finished.connect(lambda: None)
            self._runthread.terminated.connect(lambda: None)
            self._runthread.thread_exception.connect(handleExceptionMsg)
            self._runthread.error.connect(lambda msg: self.emitter.message(msg, err_msg=True))
            self._runthread.message.connect(lambda msg: self.emitter.message(msg))
            self._runthread.start()
        
            self.emitter.message(cmd, gui=True)
            self.emitter.status('Running plotter ...')        
    
    def handleMcDisplayWeb(self):
        self.emitter.status('Running mcdisplay-webgl...')
        try:
            cmd = 'mcdisplay-webgl --default --no-output-files -n100 ' + os.path.basename(self.state.getInstrumentFile()) + '&'
            self.emitter.message(cmd, gui=True)
            self.emitter.message('', gui=True)
            
            def messg(s): self.emitter.message(s)
            def messg_err(s): self.emitter.message(s, err_msg=True)
            utils.run_subtool_to_completion(cmd, stdout_cb=messg, stderr_cb=messg_err)
        finally:
            self.emitter.status('')
    
    def handleMcDisplay2D(self):
        self.emitter.status('Running mcdisplay-webgl...')
        try:
            cmd = 'mcdisplay-pyqtgraph --default --no-output-files -n100 ' + os.path.basename(self.state.getInstrumentFile()) + '&'
            self.emitter.message(cmd, gui=True)
            self.emitter.message('', gui=True)
            
            def messg(s): self.emitter.message(s)
            def messg_err(s): self.emitter.message(s, err_msg=True)
            utils.run_subtool_to_completion(cmd, stdout_cb=messg, stderr_cb=messg_err)
        finally:
            self.emitter.status('')
    
    def handleHelpWeb(self):
        # open the mcstas homepage
        mcurl = 'http://www.'+mccode_config.configuration["MCCODE"]+'.org'
        webbrowser.open_new_tab(mcurl)
    
    def handleHelpPdf(self):
        # TODO: make it cross-platform (e.g. os.path.realpath(__file__) +  ..)
        mcman = os.path.join(mccode_config.configuration["MCCODE_LIB_DIR"], "doc", "manuals", mccode_config.configuration["MCCODE"]+"-manual.pdf")
        webbrowser.open_new_tab(mcman)
    
    def handleHelpPdfComponents(self):
        # TODO: make it cross-platform (e.g. os.path.realpath(__file__) +  ...)
        mcman = os.path.join(mccode_config.configuration["MCCODE_LIB_DIR"], "doc", "manuals", mccode_config.configuration["MCCODE"]+"-components.pdf")
        webbrowser.open_new_tab(mcman)
    
    def handleHelpAbout(self):
        # get mcstas version using 'mcstas/mcxtrace -v'
        process = subprocess.Popen(mccode_config.configuration["MCCODE"] +' -v', 
                                   stdout=subprocess.PIPE, 
                                   stderr=subprocess.STDOUT,
                                   shell=True,
                                   universal_newlines=True)
        # synchronous
        (stdoutdata, stderrdata) = process.communicate()
        
        self.view.showAboutBox(stdoutdata)
    
    def handleEditInstrument(self):
        instr = self.state.getInstrumentFile()
        self.view.showCodeEditorWindow(instr)
        self.emitter.status("Editing instrument: " + os.path.basename(str(instr)))
    
    def handleCloseInstrument(self):
        if self.view.closeCodeEditorWindow():
            self.state.unloadInstrument()
            self.emitter.message("Instrument closed", gui=True)
            self.emitter.status("Instrument closed")
    
    def handleSaveInstrument(self, text):
        result = self.state.saveInstrumentIfFileExists(text)
        if result:
            self.view.ew.assumeDataSaved()
            self.emitter.status("Instrument saved: " + os.path.basename(self.state.getInstrumentFile()))
    
    def displayNotSavedWhitespaceError(self, can_throw_func, raise_err=False):
        try:
            return can_throw_func()
        except Exception as e:
            self.emitter.status("Instrument not saved")
            self.view.showErrorDialogue("Error: Instrument not saved", "Instrument files or paths should not contain white-spaces.")
            if raise_err:
                raise e
            return False
    
    def handleSaveAs(self):
        oldinstr = self.state.getInstrumentFile()
        if oldinstr != '':
            newinstr = self.view.showSaveAsDialog(oldinstr)
            if self.displayNotSavedWhitespaceError(lambda: self.state.checkInstrFileCandidate(newinstr))==False:
                return
        
        if newinstr != '':
            self.state.unloadInstrument()
            text = get_file_contents(oldinstr)
            created_instr = save_instrfile(newinstr, text)
            if created_instr != '':
                self.state.loadInstrument(created_instr)
                self.emitter.status("Instrument saved as: " + newinstr)
    
    def handleNewInstrument(self):
        new_instr_req = self.view.showNewInstrDialog(self.state.getWorkDir())
        if self.displayNotSavedWhitespaceError(lambda: self.state.checkInstrFileCandidate(new_instr_req))==False:
            return
        
        if new_instr_req != '':
            template_text = open(os.path.join(mccode_config.configuration["MCCODE_LIB_DIR"], "examples", "template_simple.instr")).read()
            new_instr = save_instrfile(new_instr_req, template_text)
            if new_instr != '':
                if self.view.closeCodeEditorWindow():
                    self.state.unloadInstrument()
                    self.state.loadInstrument(new_instr)
                    self.view.showCodeEditorWindow(new_instr)
                    self.emitter.status("Editing new instrument: " + os.path.basename(str(new_instr)))
    
    def handleNewFromTemplate(self, instr_templ=''):
        new_instr_req = self.view.showNewInstrFromTemplateDialog(os.path.join(self.state.getWorkDir(), os.path.basename(str(instr_templ))))
        if self.displayNotSavedWhitespaceError(lambda: self.state.checkInstrFileCandidate(new_instr_req))==False:
            return
        
        if new_instr_req != '':
            if self.view.closeCodeEditorWindow():
                text = get_file_contents(instr_templ)
                self.state.unloadInstrument()
                new_instr = save_instrfile(new_instr_req, text)
                self.state.loadInstrument(new_instr)
                self.emitter.status("Instrument created: " + os.path.basename(str(new_instr)))
    
    def handleOpenInstrument(self):
        instr = self.view.showOpenInstrumentDlg(self.state.getWorkDir())
        if not instr:
            return
        if not os.path.isfile(instr):
            self.emitter.status("Please select a file rather than a folder. Folder load not supported.")
            return
        if self.displayNotSavedWhitespaceError(lambda: self.state.checkInstrFileCandidate(instr))==False:
            return
        
        if instr:
            if self.view.closeCodeEditorWindow():
                self.state.unloadInstrument()
                self.state.loadInstrument(instr)
                self.emitter.message("Instrument opened: " + os.path.basename(str(instr)), gui=True)
                self.emitter.status("Instrument: " + os.path.basename(str(instr)))
    
    def handleMcdoc(self):
        cmd='%sdoc' % mccode_config.get_mccode_prefix()
        if sys.platform == "win32":
            cmd='start ' + cmd + '.bat'
        subprocess.Popen(cmd, shell=True)

    def handleMcdocCurrentInstr(self):
        cmd='%sdoc' % mccode_config.get_mccode_prefix()
        if sys.platform == "win32":
            cmd ='start ' + cmd + '.bat'
        cmd = cmd + ' %s' % self.state.getInstrumentFile()
        subprocess.Popen(cmd, shell=True)

    def handleEnvironment(self):
        terminal = mccode_config.configuration["TERMINAL"]
        if not sys.platform == 'win32':
            scriptfile = mccode_config.configuration["MCCODE_LIB_DIR"] + '/environment'
        else:
            scriptfile = 'start ' + mccode_config.configuration["MCCODE_LIB_DIR"] + '\\..\\bin\\mccodego.bat'

        subprocess.Popen(terminal + ' ' + scriptfile, shell=True)
        
    def handleDefault(self):
        reply = QtWidgets.QMessageBox.question(self.view.mw,
                                           'Define system default?',
                                           'Do you want to make the current ' +  mccode_config.configuration["MCCODE"] + ' the system default?'),
 
        if reply == QtWidgets.QMessageBox.Yes:
             subprocess.Popen('postinst set_mccode_default', shell=True)
             

    def handleDefaultMcguiPy(self):
        reply = QtWidgets.QMessageBox.question(self.view.mw,
                                           'Make Python gui App default?',
                                           'Do you want to use Python ' +  mccode_config.configuration["MCCODE"] + ' gui in the macOS App?')
        if reply == QtWidgets.QMessageBox.Yes:
            subprocess.Popen('postinst osx_app_default py', shell=True)

    def handleDefaultMcguiPl(self):
        reply = QtWidgets.QMessageBox.question(self.view.mw,
                                           'Make Python gui App default?',
                                           'Do you want to use Perl ' +  mccode_config.configuration["MCCODE"] + ' gui in the macOS App?')
        if reply == QtWidgets.QMessageBox.Yes:
            subprocess.Popen('postinst osx_app_default pl', shell=True)

        
    ''' Connect UI and state callbacks 
    '''
    def connectCallbacks(self):        
        # connect UI widget signals to our handlers/logics
        # NOTICE: This explicit widget access is an exception - all widget access is otherwise handled by the view classes

        mwui = self.view.mw.ui
        mwui.actionQuit.triggered.connect(self.handleExit)
        mwui.actionOpen_instrument.triggered.connect(self.handleOpenInstrument)
        mwui.actionClose_Instrument.triggered.connect(self.handleCloseInstrument)
        mwui.actionEdit_Instrument.triggered.connect(self.handleEditInstrument)
        mwui.actionSave_As.triggered.connect(self.handleSaveAs)
        mwui.actionNew_Instrument.triggered.connect(self.handleNewInstrument)
        mwui.actionConfiguration.triggered.connect(self.handleConfiguration)
        self.view.mw.add_conf_menu('Open terminal env.').triggered.connect(self.handleEnvironment)
        # On macOS
        # 1) add a copy of the configuration menu to File
        # 2) add menu points for changing what the bundle opens
        if sys.platform == 'darwin':
            self.view.mw.add_conf_menu('Configuration').triggered.connect(self.handleConfiguration)
            self.view.mw.add_conf_menu('Use Python App').triggered.connect(self.handleDefaultMcguiPy)
            self.view.mw.add_conf_menu('Use Perl App').triggered.connect(self.handleDefaultMcguiPl)            

        # If not on Windows add menu point to make current mccode the system default
        if not sys.platform == 'win32':
            self.view.mw.add_conf_menu('Set as default').triggered.connect(self.handleDefault)
        mwui.btnRun.clicked.connect(self.handleRunOrInterruptSim)
        mwui.btnPlot.clicked.connect(self.handlePlotResults)
        mwui.btnEdit.clicked.connect(self.handleEditInstrument)
        mwui.btnOpenInstrument.clicked.connect(self.handleOpenInstrument)

        mwui.actionCompile_Instrument.triggered.connect(self.state.compile)
        mwui.actionCompile_Instrument_MPI.triggered.connect(lambda: self.state.compile(mpi=True))
        mwui.actionRun_Simulation.triggered.connect(self.handleRunOrInterruptSim)
        mwui.actionPlot.triggered.connect(self.handlePlotResults)
        mwui.actionPlotOther.triggered.connect(self.handlePlotOtherResults)
        mwui.actionDisplay.triggered.connect(self.handleMcDisplayWeb)
        mwui.actionDisplay_2d.triggered.connect(self.handleMcDisplay2D)

        mwui.actionMcDocCurrent.triggered.connect(self.handleMcdocCurrentInstr)
        mwui.actionMcdoc.triggered.connect(self.handleMcdoc)
        mwui.actionMcstas_Web_Page.triggered.connect(self.handleHelpWeb)
        mwui.actionMcstas_User_Manual.triggered.connect(self.handleHelpPdf)
        mwui.actionMcstas_Component_Manual.triggered.connect(self.handleHelpPdfComponents)
        mwui.actionAbout.triggered.connect(self.handleHelpAbout)
        
        self.view.ew.ui.actionSave_As.triggered.connect(self.handleSaveAs)

        ew = self.view.ew
        ew.saveRequest.connect(self.handleSaveInstrument)

        st = self.state
        st.simStateUpdated.connect(self.view.updateSimState)
        st.instrumentUpdated.connect(self.view.updateInstrument)

        emitter = self.emitter
        emitter.statusUpdate.connect(self.view.updateStatus)
        emitter.logMessageUpdate.connect(self.view.updateLog)