Example #1
0
class AppMainWindow(Ui_mainWindow, QMainWindow):
    """
    Main application window
    """
    # internal decorator to perform common checks required
    # for many calls
    #############################
    class CallChecker(object):        
        def __init__(self):
            pass
        
        def setWindow(self, main_window):
            self.main_window = main_window

        def __call__(self, f):
            @functools.wraps(f)
            def wrapper(*args, **kw):
                self.main_window.setEnabled(False)
                try:
                    logUICall.log('function call %s from module %s' % (f.__name__, f.__module__), logUICall.DEBUG)                                        
                    return f(*args, **kw)
                except SIDDUIException as uie:
                    logUICall.log(uie, logUICall.WARNING)
                    self.main_window.ui.statusbar.showMessage(get_ui_string('app.error.ui'))
                except SIDDException as se:
                    logUICall.log(se, logUICall.WARNING)
                    self.main_window.ui.statusbar.showMessage(get_ui_string('app.error.model'))
                except Exception as e:
                    logUICall.log(e, logUICall.ERROR)
                    self.main_window.ui.statusbar.showMessage(get_ui_string('app.error.unexpected'))
                finally:
                    self.main_window.setEnabled(True)
            return wrapper
        
    apiCallChecker = CallChecker()

    # CONSTANTS
    #############################    
    UI_WINDOW_GEOM = 'app_main/geometry'
    UI_WINDOW_STATE = 'app_main/windowState'

    TAB_DATA, TAB_MS, TAB_MOD, TAB_RESULT = range(4)

    # constructor / destructor
    #############################    
    def __init__(self, qtapp, app_config):
        """
        constructor
        - initialize UI elements
        - connect UI elements to callback            
        """
        # create UI
        super(AppMainWindow, self).__init__()
        AppMainWindow.apiCallChecker.setWindow(self)
        
        self.qtapp = qtapp
        self.app_config = app_config
        self.taxonomy = get_taxonomy(app_config.get('options', 'taxonomy', 'gem'))
        self.ui = Ui_mainWindow()
        self.ui.setupUi(self)
        
        self.settings = QSettings(SIDD_COMPANY, '%s %s' %(SIDD_APP_NAME, SIDD_VERSION));
        self.restoreGeometry(self.settings.value(self.UI_WINDOW_GEOM).toByteArray());
        self.restoreState(self.settings.value(self.UI_WINDOW_STATE).toByteArray());
        
        self.lb_map_location = QLabel(self)
        self.lb_map_location.resize(self.ui.statusbar.width()/3, self.ui.statusbar.height())
        self.ui.statusbar.addPermanentWidget(self.lb_map_location)
        
        self.msdb_dao = MSDatabaseDAO(FILE_MS_DB)
        
        self.tab_datainput = WidgetDataInput(self)
        self.ui.mainTabs.addTab(self.tab_datainput, get_ui_string("app.window.tab.input"))

        self.tab_ms = WidgetMappingSchemes(self)
        self.ui.mainTabs.addTab(self.tab_ms, get_ui_string("app.window.tab.ms"))

        self.tab_mod = WidgetSecondaryModifier(self)
        self.ui.mainTabs.addTab(self.tab_mod, get_ui_string("app.window.tab.mod"))

        self.tab_result = WidgetResult(self)
        self.ui.mainTabs.addTab(self.tab_result, get_ui_string("app.window.tab.result"))
        self.previewInput = True
        
        self.about = DialogAbout(self)
        self.about.setModal(True)        
        
        self.dlgAttrRange = DialogAttrRanges()
        self.dlgAttrRange.setModal(True)
        
        self.progress = DialogApply(self)
        self.progress.setModal(True)
        
        self.proc_options = DialogProcessingOptions(self)
        self.proc_options.setModal(True)
        
        # connect menu action to slots (ui events)        
        self.ui.mainTabs.currentChanged.connect(self.tabChanged)
        
        self.ui.actionProject_Blank.triggered.connect(self.createBlank)
        self.ui.actionUsing_Data_Wizard.triggered.connect(self.createWizard)
        self.ui.actionOpen_Existing.triggered.connect(self.loadProj)
        self.ui.actionSave.triggered.connect(self.saveProj)
        self.ui.actionSave_as.triggered.connect(self.saveProjAs)
        self.ui.actionExit.triggered.connect(self.close)
        
        self.ui.actionData_Input.triggered.connect(self.changeTab)
        self.ui.actionMapping_Schemes.triggered.connect(self.changeTab)
        self.ui.actionResult.triggered.connect(self.changeTab)
        
        self.ui.actionProcessing_Options.triggered.connect(self.setProcessingOptions)
        
        self.ui.actionAbout.triggered.connect(self.showAbout)
        
        # set project to None and adjust ui as needed
        self.closeProject()        
        self.ui.statusbar.showMessage(get_ui_string("app.status.ready"))

        # perform clean up from previous runs
        try:
            delete_folders_in_dir(get_temp_dir(), "tmp*")
        except:
            # cleanup is not-critical. no action taken even if fails
            pass

        # hide features 
        if not app_config.get('options', 'parse_modifier', True, bool):
            self.tab_mod.setVisible(False)
            self.ui.mainTabs.removeTab(self.TAB_MOD) 
            self.TAB_MOD = self.TAB_MS
            self.TAB_RESULT -= 1             
            
        # hide view menu
        self.ui.menuView.menuAction().setVisible(False)
        # hide data wizard
        self.ui.actionUsing_Data_Wizard.setVisible(False)

    # event handlers
    #############################
    @pyqtSlot(QCloseEvent)
    def resizeEvent(self, event):
        """ handle window resize """
        self.ui.mainTabs.resize(self.width(), self.height()-40)
    
    @pyqtSlot(QCloseEvent)    
    def closeEvent(self, event):
        self.closeProject()
        self.settings.setValue(self.UI_WINDOW_GEOM, self.saveGeometry());
        self.settings.setValue(self.UI_WINDOW_STATE, self.saveState());
        self.msdb_dao.close()       
        super(AppMainWindow, self).closeEvent(event)
    
    @logUICall 
    @pyqtSlot()   
    def createBlank(self):
        """ create a new project """
        # create new project file
        project = Project(self.app_config, self.taxonomy)
        # open project and sync UI
        self.setProject(project, skipVerify=True)
        self.ui.statusbar.showMessage(get_ui_string("app.status.project.created"))
    
    @logUICall 
    @pyqtSlot()     
    def createWizard(self):
        try:
            # should not update preview while using wizard
            self.setVisible(False)
            self.previewInput = False   
                        
            wizard = WidgetDataWizard(self, Project(self.app_config, self.taxonomy))            
            if wizard.exec_() == QDialog.Accepted:
                # setProject makes call to GIS component that requires visibility 
                self.setVisible(True)
                self.previewInput = True                
                self.setProject(wizard.project)
                self.ui.statusbar.showMessage(get_ui_string("app.status.project.created"))
            # else
            # do nothing?
        except:
            pass            
        finally:
            # return to normal window
            # these calls are reached 
            # 1. in case any exception occurs,
            # 2. wizard finished or cancelled  
            self.setVisible(True)
            self.previewInput = True
    
    @logUICall 
    @pyqtSlot()   
    def loadProj(self):
        """ open file dialog to load an existing application """
        self.getOpenFileName(self, 
                             get_ui_string("app.window.msg.project.open"),
                             get_ui_string("app.extension.db"), 
                             self.openProjectFile)
        self.ui.statusbar.showMessage(get_ui_string("app.status.project.loaded"))
    
    @logUICall
    @pyqtSlot()    
    def saveProj(self):
        """ save active project """
        if self.project is None:
            return
        try: 
            self.project.sync(SyncModes.Write)
            self.ui.statusbar.showMessage(get_ui_string("app.status.project.saved"))
        except SIDDProjectException as se:
            if se.error == ProjectErrors.FileNotSet:
                self.saveProjAs()

    @logUICall
    @pyqtSlot()   
    def saveProjAs(self):
        if self.project is None:
            return
        filename = QFileDialog.getSaveFileName(self,
                                               get_ui_string("app.window.msg.project.create"),
                                               get_app_dir(),
                                               get_ui_string("app.extension.db"))
        # no need to check for file overwrite, because QFileDialog return always confirmed overwrite       
        if not filename.isNull():
            filename = str(filename)
            self.project.set_project_path(filename)                
            self.project.sync(SyncModes.Write)
            self.saveLastOpenDir(filename[0:filename.rfind("/")])            
            self.ui.statusbar.showMessage(get_ui_string("app.status.project.saved"))                

    @logUICall
    @pyqtSlot()    
    def setProcessingOptions(self):
        if self.project is None:                
            return

        # set options to be those defined in project
        self.proc_options.resetOptions()
        for attribute in dir(self.proc_options):
            try:
                proc_attribute = self.project.operator_options['proc.%s'%attribute]
                setattr(self.proc_options, attribute, proc_attribute)
            except:
                # for empty project, this is the first time proc.options is set
                # just use default from proc_option dialogbox
                pass
            
        if self.proc_options.exec_() == QDialog.Accepted:
            for attribute in dir(self.proc_options):                
                self.project.operator_options['proc.%s'%attribute] = getattr(self.proc_options, attribute)
            # alert user
            answer = QMessageBox.question(self,
                                         get_ui_string("app.confirm.title"),
                                         get_ui_string("app.confirm.build.exposure"),
                                         buttons = QMessageBox.No | QMessageBox.Yes,
                                         defaultButton=QMessageBox.No)
            if answer == QMessageBox.No:
                return
            self.buildExposure()
            
    @logUICall
    @pyqtSlot()
    def showAbout(self):
        """
        event handler for menu item 
        show about box 
        """
        self.about.setVisible(True)

    @logUICall 
    @pyqtSlot()   
    def changeTab(self):
        """
        event handler for menu item in menu 
        - switch view to tab according to menu item pushed 
        """
        sender = self.sender()
        if sender == self.ui.actionData_Input:
            self.showTab(self.TAB_DATA)
        elif sender == self.ui.actionMapping_Schemes:
            self.showTab(self.TAB_MS)            
        elif sender == self.ui.actionResult:
            self.showTab(self.TAB_RESULT)
        else:
            logUICall.log('\tdo nothing. should not even be here', logUICall.WARNING)

    @logUICall 
    @pyqtSlot(int)   
    def tabChanged(self, index):
        if index == self.TAB_RESULT:
            self.lb_map_location.setVisible(True)
        else:
            self.lb_map_location.setVisible(False)

    # public methods    
    #############################
    @apiCallChecker
    def openProjectFile(self, path_to_file):
        """ 
        load project from given path
        shows error if path does not exist 
        """
        # NOTE: set_project_path will create new project if path_to_file
        #       does not exist, os.path.exists check is not optional
        if os.path.exists(path_to_file):
            # read file to create project
            project = Project(self.app_config, self.taxonomy)
            project.set_project_path(path_to_file)
            project.sync(SyncModes.Read)
            # open project and sync UI        
            self.setProject(project)
    
    @apiCallChecker
    def setProject(self, project, skipVerify=False):
        """ open a project and sync UI accordingly"""        
        # close and reset UI 
        self.closeProject()
        self.project = project        
        
        # sync ui
        self.tab_datainput.setProject(project)
        
        # set processing options
        for attribute in dir(self.proc_options):
            if self.project.operator_options.has_key('proc.%s'%attribute):
                setattr(self.proc_options, attribute, self.project.operator_options['proc.%s'%attribute])
        
        if not skipVerify:
            # verify to make sure input file are still in same place
            self.verifyInputs()        

        if self.project.ms is not None:
            self.visualizeMappingScheme(self.project.ms)
            self.ui.mainTabs.setTabEnabled(self.TAB_MS, True)
            self.ui.mainTabs.setTabEnabled(self.TAB_MOD, True)                           
        self.ui.mainTabs.setTabEnabled(self.TAB_DATA, True)
        self.ui.mainTabs.setTabEnabled(self.TAB_RESULT, True)
        self.ui.actionSave.setEnabled(True)
        self.ui.actionSave_as.setEnabled(True)                
        self.tab_result.set_project(project)

    @apiCallChecker    
    def closeProject(self):
        """ close opened project and update UI elements accordingly """
        # adjust UI in application window
        self.tab_result.closeAll()  # this call must happen first. 
                                    # otherwise, it locks temporary GIS files
        self.ui.mainTabs.setTabEnabled(self.TAB_DATA, False)            
        self.ui.mainTabs.setTabEnabled(self.TAB_MS, False)
        self.ui.mainTabs.setTabEnabled(self.TAB_MOD, False)               
        self.ui.mainTabs.setTabEnabled(self.TAB_RESULT, False) 
        self.showTab(self.TAB_DATA)
        # disable menu/menu items
        self.ui.actionSave.setEnabled(False)
        self.ui.actionSave_as.setEnabled(False)
        self.ui.actionMapping_Schemes.setEnabled(False)
        self.ui.actionResult.setEnabled(False)
        self.ui.actionProcessing_Options.setEnabled(False)

        if getattr(self, 'project', None) is not None:
            # save existing project is needed
            if self.project.require_save:
                ans = QMessageBox.question(self, 
                                           get_ui_string("app.window.msg.project.not_saved"),
                                           get_ui_string("app.window.msg.project.save_or_not"),
                                           buttons=QMessageBox.Yes|QMessageBox.No,
                                           defaultButton=QMessageBox.Yes)
                if ans == QMessageBox.Yes:
                    self.saveProj()
            
            # adjust UI in tabs 
            self.tab_datainput.closeProject()
            self.tab_ms.clearMappingScheme()
            self.tab_mod.closeMappingScheme()
            self.project.clean_up()           
            del self.project
            self.project = None
            
    @apiCallChecker
    def verifyInputs(self):
        """ perform checks on current dataset provided and update UI accordingly """                
        # remove result
        self.tab_result.closeResult()
        
        # verify current dataset
        self.ui.statusbar.showMessage(get_ui_string("app.status.ms.processing"))
        # invoke asynchronously 
        invoke_async(get_ui_string("app.status.processing"), self.project.verify_data)       
        self.tab_datainput.showVerificationResults()
        
        # always allow mapping scheme
        self.ui.mainTabs.setTabEnabled(self.TAB_MS, True)
        self.ui.mainTabs.setTabEnabled(self.TAB_MOD, True)
        self.ui.actionMapping_Schemes.setEnabled(True)
        self.ui.actionResult.setEnabled(True)      
        self.ui.actionProcessing_Options.setEnabled(True)   
        
        self.ui.statusbar.showMessage(get_ui_string("app.status.input.verified"))       

    @apiCallChecker
    def buildMappingScheme(self):
        """ build mapping scheme with given data """
        self.ui.statusbar.showMessage(get_ui_string("app.status.ms.processing"))
        # invoke asynchronously
        try:
            invoke_async(get_ui_string("app.status.processing"), self.project.build_ms)
        except SIDDException as err:
            # different error message used in this case
            raise SIDDUIException(get_ui_string('project.error.sampling', str(err)))
        
        self.visualizeMappingScheme(self.project.ms)
        self.ui.statusbar.showMessage(get_ui_string("app.status.ms.created"))

    @apiCallChecker
    def createEmptyMS(self):
        """ build an empty mapping scheme tree for user to manipulate manually """
        self.ui.statusbar.showMessage(get_ui_string("app.status.ms.processing"))
        # invoke asynchronously 
        invoke_async(get_ui_string("app.status.processing"), self.project.create_empty_ms)
        self.visualizeMappingScheme(self.project.ms)
        self.ui.statusbar.showMessage(get_ui_string("app.status.ms.created"))
    
    @apiCallChecker
    def appendMSBranch(self, node, branch):
        """ append a branch (from library) to a node in a mapping scheme tree """
        self.ui.statusbar.showMessage(get_ui_string("app.status.ms.processing"))         
        self.project.ms.append_branch(node, branch)
        self.visualizeMappingScheme(self.project.ms)
        self.ui.statusbar.showMessage(get_ui_string("app.status.ms.modified"))
    
    @apiCallChecker
    def deleteMSBranch(self, node):
        """ delete selected node and children from mapping scheme tree """
        self.ui.statusbar.showMessage(get_ui_string("app.status.ms.processing"))
        self.project.ms.delete_branch(node)
        self.visualizeMappingScheme(self.project.ms)
        self.ui.statusbar.showMessage(get_ui_string("app.status.ms.modified"))

    @apiCallChecker
    def exportMS(self, path, format):
        """ export mapping scheme leaves as CSV """
        self.ui.statusbar.showMessage(get_ui_string("app.status.ms.processing"))
        # invoke asynchronously       
        invoke_async(get_ui_string("app.status.processing"), self.project.export_ms, path, format)
        self.ui.statusbar.showMessage(get_ui_string("app.status.ms.exported"))
        
    @apiCallChecker
    def loadMS(self, path):
        self.ui.statusbar.showMessage(get_ui_string("app.status.ms.processing"))
        # invoke asynchronously       
        invoke_async(get_ui_string("app.status.processing"), self.project.load_ms, path)
        self.visualizeMappingScheme(self.project.ms)
        self.ui.statusbar.showMessage(get_ui_string("app.status.ms.created"))

    @apiCallChecker
    def exportResults(self, export_format, export_path):
        """ export mapping scheme leaves as CSV """
        self.ui.statusbar.showMessage(get_ui_string("app.status.exposure.exported"))
        # invoke asynchronously
        self.project.set_export(export_format, export_path)        
        invoke_async(get_ui_string("app.status.processing"), self.project.export_data)
        self.ui.statusbar.showMessage(get_ui_string("app.status.exposure.exported"))

    @apiCallChecker
    def buildExposure(self):        
        """ build exposure """
        self.ui.statusbar.showMessage(get_ui_string("app.status.ms.processing"))
                
        # verify current dataset to make sure can process exposure
        self.project.verify_data()
        
        # can not proceed if project is not ready for exposure
        if self.project.status != ProjectStatus.ReadyForExposure:
            logUICall.log(get_ui_string("project.error.NotEnoughData"), logUICall.WARNING)

            # show result 
            self.tab_datainput.showVerificationResults()
            self.ui.mainTabs.setCurrentIndex(self.TAB_DATA)
            return
        
        # close current results
        self.tab_result.closeResult()
        self.ui.mainTabs.setTabEnabled(self.TAB_RESULT, True)

        # reset progress dialog
        self.progress.setVisible(True)
        self.progress.ui.pb_progress.setRange(0, self.project.build_exposure_total_steps())        
        self.progress.ui.txt_progress.clear()
        self.progress.ui.txt_progress.appendPlainText(get_ui_string("app.status.processing"))        
        self.qtapp.processEvents()
        
        cancelled = False
        error_occured = False
        error_message = ""
        curStep = 0
        try:
            for step in self.project.build_exposure_steps():
                if cancelled or error_occured:
                    break
                
                # use introspection to get operator class                           
                class_name = str(step.__class__)
                # result of above call has format 
                # <class '...'> where ... is the class name of interest
                class_name = class_name[class_name.find("'")+1:class_name.rfind("'")]
                
                # update UI
                logAPICall.log('\t %s' % step.name, logAPICall.DEBUG)
                self.progress.ui.txt_progress.appendPlainText(get_ui_string('message.%s'% class_name))
                self.progress.ui.pb_progress.setValue(curStep)                        
                self.qtapp.processEvents()
                sleep(0.5)
                
                # perform operation
                step.do_operation()
                if not self.progress.isVisible():
                    cancelled = True
                
                # operation successful
                curStep+=1
        except Exception as err:
            # exception are thrown if data is not ready for exposure 
            error_message = err.message
            error_occured = True
            self.progress.setVisible(False)
            
        if error_occured:
            # processing cancelled
            logUICall.log(error_message, logUICall.WARNING)
            self.ui.statusbar.showMessage(get_ui_string("app.status.cancelled"))
        elif cancelled:
            # processing cancelled
            logUICall.log(get_ui_string("app.status.cancelled"), logUICall.WARNING)
            self.ui.statusbar.showMessage(get_ui_string("app.status.cancelled"))
        else:
            # processing completed            
            self.project.verify_result()
            self.progress.setVisible(False)
        
            # show result
            self.tab_result.refreshResult()
            self.ui.mainTabs.setTabEnabled(self.TAB_RESULT, True)
            self.ui.mainTabs.setCurrentIndex(self.TAB_RESULT)
            self.ui.statusbar.showMessage(get_ui_string("app.status.exposure.created"))       
    
    def showTab(self, index):
        """ switch view to tab with given index. do nothing if index is not valid """
        if index >=self.TAB_DATA and index <=self.TAB_RESULT:
            self.ui.mainTabs.setCurrentIndex(index)
    
    def refreshPreview(self):
        """ refresh all layers shown in Preview tab """
        if self.previewInput:
            self.tab_result.refreshView()

    def visualizeMappingScheme(self, ms):
        """ display the given mapping scheme in Mapping scheme and Modifier tabs"""
        self.tab_ms.showMappingScheme(ms)
        self.tab_mod.showMappingScheme(ms)
    
    def updateMapLocation(self, x, y):
        self.lb_map_location.setText("Longitude %.4f latitude %4f" % (x, y))
    
    # utility methods    
    # no error checking is performed in these functions
    # caller must catch possible exception
    ###############################
    def setRange(self, ranges, attribute):         
        if ranges.has_key(attribute):
            range = ranges[attribute]
            self.dlgAttrRange.set_values(attribute, range['min_values'], range['max_values'])
        else:
            self.dlgAttrRange.set_values(attribute, [], [])
        
        range_updated = self.dlgAttrRange.exec_() == QDialog.Accepted         
        if range_updated:
            ranges[attribute] = {'min_values':self.dlgAttrRange.min_values,
                                 'max_values':self.dlgAttrRange.max_values}
        return range_updated
                
    def getOpenFileName(self, parent, title, extension, callback):
        """ show open file dialog box to get a filename """
        filename = QFileDialog.getOpenFileName(parent, title, self.getLastOpenDir(), extension)
        if not filename.isNull():
            filename = str(filename)
            if os.path.exists(filename):                
                # store directory to file, so next Open will be in same dir
                self.saveLastOpenDir(filename[0:filename.rfind("/")])
                callback(filename)  # no error catching to make sure callback is actually a function

    def saveLastOpenDir(self, dir_path):
        """ store path so it can be retrieved by other parts of the application """
        self.settings.setValue('LAST_OPEN_DIR', dir_path)

    def getLastOpenDir(self):
        """ retrieve remembered path """
        last_dir = self.settings.value('LAST_OPEN_DIR').toString()
        if last_dir is None:
            return get_app_dir()
        else:
            return str(last_dir)
Example #2
0
class AppMainWindow(Ui_mainWindow, QMainWindow):
    """
    Main application window
    """

    # internal decorator to perform common checks required
    # for many calls
    #############################
    class CallChecker(object):
        def __init__(self):
            pass

        def setWindow(self, main_window):
            self.main_window = main_window

        def __call__(self, f):
            @functools.wraps(f)
            def wrapper(*args, **kw):
                self.main_window.setEnabled(False)
                try:
                    logUICall.log(
                        'function call %s from module %s' %
                        (f.__name__, f.__module__), logUICall.DEBUG)
                    return f(*args, **kw)
                except SIDDUIException as uie:
                    logUICall.log(uie, logUICall.WARNING)
                    self.main_window.ui.statusbar.showMessage(
                        get_ui_string('app.error.ui'))
                except SIDDException as se:
                    logUICall.log(se, logUICall.WARNING)
                    self.main_window.ui.statusbar.showMessage(
                        get_ui_string('app.error.model'))
                except Exception as e:
                    logUICall.log(e, logUICall.ERROR)
                    self.main_window.ui.statusbar.showMessage(
                        get_ui_string('app.error.unexpected'))
                finally:
                    self.main_window.setEnabled(True)

            return wrapper

    apiCallChecker = CallChecker()

    # CONSTANTS
    #############################
    UI_WINDOW_GEOM = 'app_main/geometry'
    UI_WINDOW_STATE = 'app_main/windowState'

    TAB_DATA, TAB_MS, TAB_MOD, TAB_RESULT = range(4)

    # constructor / destructor
    #############################
    def __init__(self, qtapp, app_config):
        """
        constructor
        - initialize UI elements
        - connect UI elements to callback            
        """
        # create UI
        super(AppMainWindow, self).__init__()
        AppMainWindow.apiCallChecker.setWindow(self)

        self.qtapp = qtapp
        self.app_config = app_config
        self.taxonomy = get_taxonomy(
            app_config.get('options', 'taxonomy', 'gem'))
        self.ui = Ui_mainWindow()
        self.ui.setupUi(self)

        self.settings = QSettings(SIDD_COMPANY,
                                  '%s %s' % (SIDD_APP_NAME, SIDD_VERSION))
        self.restoreGeometry(
            self.settings.value(self.UI_WINDOW_GEOM).toByteArray())
        self.restoreState(
            self.settings.value(self.UI_WINDOW_STATE).toByteArray())

        self.lb_map_location = QLabel(self)
        self.lb_map_location.resize(self.ui.statusbar.width() / 3,
                                    self.ui.statusbar.height())
        self.ui.statusbar.addPermanentWidget(self.lb_map_location)

        self.msdb_dao = MSDatabaseDAO(FILE_MS_DB)

        self.tab_datainput = WidgetDataInput(self)
        self.ui.mainTabs.addTab(self.tab_datainput,
                                get_ui_string("app.window.tab.input"))

        self.tab_ms = WidgetMappingSchemes(self)
        self.ui.mainTabs.addTab(self.tab_ms,
                                get_ui_string("app.window.tab.ms"))

        self.tab_mod = WidgetSecondaryModifier(self)
        self.ui.mainTabs.addTab(self.tab_mod,
                                get_ui_string("app.window.tab.mod"))

        self.tab_result = WidgetResult(self)
        self.ui.mainTabs.addTab(self.tab_result,
                                get_ui_string("app.window.tab.result"))
        self.previewInput = True

        self.about = DialogAbout(self)
        self.about.setModal(True)

        self.dlgAttrRange = DialogAttrRanges()
        self.dlgAttrRange.setModal(True)

        self.progress = DialogApply(self)
        self.progress.setModal(True)

        self.proc_options = DialogProcessingOptions(self)
        self.proc_options.setModal(True)

        # connect menu action to slots (ui events)
        self.ui.mainTabs.currentChanged.connect(self.tabChanged)

        self.ui.actionProject_Blank.triggered.connect(self.createBlank)
        self.ui.actionUsing_Data_Wizard.triggered.connect(self.createWizard)
        self.ui.actionOpen_Existing.triggered.connect(self.loadProj)
        self.ui.actionSave.triggered.connect(self.saveProj)
        self.ui.actionSave_as.triggered.connect(self.saveProjAs)
        self.ui.actionExit.triggered.connect(self.close)

        self.ui.actionData_Input.triggered.connect(self.changeTab)
        self.ui.actionMapping_Schemes.triggered.connect(self.changeTab)
        self.ui.actionResult.triggered.connect(self.changeTab)

        self.ui.actionProcessing_Options.triggered.connect(
            self.setProcessingOptions)

        self.ui.actionAbout.triggered.connect(self.showAbout)

        # set project to None and adjust ui as needed
        self.closeProject()
        self.ui.statusbar.showMessage(get_ui_string("app.status.ready"))

        # perform clean up from previous runs
        try:
            delete_folders_in_dir(get_temp_dir(), "tmp*")
        except:
            # cleanup is not-critical. no action taken even if fails
            pass

        # hide features
        if not app_config.get('options', 'parse_modifier', True, bool):
            self.tab_mod.setVisible(False)
            self.ui.mainTabs.removeTab(self.TAB_MOD)
            self.TAB_MOD = self.TAB_MS
            self.TAB_RESULT -= 1

        # hide view menu
        self.ui.menuView.menuAction().setVisible(False)
        # hide data wizard
        self.ui.actionUsing_Data_Wizard.setVisible(False)

    # event handlers
    #############################
    @pyqtSlot(QCloseEvent)
    def resizeEvent(self, event):
        """ handle window resize """
        self.ui.mainTabs.resize(self.width(), self.height() - 40)

    @pyqtSlot(QCloseEvent)
    def closeEvent(self, event):
        self.closeProject()
        self.settings.setValue(self.UI_WINDOW_GEOM, self.saveGeometry())
        self.settings.setValue(self.UI_WINDOW_STATE, self.saveState())
        self.msdb_dao.close()
        super(AppMainWindow, self).closeEvent(event)

    @logUICall
    @pyqtSlot()
    def createBlank(self):
        """ create a new project """
        # create new project file
        project = Project(self.app_config, self.taxonomy)
        # open project and sync UI
        self.setProject(project, skipVerify=True)
        self.ui.statusbar.showMessage(
            get_ui_string("app.status.project.created"))

    @logUICall
    @pyqtSlot()
    def createWizard(self):
        try:
            # should not update preview while using wizard
            self.setVisible(False)
            self.previewInput = False

            wizard = WidgetDataWizard(self,
                                      Project(self.app_config, self.taxonomy))
            if wizard.exec_() == QDialog.Accepted:
                # setProject makes call to GIS component that requires visibility
                self.setVisible(True)
                self.previewInput = True
                self.setProject(wizard.project)
                self.ui.statusbar.showMessage(
                    get_ui_string("app.status.project.created"))
            # else
            # do nothing?
        except:
            pass
        finally:
            # return to normal window
            # these calls are reached
            # 1. in case any exception occurs,
            # 2. wizard finished or cancelled
            self.setVisible(True)
            self.previewInput = True

    @logUICall
    @pyqtSlot()
    def loadProj(self):
        """ open file dialog to load an existing application """
        self.getOpenFileName(self,
                             get_ui_string("app.window.msg.project.open"),
                             get_ui_string("app.extension.db"),
                             self.openProjectFile)
        self.ui.statusbar.showMessage(
            get_ui_string("app.status.project.loaded"))

    @logUICall
    @pyqtSlot()
    def saveProj(self):
        """ save active project """
        if self.project is None:
            return
        try:
            self.project.sync(SyncModes.Write)
            self.ui.statusbar.showMessage(
                get_ui_string("app.status.project.saved"))
        except SIDDProjectException as se:
            if se.error == ProjectErrors.FileNotSet:
                self.saveProjAs()

    @logUICall
    @pyqtSlot()
    def saveProjAs(self):
        if self.project is None:
            return
        filename = QFileDialog.getSaveFileName(
            self, get_ui_string("app.window.msg.project.create"),
            get_app_dir(), get_ui_string("app.extension.db"))
        # no need to check for file overwrite, because QFileDialog return always confirmed overwrite
        if not filename.isNull():
            filename = str(filename)
            self.project.set_project_path(filename)
            self.project.sync(SyncModes.Write)
            self.saveLastOpenDir(filename[0:filename.rfind("/")])
            self.ui.statusbar.showMessage(
                get_ui_string("app.status.project.saved"))

    @logUICall
    @pyqtSlot()
    def setProcessingOptions(self):
        if self.project is None:
            return

        # set options to be those defined in project
        self.proc_options.resetOptions()
        for attribute in dir(self.proc_options):
            try:
                proc_attribute = self.project.operator_options['proc.%s' %
                                                               attribute]
                setattr(self.proc_options, attribute, proc_attribute)
            except:
                # for empty project, this is the first time proc.options is set
                # just use default from proc_option dialogbox
                pass

        if self.proc_options.exec_() == QDialog.Accepted:
            for attribute in dir(self.proc_options):
                self.project.operator_options['proc.%s' % attribute] = getattr(
                    self.proc_options, attribute)
            # alert user
            answer = QMessageBox.question(
                self,
                get_ui_string("app.confirm.title"),
                get_ui_string("app.confirm.build.exposure"),
                buttons=QMessageBox.No | QMessageBox.Yes,
                defaultButton=QMessageBox.No)
            if answer == QMessageBox.No:
                return
            self.buildExposure()

    @logUICall
    @pyqtSlot()
    def showAbout(self):
        """
        event handler for menu item 
        show about box 
        """
        self.about.setVisible(True)

    @logUICall
    @pyqtSlot()
    def changeTab(self):
        """
        event handler for menu item in menu 
        - switch view to tab according to menu item pushed 
        """
        sender = self.sender()
        if sender == self.ui.actionData_Input:
            self.showTab(self.TAB_DATA)
        elif sender == self.ui.actionMapping_Schemes:
            self.showTab(self.TAB_MS)
        elif sender == self.ui.actionResult:
            self.showTab(self.TAB_RESULT)
        else:
            logUICall.log('\tdo nothing. should not even be here',
                          logUICall.WARNING)

    @logUICall
    @pyqtSlot(int)
    def tabChanged(self, index):
        if index == self.TAB_RESULT:
            self.lb_map_location.setVisible(True)
        else:
            self.lb_map_location.setVisible(False)

    # public methods
    #############################
    @apiCallChecker
    def openProjectFile(self, path_to_file):
        """ 
        load project from given path
        shows error if path does not exist 
        """
        # NOTE: set_project_path will create new project if path_to_file
        #       does not exist, os.path.exists check is not optional
        if os.path.exists(path_to_file):
            # read file to create project
            project = Project(self.app_config, self.taxonomy)
            project.set_project_path(path_to_file)
            project.sync(SyncModes.Read)
            # open project and sync UI
            self.setProject(project)

    @apiCallChecker
    def setProject(self, project, skipVerify=False):
        """ open a project and sync UI accordingly"""
        # close and reset UI
        self.closeProject()
        self.project = project

        # sync ui
        self.tab_datainput.setProject(project)

        # set processing options
        for attribute in dir(self.proc_options):
            if self.project.operator_options.has_key('proc.%s' % attribute):
                setattr(self.proc_options, attribute,
                        self.project.operator_options['proc.%s' % attribute])

        if not skipVerify:
            # verify to make sure input file are still in same place
            self.verifyInputs()

        if self.project.ms is not None:
            self.visualizeMappingScheme(self.project.ms)
            self.ui.mainTabs.setTabEnabled(self.TAB_MS, True)
            self.ui.mainTabs.setTabEnabled(self.TAB_MOD, True)
        self.ui.mainTabs.setTabEnabled(self.TAB_DATA, True)
        self.ui.mainTabs.setTabEnabled(self.TAB_RESULT, True)
        self.ui.actionSave.setEnabled(True)
        self.ui.actionSave_as.setEnabled(True)
        self.tab_result.set_project(project)

    @apiCallChecker
    def closeProject(self):
        """ close opened project and update UI elements accordingly """
        # adjust UI in application window
        self.tab_result.closeAll()  # this call must happen first.
        # otherwise, it locks temporary GIS files
        self.ui.mainTabs.setTabEnabled(self.TAB_DATA, False)
        self.ui.mainTabs.setTabEnabled(self.TAB_MS, False)
        self.ui.mainTabs.setTabEnabled(self.TAB_MOD, False)
        self.ui.mainTabs.setTabEnabled(self.TAB_RESULT, False)
        self.showTab(self.TAB_DATA)
        # disable menu/menu items
        self.ui.actionSave.setEnabled(False)
        self.ui.actionSave_as.setEnabled(False)
        self.ui.actionMapping_Schemes.setEnabled(False)
        self.ui.actionResult.setEnabled(False)
        self.ui.actionProcessing_Options.setEnabled(False)

        if getattr(self, 'project', None) is not None:
            # save existing project is needed
            if self.project.require_save:
                ans = QMessageBox.question(
                    self,
                    get_ui_string("app.window.msg.project.not_saved"),
                    get_ui_string("app.window.msg.project.save_or_not"),
                    buttons=QMessageBox.Yes | QMessageBox.No,
                    defaultButton=QMessageBox.Yes)
                if ans == QMessageBox.Yes:
                    self.saveProj()

            # adjust UI in tabs
            self.tab_datainput.closeProject()
            self.tab_ms.clearMappingScheme()
            self.tab_mod.closeMappingScheme()
            self.project.clean_up()
            del self.project
            self.project = None

    @apiCallChecker
    def verifyInputs(self):
        """ perform checks on current dataset provided and update UI accordingly """
        # remove result
        self.tab_result.closeResult()

        # verify current dataset
        self.ui.statusbar.showMessage(
            get_ui_string("app.status.ms.processing"))
        # invoke asynchronously
        invoke_async(get_ui_string("app.status.processing"),
                     self.project.verify_data)
        self.tab_datainput.showVerificationResults()

        # always allow mapping scheme
        self.ui.mainTabs.setTabEnabled(self.TAB_MS, True)
        self.ui.mainTabs.setTabEnabled(self.TAB_MOD, True)

        self.ui.actionMapping_Schemes.setEnabled(True)
        self.ui.actionResult.setEnabled(True)
        self.ui.actionProcessing_Options.setEnabled(True)

        self.ui.statusbar.showMessage(
            get_ui_string("app.status.input.verified"))

    @apiCallChecker
    def buildMappingScheme(self):
        """ build mapping scheme with given data """
        self.ui.statusbar.showMessage(
            get_ui_string("app.status.ms.processing"))
        # invoke asynchronously
        try:
            invoke_async(get_ui_string("app.status.processing"),
                         self.project.build_ms)
        except SIDDException as err:
            # different error message used in this case
            raise SIDDUIException(
                get_ui_string('project.error.sampling', str(err)))

        self.visualizeMappingScheme(self.project.ms)
        self.ui.statusbar.showMessage(get_ui_string("app.status.ms.created"))

    @apiCallChecker
    def createEmptyMS(self):
        """ build an empty mapping scheme tree for user to manipulate manually """
        self.ui.statusbar.showMessage(
            get_ui_string("app.status.ms.processing"))
        # invoke asynchronously
        invoke_async(get_ui_string("app.status.processing"),
                     self.project.create_empty_ms)
        self.visualizeMappingScheme(self.project.ms)
        self.ui.statusbar.showMessage(get_ui_string("app.status.ms.created"))

    @apiCallChecker
    def appendMSBranch(self, node, branch):
        """ append a branch (from library) to a node in a mapping scheme tree """
        self.ui.statusbar.showMessage(
            get_ui_string("app.status.ms.processing"))
        self.project.ms.append_branch(node, branch)
        self.visualizeMappingScheme(self.project.ms)
        self.ui.statusbar.showMessage(get_ui_string("app.status.ms.modified"))

    @apiCallChecker
    def deleteMSBranch(self, node):
        """ delete selected node and children from mapping scheme tree """
        self.ui.statusbar.showMessage(
            get_ui_string("app.status.ms.processing"))
        self.project.ms.delete_branch(node)
        self.visualizeMappingScheme(self.project.ms)
        self.ui.statusbar.showMessage(get_ui_string("app.status.ms.modified"))

    @apiCallChecker
    def exportMS(self, path, format):
        """ export mapping scheme leaves as CSV """
        self.ui.statusbar.showMessage(
            get_ui_string("app.status.ms.processing"))
        # invoke asynchronously
        invoke_async(get_ui_string("app.status.processing"),
                     self.project.export_ms, path, format)
        self.ui.statusbar.showMessage(get_ui_string("app.status.ms.exported"))

    @apiCallChecker
    def loadMS(self, path):
        self.ui.statusbar.showMessage(
            get_ui_string("app.status.ms.processing"))
        # invoke asynchronously
        invoke_async(get_ui_string("app.status.processing"),
                     self.project.load_ms, path)
        self.visualizeMappingScheme(self.project.ms)
        self.ui.statusbar.showMessage(get_ui_string("app.status.ms.created"))

    @apiCallChecker
    def exportResults(self, export_format, export_path):
        """ export mapping scheme leaves as CSV """
        self.ui.statusbar.showMessage(
            get_ui_string("app.status.exposure.exported"))
        # invoke asynchronously
        self.project.set_export(export_format, export_path)
        invoke_async(get_ui_string("app.status.processing"),
                     self.project.export_data)
        self.ui.statusbar.showMessage(
            get_ui_string("app.status.exposure.exported"))

    @apiCallChecker
    def buildExposure(self):
        """ build exposure """
        self.ui.statusbar.showMessage(
            get_ui_string("app.status.ms.processing"))

        # verify current dataset to make sure can process exposure
        self.project.verify_data()

        # can not proceed if project is not ready for exposure
        if self.project.status != ProjectStatus.ReadyForExposure:
            logUICall.log(get_ui_string("project.error.NotEnoughData"),
                          logUICall.WARNING)

            # show result
            self.tab_datainput.showVerificationResults()
            self.ui.mainTabs.setCurrentIndex(self.TAB_DATA)
            return

        # close current results
        self.tab_result.closeResult()
        self.ui.mainTabs.setTabEnabled(self.TAB_RESULT, True)

        # reset progress dialog
        self.progress.setVisible(True)
        self.progress.ui.pb_progress.setRange(
            0, self.project.build_exposure_total_steps())
        self.progress.ui.txt_progress.clear()
        self.progress.ui.txt_progress.appendPlainText(
            get_ui_string("app.status.processing"))
        self.qtapp.processEvents()

        cancelled = False
        error_occured = False
        error_message = ""
        curStep = 0
        try:
            for step in self.project.build_exposure_steps():
                if cancelled or error_occured:
                    break

                # use introspection to get operator class
                class_name = str(step.__class__)
                # result of above call has format
                # <class '...'> where ... is the class name of interest
                class_name = class_name[class_name.find("'") +
                                        1:class_name.rfind("'")]

                # update UI
                logAPICall.log('\t %s' % step.name, logAPICall.DEBUG)
                self.progress.ui.txt_progress.appendPlainText(
                    get_ui_string('message.%s' % class_name))
                self.progress.ui.pb_progress.setValue(curStep)
                self.qtapp.processEvents()
                sleep(0.5)

                # perform operation
                step.do_operation()
                if not self.progress.isVisible():
                    cancelled = True

                # operation successful
                curStep += 1
        except Exception as err:
            # exception are thrown if data is not ready for exposure
            error_message = err.message
            error_occured = True
            self.progress.setVisible(False)

        if error_occured:
            # processing cancelled
            logUICall.log(error_message, logUICall.WARNING)
            self.ui.statusbar.showMessage(
                get_ui_string("app.status.cancelled"))
        elif cancelled:
            # processing cancelled
            logUICall.log(get_ui_string("app.status.cancelled"),
                          logUICall.WARNING)
            self.ui.statusbar.showMessage(
                get_ui_string("app.status.cancelled"))
        else:
            # processing completed
            self.project.verify_result()
            self.progress.setVisible(False)

            # show result
            self.tab_result.refreshResult()
            self.ui.mainTabs.setTabEnabled(self.TAB_RESULT, True)
            self.ui.mainTabs.setCurrentIndex(self.TAB_RESULT)
            self.ui.statusbar.showMessage(
                get_ui_string("app.status.exposure.created"))

    def showTab(self, index):
        """ switch view to tab with given index. do nothing if index is not valid """
        if index >= self.TAB_DATA and index <= self.TAB_RESULT:
            self.ui.mainTabs.setCurrentIndex(index)

    def refreshPreview(self):
        """ refresh all layers shown in Preview tab """
        if self.previewInput:
            self.tab_result.refreshView()

    def visualizeMappingScheme(self, ms):
        """ display the given mapping scheme in Mapping scheme and Modifier tabs"""
        self.tab_ms.showMappingScheme(ms)
        self.tab_mod.showMappingScheme(ms)

    def updateMapLocation(self, x, y):
        self.lb_map_location.setText("Longitude %.4f latitude %4f" % (x, y))

    # utility methods
    # no error checking is performed in these functions
    # caller must catch possible exception
    ###############################
    def setRange(self, ranges, attribute):
        if ranges.has_key(attribute):
            range = ranges[attribute]
            self.dlgAttrRange.set_values(attribute, range['min_values'],
                                         range['max_values'])
        else:
            self.dlgAttrRange.set_values(attribute, [], [])

        range_updated = self.dlgAttrRange.exec_() == QDialog.Accepted
        if range_updated:
            ranges[attribute] = {
                'min_values': self.dlgAttrRange.min_values,
                'max_values': self.dlgAttrRange.max_values
            }
        return range_updated

    def getOpenFileName(self, parent, title, extension, callback):
        """ show open file dialog box to get a filename """
        filename = QFileDialog.getOpenFileName(parent, title,
                                               self.getLastOpenDir(),
                                               extension)
        if not filename.isNull():
            filename = str(filename)
            if os.path.exists(filename):
                # store directory to file, so next Open will be in same dir
                self.saveLastOpenDir(filename[0:filename.rfind("/")])
                callback(
                    filename
                )  # no error catching to make sure callback is actually a function

    def saveLastOpenDir(self, dir_path):
        """ store path so it can be retrieved by other parts of the application """
        self.settings.setValue('LAST_OPEN_DIR', dir_path)

    def getLastOpenDir(self):
        """ retrieve remembered path """
        last_dir = self.settings.value('LAST_OPEN_DIR').toString()
        if last_dir is None:
            return get_app_dir()
        else:
            return str(last_dir)