Exemple #1
0
class DocumentGeneratorDialog(QDialog, Ui_DocumentGeneratorDialog):
    """
    Dialog that enables a user to generate documents by using configuration
    information for different entities.
    """
    def __init__(self, iface, parent=None, plugin=None):
        QDialog.__init__(self, parent)
        self.setupUi(self)

        self._iface = iface
        self.plugin = plugin
        self._docTemplatePath = ""
        self._outputFilePath = ""
        self.curr_profile = current_profile()
        self.last_data_source = None
        self._config_mapping = OrderedDict()

        self._notif_bar = NotificationBar(self.vlNotification)

        self._doc_generator = DocumentGenerator(self._iface, self)

        self._data_source = ""

        enable_drag_sort(self.lstDocNaming)

        #Configure generate button
        generateBtn = self.buttonBox.button(QDialogButtonBox.Ok)
        if not generateBtn is None:
            generateBtn.setText(
                QApplication.translate("DocumentGeneratorDialog", "Generate"))

        #Load supported image types
        supportedImageTypes = QImageWriter.supportedImageFormats()
        for imageType in supportedImageTypes:
            imageTypeStr = imageType.data()
            self.cboImageType.addItem(imageTypeStr)

        self._init_progress_dialog()
        #Connect signals
        self.btnSelectTemplate.clicked.connect(self.onSelectTemplate)
        self.buttonBox.accepted.connect(self.onGenerate)
        self.chkUseOutputFolder.stateChanged.connect(
            self.onToggledOutputFolder)
        self.rbExpImage.toggled.connect(self.onToggleExportImage)
        self.tabWidget.currentChanged.connect(self.on_tab_index_changed)
        self.chk_template_datasource.stateChanged.connect(
            self.on_use_template_datasource)

        self.btnShowOutputFolder.clicked.connect(self.onShowOutputFolder)

    def _init_progress_dialog(self):
        """
        Initializes the progress dialog.
        """
        self.progress = QProgressBar(self)
        self.progress.resize(self.width(), 10)
        self.progress.setTextVisible(False)

    def add_entity_configuration(self, **kwargs):
        ent_config = EntityConfig(**kwargs)
        self.add_entity_config(ent_config)

    def add_entity_config(self, ent_config, progress_value=0):
        QApplication.processEvents()
        if not self._config_mapping.get(ent_config.title(), ""):
            fk_mapper = self._create_fk_mapper(ent_config)
            self.tabWidget.addTab(fk_mapper, ent_config.title())
            self._config_mapping[ent_config.title()] = ent_config

            #Force list of column names to be loaded
            if self.tabWidget.currentIndex() != 0:
                self.tabWidget.setCurrentIndex(0)

            else:
                self.on_tab_index_changed(0)

        self.progress.setValue(progress_value)

    def on_tab_index_changed(self, index):
        if index == -1:
            return

        config = self.config(index)

        if not config is None:
            #Set data source name
            self._data_source = config.data_source()

    def on_use_template_datasource(self, state):
        if state == Qt.Checked:
            self.tabWidget.setEnabled(False)
            self.chkUseOutputFolder.setEnabled(False)
            self.chkUseOutputFolder.setChecked(False)

        elif state == Qt.Unchecked:
            self.tabWidget.setEnabled(True)
            self.chkUseOutputFolder.setEnabled(True)
            self.chkUseOutputFolder.setChecked(False)
            self.on_tab_index_changed(self.tabWidget.currentIndex())

    def onShowOutputFolder(self):
        reg_config = RegistryConfig()
        path = reg_config.read([COMPOSER_OUTPUT])
        output_path = path.get(COMPOSER_OUTPUT, '')

        # windows
        if sys.platform.startswith('win32'):
            os.startfile(output_path)

        # *nix systems
        if sys.platform.startswith('linux'):
            subprocess.Popen(['xdg-open', output_path])

        # macOS
        if sys.platform.startswith('darwin'):
            subprocess.Popen(['open', output_path])

    def notification_bar(self):
        """
        :return: Returns an instance of the notification bar.
        :rtype: NotificationBar
        """
        return self._notif_bar

    def config(self, index):
        """
        Returns the configuration for the current mapper in the tab widget.
        """
        tab_key = self.tabWidget.tabText(index)

        return self._config_mapping.get(tab_key, None)

    def current_config(self):
        """
        Returns the configuration corresponding to the current widget in the
        tab.
        """
        return self.config(self.tabWidget.currentIndex())

    def _load_model_columns(self, config):
        """
        Load model columns into the view for specifying file output name.
        Only those columns of display type variants will be
        used.
        """
        model_attr_mapping = OrderedDict()

        for c in config.data_source_columns():
            model_attr_mapping[c] = format_name(c)

        self.lstDocNaming.load_mapping(model_attr_mapping)

    def _load_data_source_columns(self, entity):
        """
        Load the columns of a data source for use in the file naming.
        """
        table_cols = entity_display_columns(entity, True)

        attr_mapping = OrderedDict()

        for c, header in table_cols.iteritems():
            attr_mapping[c] = header

        self.lstDocNaming.load_mapping(attr_mapping)

    def _create_fk_mapper(self, config):
        fk_mapper = ForeignKeyMapper(config.ds_entity,
                                     self.tabWidget,
                                     self._notif_bar,
                                     enable_list=True,
                                     can_filter=True,
                                     plugin=self.plugin)

        fk_mapper.setDatabaseModel(config.model())
        fk_mapper.setSupportsList(True)
        fk_mapper.setDeleteonRemove(False)
        fk_mapper.setNotificationBar(self._notif_bar)

        return fk_mapper

    def onSelectTemplate(self):
        """
        Slot raised to load the template selector dialog.
        """
        current_config = self.current_config()
        if current_config is None:
            msg = QApplication.translate(
                'DocumentGeneratorDialog',
                'An error occured while trying to determine the data source '
                'for the current entity.\nPlease check your current profile '
                'settings.')
            QMessageBox.critical(
                self,
                QApplication.translate('DocumentGeneratorDialog',
                                       'Template Selector'), msg)
            return

        #Set the template selector to only load those templates that
        # reference the current data source.
        filter_table = current_config.data_source()
        templateSelector = TemplateDocumentSelector(
            self, filter_data_source=filter_table)

        if templateSelector.exec_() == QDialog.Accepted:
            docName, docPath = templateSelector.documentMapping()

            self.lblTemplateName.setText(docName)
            self._docTemplatePath = docPath
            if filter_table != self.last_data_source:
                #Load template data source fields
                self._load_template_datasource_fields()

    def _load_template_datasource_fields(self):
        #If using template data source
        template_doc, err_msg = self._doc_generator.template_document(
            self._docTemplatePath)
        if template_doc is None:
            QMessageBox.critical(
                self, "Error Generating documents",
                QApplication.translate(
                    "DocumentGeneratorDialog",
                    "Error Generating documents - %s" % (err_msg)))

            return

        composer_ds, err_msg = self._doc_generator.composer_data_source(
            template_doc)

        if composer_ds is None:
            QMessageBox.critical(
                self, "Error Generating documents",
                QApplication.translate(
                    "DocumentGeneratorDialog",
                    "Error Generating documents - %s" % (err_msg)))

            return

        #Load data source columns
        self._data_source = self.current_config().data_source()

        self.ds_entity = self.curr_profile.entity_by_name(self._data_source)

        self._load_data_source_columns(self.ds_entity)

    def onToggledOutputFolder(self, state):
        """
        Slot raised to enable/disable the generated output documents to be 
        written to the plugin composer output folder using the specified
        naming convention.
        """
        if state == Qt.Checked:
            self.gbDocNaming.setEnabled(True)

        elif state == Qt.Unchecked:
            self.gbDocNaming.setEnabled(False)

    def reset(self, success_status=False):
        """
        Clears/resets the dialog from user-defined options.
        """
        self._docTemplatePath = ""

        self._data_source = ""

        self.lblTemplateName.setText("")
        # reset form only if generation is successful
        if success_status:
            fk_table_view = self.tabWidget.currentWidget().\
                findChild(QTableView)
            while fk_table_view.model().rowCount() > 0:
                fk_table_view.model().rowCount(0)
                fk_table_view.model().removeRow(0)

            if self.tabWidget.count() > 0 and \
                    not self.chk_template_datasource.isChecked():
                self.on_tab_index_changed(0)

            if self.cboImageType.count() > 0:
                self.cboImageType.setCurrentIndex(0)

    def onToggleExportImage(self, state):
        """
        Slot raised to enable/disable the image formats combobox.
        """
        if state:
            self.cboImageType.setEnabled(True)

        else:
            self.cboImageType.setEnabled(False)

    def onGenerate(self):
        """
        Slot raised to initiate the certificate generation process.
        """
        self._notif_bar.clear()
        success_status = True
        config = self.current_config()
        self.last_data_source = config.data_source()
        if config is None:
            self._notif_bar.insertErrorNotification(QApplication.translate("DocumentGeneratorDialog", \
                                            "The entity configuration could not be extracted."))
            return

        #Get selected records and validate
        records = self.tabWidget.currentWidget().entities()

        if self.chk_template_datasource.isChecked():
            records = self._dummy_template_records()

        if len(records) == 0:
            self._notif_bar.insertErrorNotification(QApplication.translate("DocumentGeneratorDialog", \
                                                                          "Please load at least one entity record"))
            return

        if not self._docTemplatePath:
            self._notif_bar.insertErrorNotification(QApplication.translate("DocumentGeneratorDialog", \
                                                                          "Please select a document template to use"))
            return

        documentNamingAttrs = self.lstDocNaming.selectedMappings()

        if self.chkUseOutputFolder.checkState() == Qt.Checked and len(
                documentNamingAttrs) == 0:
            self._notif_bar.insertErrorNotification(QApplication.translate("DocumentGeneratorDialog", \
                                                "Please select at least one field for naming the output document"))

            return

        #Set output file properties
        if self.rbExpImage.isChecked():
            outputMode = DocumentGenerator.Image
            fileExtension = self.cboImageType.currentText()
            saveAsText = "Image File"

        else:
            outputMode = DocumentGenerator.PDF
            fileExtension = "pdf"
            saveAsText = "PDF File"

        #Show save file dialog if not using output folder
        if self.chkUseOutputFolder.checkState() == Qt.Unchecked:
            docDir = source_document_location()

            if self._outputFilePath:
                fileInfo = QFileInfo(self._outputFilePath)
                docDir = fileInfo.dir().path()

            self._outputFilePath = QFileDialog.getSaveFileName(
                self,
                QApplication.translate("DocumentGeneratorDialog",
                                       "Save Document"), docDir,
                "{0} (*.{1})".format(
                    QApplication.translate("DocumentGeneratorDialog",
                                           saveAsText), fileExtension))

            if not self._outputFilePath:
                self._notif_bar.insertErrorNotification(
                    QApplication.translate(
                        "DocumentGeneratorDialog",
                        "Process aborted. No output file was specified."))

                return

            #Include extension in file name
            self._outputFilePath = self._outputFilePath  #+ "." + fileExtension

        #else:
        #Multiple files to be generated.
        #pass

        self._doc_generator.set_link_field(config.link_field())

        self._doc_generator.clear_attr_value_formatters()

        if not self.chk_template_datasource.isChecked():
            #Apply cell formatters for naming output files
            self._doc_generator.set_attr_value_formatters(config.formatters())

        entity_field_name = "id"

        #Iterate through the selected records
        progressDlg = QProgressDialog(self)
        progressDlg.setMaximum(len(records))

        try:
            QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))

            for i, record in enumerate(records):
                progressDlg.setValue(i)

                if progressDlg.wasCanceled():
                    success_status = False
                    break

                #User-defined location
                if self.chkUseOutputFolder.checkState() == Qt.Unchecked:
                    status, msg = self._doc_generator.run(
                        self._docTemplatePath,
                        entity_field_name,
                        record.id,
                        outputMode,
                        data_source=self.ds_entity.name,
                        filePath=self._outputFilePath)
                    self._doc_generator.clear_temporary_layers()
                #Output folder location using custom naming
                else:

                    status, msg = self._doc_generator.run(
                        self._docTemplatePath,
                        entity_field_name,
                        record.id,
                        outputMode,
                        dataFields=documentNamingAttrs,
                        fileExtension=fileExtension,
                        data_source=self.ds_entity.name)
                    self._doc_generator.clear_temporary_layers()

                if not status:
                    result = QMessageBox.warning(
                        self,
                        QApplication.translate("DocumentGeneratorDialog",
                                               "Document Generate Error"), msg,
                        QMessageBox.Ignore | QMessageBox.Abort)

                    if result == QMessageBox.Abort:
                        progressDlg.close()
                        success_status = False

                        #Restore cursor
                        QApplication.restoreOverrideCursor()

                        return

                    #If its the last record and user has selected to ignore
                    if i + 1 == len(records):
                        progressDlg.close()
                        success_status = False

                        #Restore cursor
                        QApplication.restoreOverrideCursor()

                        return

                else:
                    progressDlg.setValue(len(records))

            QApplication.restoreOverrideCursor()

            QMessageBox.information(
                self,
                QApplication.translate("DocumentGeneratorDialog",
                                       "Document Generation Complete"),
                QApplication.translate(
                    "DocumentGeneratorDialog",
                    "Document generation has successfully completed."))

        except Exception as ex:
            LOGGER.debug(str(ex))
            err_msg = sys.exc_info()[1]
            QApplication.restoreOverrideCursor()

            QMessageBox.critical(
                self, "STDM",
                QApplication.translate(
                    "DocumentGeneratorDialog",
                    "Error Generating documents - %s" % (err_msg)))
            success_status = False

        #Reset UI
        self.reset(success_status)

    def _dummy_template_records(self):
        """
        This is applied when records from a template data source are to be
        used to generate the documents where no related entity will be used
        to filter matching records in the data source. The iteration of the
        data source records will be done internally within the
        DocumentGenerator class.
        """
        class _DummyRecord:
            id = 1

        return [_DummyRecord()]

    def showEvent(self, event):
        """
        Notifies if there are not entity configuration objects defined.
        :param event: Window event
        :type event: QShowEvent
        """
        QTimer.singleShot(500, self.check_entity_config)

        return QDialog.showEvent(self, event)

    def check_entity_config(self):
        if len(self._config_mapping) == 0:
            self._notif_bar.clear()

            msg = QApplication.translate(
                "DocumentGeneratorDialog", "Table "
                "configurations do not exist or have not been configured properly"
            )
            self._notif_bar.insertErrorNotification(msg)
Exemple #2
0
class Qt4SysTrayIcon:
    def __init__( self ):
        self.snapshots = snapshots.Snapshots()
        self.config = self.snapshots.config
        self.decode = None

        if len( sys.argv ) > 1:
            if not self.config.set_current_profile(sys.argv[1]):
                logger.warning("Failed to change Profile_ID %s"
                               %sys.argv[1], self)

        self.qapp = qt4tools.create_qapplication(self.config.APP_NAME)
        translator = qt4tools.get_translator()
        self.qapp.installTranslator(translator)
        self.qapp.setQuitOnLastWindowClosed(False)

        import icon
        self.icon = icon
        self.qapp.setWindowIcon(icon.BIT_LOGO)

        self.status_icon = QSystemTrayIcon(icon.BIT_LOGO)
        #self.status_icon.actionCollection().clear()
        self.contextMenu = QMenu()

        self.menuProfileName = self.contextMenu.addAction(_('Profile: "%s"') % self.config.get_profile_name())
        qt4tools.set_font_bold(self.menuProfileName)
        self.contextMenu.addSeparator()

        self.menuStatusMessage = self.contextMenu.addAction(_('Done'))
        self.menuProgress = self.contextMenu.addAction('')
        self.menuProgress.setVisible(False)
        self.contextMenu.addSeparator()

        self.btnDecode = self.contextMenu.addAction(icon.VIEW_SNAPSHOT_LOG, _('decode paths'))
        self.btnDecode.setCheckable(True)
        self.btnDecode.setVisible(self.config.get_snapshots_mode() == 'ssh_encfs')
        QObject.connect(self.btnDecode, SIGNAL('toggled(bool)'), self.onBtnDecode)

        self.openLog = self.contextMenu.addAction(icon.VIEW_LAST_LOG, _('View Last Log'))
        QObject.connect(self.openLog, SIGNAL('triggered()'), self.onOpenLog)
        self.startBIT = self.contextMenu.addAction(icon.BIT_LOGO, _('Start BackInTime'))
        QObject.connect(self.startBIT, SIGNAL('triggered()'), self.onStartBIT)
        self.status_icon.setContextMenu(self.contextMenu)

        self.pixmap = icon.BIT_LOGO.pixmap(24)
        self.progressBar = QProgressBar()
        self.progressBar.setMinimum(0)
        self.progressBar.setMaximum(100)
        self.progressBar.setValue(0)
        self.progressBar.setTextVisible(False)
        self.progressBar.resize(24, 6)
        self.progressBar.render(self.pixmap, sourceRegion = QRegion(0, -14, 24, 6), flags = QWidget.RenderFlags(QWidget.DrawChildren))

        self.first_error = self.config.is_notify_enabled()
        self.popup = None
        self.last_message = None

        self.timer = QTimer()
        QObject.connect( self.timer, SIGNAL('timeout()'), self.update_info )

        self.ppid = os.getppid()

    def prepare_exit( self ):
        self.timer.stop()

        if not self.status_icon is None:
            self.status_icon.hide()
            self.status_icon = None

        if not self.popup is None:
            self.popup.deleteLater()
            self.popup = None

        self.qapp.processEvents()

    def run( self ):
        self.status_icon.show()
        self.timer.start( 500 )

        logger.info("[qt4systrayicon] begin loop", self)

        self.qapp.exec_()

        logger.info("[qt4systrayicon] end loop", self)

        self.prepare_exit()

    def update_info( self ):
        if not tools.is_process_alive( self.ppid ):
            self.prepare_exit()
            self.qapp.exit(0)
            return

        message = self.snapshots.get_take_snapshot_message()
        if message is None and self.last_message is None:
            message = ( 0, _('Working...') )

        if not message is None:
            if message != self.last_message:
                self.last_message = message
                if self.decode:
                    message = (message[0], self.decode.log(message[1]))
                self.menuStatusMessage.setText('\n'.join(tools.wrap_line(message[1],\
                                                                         size = 80,\
                                                                         delimiters = '',\
                                                                         new_line_indicator = '') \
                                                                        ))
                self.status_icon.setToolTip(message[1])

        pg = progress.ProgressFile(self.config)
        if pg.isFileReadable():
            pg.load()
            percent = pg.get_int_value('percent')
            if percent != self.progressBar.value():
                self.progressBar.setValue(percent)
                self.progressBar.render(self.pixmap, sourceRegion = QRegion(0, -14, 24, 6), flags = QWidget.RenderFlags(QWidget.DrawChildren))
                self.status_icon.setIcon(QIcon(self.pixmap))

            self.menuProgress.setText(' | '.join(self.getMenuProgress(pg)) )
            self.menuProgress.setVisible(True)
        else:
            self.status_icon.setIcon(self.icon.BIT_LOGO)
            self.menuProgress.setVisible(False)


    def getMenuProgress(self, pg):
        d = (('sent',   _('Sent:')), \
             ('speed',  _('Speed:')),\
             ('eta',    _('ETA:')) )
        for key, txt in d:
            value = pg.get_str_value(key, '')
            if not value:
                continue
            yield txt + ' ' + value

    def onStartBIT(self):
        profileID = self.config.get_current_profile()
        cmd = ['backintime-qt4',]
        if not profileID == '1':
            cmd += ['--profile-id', profileID]
        proc = subprocess.Popen(cmd)

    def onOpenLog(self):
        dlg = logviewdialog.LogViewDialog(self, systray = True)
        dlg.decode = self.decode
        dlg.cb_decode.setChecked(self.btnDecode.isChecked())
        dlg.exec_()

    def onBtnDecode(self, checked):
        if checked:
            self.decode = encfstools.Decode(self.config)
            self.last_message = None
            self.update_info()
        else:
            self.decode = None
Exemple #3
0
class DocumentGeneratorDialog(QDialog, Ui_DocumentGeneratorDialog):
    """
    Dialog that enables a user to generate documents by using configuration
    information for different entities.
    """
    def __init__(self, iface, parent=None, plugin=None):
        QDialog.__init__(self, parent)
        self.setupUi(self)

        self._iface = iface
        self.plugin = plugin
        self._docTemplatePath = ""
        self._outputFilePath = ""
        self.curr_profile = current_profile()
        self.last_data_source = None
        self._config_mapping = OrderedDict()
        
        self._notif_bar = NotificationBar(self.vlNotification)

        self._doc_generator = DocumentGenerator(self._iface, self)

        self._data_source = ""

        enable_drag_sort(self.lstDocNaming)

        #Configure generate button
        generateBtn = self.buttonBox.button(QDialogButtonBox.Ok)
        if not generateBtn is None:
            generateBtn.setText(QApplication.translate("DocumentGeneratorDialog",
                                                       "Generate"))
        
        #Load supported image types
        supportedImageTypes = QImageWriter.supportedImageFormats()
        for imageType in supportedImageTypes:
            imageTypeStr = imageType.data()
            self.cboImageType.addItem(imageTypeStr)

        self._init_progress_dialog()
        #Connect signals
        self.btnSelectTemplate.clicked.connect(self.onSelectTemplate)
        self.buttonBox.accepted.connect(self.onGenerate)
        self.chkUseOutputFolder.stateChanged.connect(self.onToggledOutputFolder)
        self.rbExpImage.toggled.connect(self.onToggleExportImage)
        self.tabWidget.currentChanged.connect(self.on_tab_index_changed)
        self.chk_template_datasource.stateChanged.connect(self.on_use_template_datasource)

        self.btnShowOutputFolder.clicked.connect(self.onShowOutputFolder)


    def _init_progress_dialog(self):
        """
        Initializes the progress dialog.
        """
        self.progress = QProgressBar(self)
        self.progress.resize(self.width(), 10)
        self.progress.setTextVisible(False)

    def add_entity_configuration(self, **kwargs):
        ent_config = EntityConfig(**kwargs)
        self.add_entity_config(ent_config)


    def add_entity_config(self, ent_config, progress_value=0):
        QApplication.processEvents()
        if not self._config_mapping.get(ent_config.title(), ""):
            fk_mapper = self._create_fk_mapper(ent_config)
            self.tabWidget.addTab(fk_mapper, ent_config.title())
            self._config_mapping[ent_config.title()] = ent_config

            #Force list of column names to be loaded
            if self.tabWidget.currentIndex() != 0:
                self.tabWidget.setCurrentIndex(0)

            else:
                self.on_tab_index_changed(0)

        self.progress.setValue(progress_value)

    def on_tab_index_changed(self, index):
        if index == -1:
            return

        config = self.config(index)

        if not config is None:
            #Set data source name
            self._data_source = config.data_source()

    def on_use_template_datasource(self, state):
        if state == Qt.Checked:
            self.tabWidget.setEnabled(False)
            self.chkUseOutputFolder.setEnabled(False)
            self.chkUseOutputFolder.setChecked(False)

        elif state == Qt.Unchecked:
            self.tabWidget.setEnabled(True)
            self.chkUseOutputFolder.setEnabled(True)
            self.chkUseOutputFolder.setChecked(False)
            self.on_tab_index_changed(self.tabWidget.currentIndex())

    def onShowOutputFolder(self):
        reg_config = RegistryConfig()
        path = reg_config.read([COMPOSER_OUTPUT])
        output_path = path.get(COMPOSER_OUTPUT,'')

        # windows
        if sys.platform.startswith('win32'):
            os.startfile(output_path)

        # *nix systems
        if sys.platform.startswith('linux'):
            subprocess.Popen(['xdg-open', output_path])
        
        # macOS
        if sys.platform.startswith('darwin'):
            subprocess.Popen(['open', output_path])


    def notification_bar(self):
        """
        :return: Returns an instance of the notification bar.
        :rtype: NotificationBar
        """
        return self._notif_bar

    def config(self, index):
        """
        Returns the configuration for the current mapper in the tab widget.
        """
        tab_key = self.tabWidget.tabText(index)

        return self._config_mapping.get(tab_key, None)

    def current_config(self):
        """
        Returns the configuration corresponding to the current widget in the
        tab.
        """
        return self.config(self.tabWidget.currentIndex())

    def _load_model_columns(self, config):
        """
        Load model columns into the view for specifying file output name.
        Only those columns of display type variants will be
        used.
        """
        model_attr_mapping = OrderedDict()

        for c in config.data_source_columns():
            model_attr_mapping[c] = format_name(c)

        self.lstDocNaming.load_mapping(model_attr_mapping)

    def _load_data_source_columns(self, entity):
        """
        Load the columns of a data source for use in the file naming.
        """
        table_cols = entity_display_columns(entity, True)

        attr_mapping = OrderedDict()

        for c, header in table_cols.iteritems():
            attr_mapping[c] = header

        self.lstDocNaming.load_mapping(attr_mapping)

    def _create_fk_mapper(self, config):
        fk_mapper = ForeignKeyMapper(
            config.ds_entity,
            self.tabWidget,
            self._notif_bar,
            enable_list=True,
            can_filter=True,
            plugin=self.plugin
        )

        fk_mapper.setDatabaseModel(config.model())
        fk_mapper.setSupportsList(True)
        fk_mapper.setDeleteonRemove(False)
        fk_mapper.setNotificationBar(self._notif_bar)

        return fk_mapper
            
    def onSelectTemplate(self):
        """
        Slot raised to load the template selector dialog.
        """
        current_config = self.current_config()
        if current_config is None:
            msg = QApplication.translate(
                'DocumentGeneratorDialog',
                'An error occured while trying to determine the data source '
                'for the current entity.\nPlease check your current profile '
                'settings.'
            )
            QMessageBox.critical(
                self,
                QApplication.translate(
                    'DocumentGeneratorDialog',
                    'Template Selector'
                ),
                msg
            )
            return

        #Set the template selector to only load those templates that
        # reference the current data source.
        filter_table = current_config.data_source()
        templateSelector = TemplateDocumentSelector(
            self,
            filter_data_source=filter_table
        )

        if templateSelector.exec_() == QDialog.Accepted:
            docName,docPath = templateSelector.documentMapping()
            
            self.lblTemplateName.setText(docName)
            self._docTemplatePath = docPath
            if filter_table != self.last_data_source:
                #Load template data source fields
                self._load_template_datasource_fields()

    def _load_template_datasource_fields(self):
        #If using template data source
        template_doc, err_msg = self._doc_generator.template_document(self._docTemplatePath)
        if template_doc is None:
            QMessageBox.critical(self, "Error Generating documents", QApplication.translate("DocumentGeneratorDialog",
                                                "Error Generating documents - %s"%(err_msg)))

            return

        composer_ds, err_msg = self._doc_generator.composer_data_source(template_doc)

        if composer_ds is None:
            QMessageBox.critical(self, "Error Generating documents", QApplication.translate("DocumentGeneratorDialog",
                                                "Error Generating documents - %s"%(err_msg)))

            return

        #Load data source columns
        self._data_source = self.current_config().data_source()

        self.ds_entity = self.curr_profile.entity_by_name(
            self._data_source
        )

        self._load_data_source_columns(self.ds_entity)
            
    def onToggledOutputFolder(self,state):
        """
        Slot raised to enable/disable the generated output documents to be 
        written to the plugin composer output folder using the specified
        naming convention.
        """
        if state == Qt.Checked:
            self.gbDocNaming.setEnabled(True)
            
        elif state == Qt.Unchecked:
            self.gbDocNaming.setEnabled(False)
            
    def reset(self, success_status=False):
        """
        Clears/resets the dialog from user-defined options.
        """
        self._docTemplatePath = ""

        self._data_source = ""

        self.lblTemplateName.setText("")
        # reset form only if generation is successful
        if success_status:
            fk_table_view = self.tabWidget.currentWidget().\
                findChild(QTableView)
            while fk_table_view.model().rowCount() > 0:
                fk_table_view.model().rowCount(0)
                fk_table_view.model().removeRow(0)

            if self.tabWidget.count() > 0 and \
                    not self.chk_template_datasource.isChecked():
                self.on_tab_index_changed(0)

            if self.cboImageType.count() > 0:
                self.cboImageType.setCurrentIndex(0)
        
    def onToggleExportImage(self, state):
        """
        Slot raised to enable/disable the image formats combobox.
        """
        if state:
            self.cboImageType.setEnabled(True)

        else:
            self.cboImageType.setEnabled(False)

    def onGenerate(self):
        """
        Slot raised to initiate the certificate generation process.
        """
        self._notif_bar.clear()
        success_status = True
        config = self.current_config()
        self.last_data_source = config.data_source()
        if config is None:
            self._notif_bar.insertErrorNotification(QApplication.translate("DocumentGeneratorDialog", \
                                            "The entity configuration could not be extracted."))
            return
        
        #Get selected records and validate
        records = self.tabWidget.currentWidget().entities()

        if self.chk_template_datasource.isChecked():
            records = self._dummy_template_records()

        if len(records) == 0:
            self._notif_bar.insertErrorNotification(QApplication.translate("DocumentGeneratorDialog", \
                                                                          "Please load at least one entity record"))
            return
        
        if not self._docTemplatePath:
            self._notif_bar.insertErrorNotification(QApplication.translate("DocumentGeneratorDialog", \
                                                                          "Please select a document template to use"))
            return
        
        documentNamingAttrs = self.lstDocNaming.selectedMappings()

        if self.chkUseOutputFolder.checkState() == Qt.Checked and len(documentNamingAttrs) == 0:
            self._notif_bar.insertErrorNotification(QApplication.translate("DocumentGeneratorDialog", \
                                                "Please select at least one field for naming the output document"))

            return
        
        #Set output file properties
        if self.rbExpImage.isChecked():
            outputMode = DocumentGenerator.Image
            fileExtension = self.cboImageType.currentText()
            saveAsText = "Image File"

        else:
            outputMode = DocumentGenerator.PDF 
            fileExtension = "pdf"
            saveAsText = "PDF File"
            
        #Show save file dialog if not using output folder
        if self.chkUseOutputFolder.checkState() == Qt.Unchecked:
            docDir = source_document_location()
            
            if self._outputFilePath:
                fileInfo = QFileInfo(self._outputFilePath)
                docDir = fileInfo.dir().path()
                
            self._outputFilePath = QFileDialog.getSaveFileName(self,
                                                    QApplication.translate("DocumentGeneratorDialog",
                                                    "Save Document"),
                                                    docDir,
                                                    "{0} (*.{1})".format(
                                                    QApplication.translate("DocumentGeneratorDialog",
                                                                          saveAsText),
                                                    fileExtension))
            
            if not self._outputFilePath:
                self._notif_bar.insertErrorNotification(
                    QApplication.translate("DocumentGeneratorDialog",
                                "Process aborted. No output file was specified."))

                return
            
            #Include extension in file name
            self._outputFilePath = self._outputFilePath #+ "." + fileExtension
            
        else:
            #Multiple files to be generated.
            pass

        self._doc_generator.set_link_field(config.link_field())

        self._doc_generator.clear_attr_value_formatters()

        if not self.chk_template_datasource.isChecked():
            #Apply cell formatters for naming output files
            self._doc_generator.set_attr_value_formatters(config.formatters())

        entity_field_name = "id"
        
        #Iterate through the selected records
        progressDlg = QProgressDialog(self)
        progressDlg.setMaximum(len(records))

        try:
            QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))

            for i, record in enumerate(records):
                progressDlg.setValue(i)

                if progressDlg.wasCanceled():
                    success_status = False
                    break

                #User-defined location
                if self.chkUseOutputFolder.checkState() == Qt.Unchecked:
                    status,msg = self._doc_generator.run(self._docTemplatePath, entity_field_name,
                                                  record.id, outputMode,
                                                  filePath = self._outputFilePath)
                    self._doc_generator.clear_temporary_layers()
                #Output folder location using custom naming
                else:

                    status, msg = self._doc_generator.run(self._docTemplatePath, entity_field_name,
                                                    record.id, outputMode,
                                                    dataFields = documentNamingAttrs,
                                                    fileExtension = fileExtension,
                                                    data_source = self.ds_entity.name)
                    self._doc_generator.clear_temporary_layers()

                if not status:
                    result = QMessageBox.warning(self,
                                                 QApplication.translate("DocumentGeneratorDialog",
                                                                        "Document Generate Error"),
                                                 msg, QMessageBox.Ignore | QMessageBox.Abort)

                    if result == QMessageBox.Abort:
                        progressDlg.close()
                        success_status = False

                        #Restore cursor
                        QApplication.restoreOverrideCursor()

                        return

                    #If its the last record and user has selected to ignore
                    if i+1 == len(records):
                        progressDlg.close()
                        success_status = False

                        #Restore cursor
                        QApplication.restoreOverrideCursor()

                        return

                else:
                    progressDlg.setValue(len(records))

            QApplication.restoreOverrideCursor()

            QMessageBox.information(self,
                QApplication.translate("DocumentGeneratorDialog",
                                       "Document Generation Complete"),
                QApplication.translate("DocumentGeneratorDialog",
                                    "Document generation has successfully completed.")
                                    )

        except Exception as ex:
            LOGGER.debug(str(ex))
            err_msg = sys.exc_info()[1]
            QApplication.restoreOverrideCursor()

            QMessageBox.critical(
                self,
                "STDM",
                QApplication.translate(
                    "DocumentGeneratorDialog",
                    "Error Generating documents - %s"%(err_msg)
                )
            )
            success_status = False

        #Reset UI
        self.reset(success_status)

    def _dummy_template_records(self):
        """
        This is applied when records from a template data source are to be
        used to generate the documents where no related entity will be used
        to filter matching records in the data source. The iteration of the
        data source records will be done internally within the
        DocumentGenerator class.
        """
        class _DummyRecord:
            id = 1

        return [_DummyRecord()]

    def showEvent(self, event):
        """
        Notifies if there are not entity configuration objects defined.
        :param event: Window event
        :type event: QShowEvent
        """
        QTimer.singleShot(500, self.check_entity_config)

        return QDialog.showEvent(self, event)

    def check_entity_config(self):
        if len(self._config_mapping) == 0:
            self._notif_bar.clear()

            msg = QApplication.translate("DocumentGeneratorDialog", "Table "
                                                                    "configurations do not exist or have not been configured properly")
            self._notif_bar.insertErrorNotification(msg)
Exemple #4
0
class Qt4SysTrayIcon:
    def __init__(self):
        self.snapshots = snapshots.Snapshots()
        self.config = self.snapshots.config

        if len(sys.argv) > 1:
            if not self.config.set_current_profile(sys.argv[1]):
                logger.warning("Failed to change Profile_ID %s" % sys.argv[1],
                               self)

        self.qapp = qt4tools.create_qapplication(self.config.APP_NAME)

        import icon
        self.icon = icon
        self.qapp.setWindowIcon(icon.BIT_LOGO)

        self.status_icon = QSystemTrayIcon(icon.BIT_LOGO)
        #self.status_icon.actionCollection().clear()
        self.contextMenu = QMenu()

        self.menuProfileName = self.contextMenu.addAction(
            _('Profile: "%s"') % self.config.get_profile_name())
        qt4tools.set_font_bold(self.menuProfileName)
        self.contextMenu.addSeparator()

        self.menuStatusMessage = self.contextMenu.addAction(_('Done'))
        self.menuProgress = self.contextMenu.addAction('')
        self.menuProgress.setVisible(False)
        self.contextMenu.addSeparator()
        self.startBIT = self.contextMenu.addAction(icon.BIT_LOGO,
                                                   _('Start BackInTime'))
        QObject.connect(self.startBIT, SIGNAL('triggered()'), self.onStartBIT)
        self.status_icon.setContextMenu(self.contextMenu)

        self.pixmap = icon.BIT_LOGO.pixmap(24)
        self.progressBar = QProgressBar()
        self.progressBar.setMinimum(0)
        self.progressBar.setMaximum(100)
        self.progressBar.setValue(0)
        self.progressBar.setTextVisible(False)
        self.progressBar.resize(24, 6)
        self.progressBar.render(self.pixmap,
                                sourceRegion=QRegion(0, -14, 24, 6),
                                flags=QWidget.RenderFlags(
                                    QWidget.DrawChildren))

        self.first_error = self.config.is_notify_enabled()
        self.popup = None
        self.last_message = None

        self.timer = QTimer()
        QObject.connect(self.timer, SIGNAL('timeout()'), self.update_info)

        self.ppid = os.getppid()

    def prepare_exit(self):
        self.timer.stop()

        if not self.status_icon is None:
            self.status_icon.hide()
            self.status_icon = None

        if not self.popup is None:
            self.popup.deleteLater()
            self.popup = None

        self.qapp.processEvents()

    def run(self):
        self.status_icon.show()
        self.timer.start(500)

        logger.info("[qt4systrayicon] begin loop", self)

        self.qapp.exec_()

        logger.info("[qt4systrayicon] end loop", self)

        self.prepare_exit()

    def update_info(self):
        if not tools.is_process_alive(self.ppid):
            self.prepare_exit()
            self.qapp.exit(0)
            return

        message = self.snapshots.get_take_snapshot_message()
        if message is None and self.last_message is None:
            message = (0, _('Working...'))

        if not message is None:
            if message != self.last_message:
                self.last_message = message
                self.menuStatusMessage.setText('\n'.join(tools.wrap_line(self.last_message[1],\
                                                                         size = 80,\
                                                                         delimiters = '',\
                                                                         new_line_indicator = '') \
                                                                        ))
                self.status_icon.setToolTip(self.last_message[1])

        pg = progress.ProgressFile(self.config)
        if pg.isFileReadable():
            pg.load()
            percent = pg.get_int_value('percent')
            if percent != self.progressBar.value():
                self.progressBar.setValue(percent)
                self.progressBar.render(self.pixmap,
                                        sourceRegion=QRegion(0, -14, 24, 6),
                                        flags=QWidget.RenderFlags(
                                            QWidget.DrawChildren))
                self.status_icon.setIcon(QIcon(self.pixmap))

            self.menuProgress.setText(' | '.join(self.getMenuProgress(pg)))
            self.menuProgress.setVisible(True)
        else:
            self.status_icon.setIcon(self.icon.BIT_LOGO)
            self.menuProgress.setVisible(False)

    def getMenuProgress(self, pg):
        d = (('sent',   _('Sent:')), \
             ('speed',  _('Speed:')),\
             ('eta',    _('ETA:')) )
        for key, txt in d:
            value = pg.get_str_value(key, '')
            if not value:
                continue
            yield txt + ' ' + value

    def onStartBIT(self):
        profileID = self.config.get_current_profile()
        cmd = [
            'backintime-qt4',
        ]
        if not profileID == '1':
            cmd += ['--profile-id', profileID]
        proc = subprocess.Popen(cmd)
Exemple #5
0
class SegmentationClassificationWindow(SoundLabWindow, Ui_MainWindow):
    """

    Window that process the segmentation and classification of a signal
    Contains a QSignalDetectorWidget that wrapper several functionality
    Allows to select the segmentation and classification settings,
    and parameter measurement for detected segments.
    Provides a table for visualization of segment and measures,
    A two dimensional window to graph two measured params. One for each axis.
    Options for selection and visualization of segments
    Provides options for save the parameters to excel.
    """

    # region CONSTANTS

    # sort type of the parameter table
    PARAMETER_SORT_NONE, PARAMETER_SORT_BY_LOCATION, PARAMETER_SORT_BY_PARAMETER = range(
        3)

    # different colors for the even and odds rows in the parameter table and segment colors.
    TABLE_ROW_COLOR_ODD = QColor(0, 0, 255, 150)
    TABLE_ROW_COLOR_EVEN = QColor(0, 255, 0, 150)

    # stylesheet to use on excel file saved
    EXCEL_STYLE_HEADER = xlwt.easyxf(
        'font: name Times New Roman, color-index black, bold on, height 300')
    EXCEL_STYLE_BODY = xlwt.easyxf(
        'font: name Times New Roman, color-index black, height 220',
        num_format_str='#,# # 0.00')
    EXCEL_STYLE_COPYRIGHT = xlwt.easyxf(
        'font: name Arial, color-index pale_blue, height 250, italic on',
        num_format_str='# ,# # 0.00')

    # endregion

    # region Initialize

    def __init__(self, parent=None, signal=None, workspace=None):
        """
        Create a the window of segmentation and classification.
        :param parent:    the parent widget if any
        :param signal:    the signal to visualize for segmentation and classification
        :param workspace: the workspace with specgram and oscilogram generation options
        :return:
        """

        # set the visual variables and methods from ancestor's
        SoundLabWindow.__init__(self, parent)
        self.setupUi(self)

        # check the parameters
        if signal is None or not isinstance(signal, AudioSignal):
            raise Exception(
                "The signal to analyze must be of type AudioSignal")

        self.configureToolBarActionsGroups()

        # the segmentation window do not allow to record a signal (for now...)
        self.actionRecord.setEnabled(False)

        # the object that handles the measuring of parameters and manage the segments
        self.segmentManager = SegmentManager()
        self.segmentManager.measurementsFinished.connect(
            self.measurement_finished)
        self.segmentManager.detectionProgressChanged.connect(
            lambda x: self.windowProgressDetection.setValue(x * 0.9))
        self.segmentManager.segmentVisualItemAdded.connect(
            self.widget.add_parameter_visual_items)
        self.segmentManager.segmentationFinished.connect(
            self.segmentation_finished)

        # the factory of adapters for parameters to supply to the ui window or segmentation dialog
        self.parameter_manager = MeasurementTemplate(signal=signal)

        # set the signal to the widget
        self.widget.signal = signal
        self.segmentManager.signal = signal

        # set the configurations for the name of the signal and the visible label
        self.updateSignalPropertiesLabel(signal)
        self.signalNameLineEdit.setReadOnly(True)
        self.actionSignalName.setText(self.widget.signalName)

        # set visible the two graphs by default
        self.changeWidgetsVisibility(True, True)

        # shift of the table parameters rows. If the table visualization isn't expanded
        # the first row are the name of the parameters and the second one the locations
        self.parameter_table_row_shift = 2

        # connect the signals on the widget for new detected data by its tools
        # and to select the element in the table. Binding the element click to the table
        self.widget.toolDataDetected.connect(self.update_status_bar)
        self.widget.elementClicked.connect(self.select_element)

        self.tableParameterOscilogram.setSelectionBehavior(
            QAbstractItemView.SelectRows)
        self.tableParameterOscilogram.setEditTriggers(
            QAbstractItemView.NoEditTriggers)
        self.dockWidgetParameterTableOscilogram.setVisible(False)
        self.tableParameterOscilogram.setSortingEnabled(False)

        # connect the table parameter sort type actions
        self.actionGroupNone.triggered.connect(
            self.groupTableParametersChanged)
        self.actionGroupByParameter.triggered.connect(
            self.groupTableParametersChanged)
        self.actionGroupByLocation.triggered.connect(
            self.groupTableParametersChanged)

        # configuration variable for the parameter table visualization sort
        self.parameter_table_sort_type = self.PARAMETER_SORT_BY_PARAMETER

        # add the context menu actions to the widget
        self.__addContextMenuActions()

        # create the progress bar that is showed while the detection is made
        self.windowProgressDetection = QProgressBar(self)
        self.set_progress_bar_visibility(False)

        # array of windows with two dimensional graphs
        self.two_dim_windows = []
        self._cross_correlation_windows = []

        if workspace is not None:
            self.load_workspace(workspace)

        if parent is not None:
            # show window in the center of the parent
            parent_x, parent_y = parent.x(), parent.y()
            parent_width, parent_height = parent.width(), parent.height()
            a = str('a')
            window_x = parent_x + (parent_width - self.width()) / 2
            window_y = parent_y + (parent_height - self.height()) / 2
            self.move(window_x, window_y)

        self.show()

        self.try_restore_previous_session()

    def try_restore_previous_session(self):
        """
        Restore (if any) the previous session with this file.
        That means detected elements, measured parameters etc that are saved on the signal
        extra data.
        :return:
        """
        elements = self.widget.get_signal_segmentation_data()

        if len(elements) == 0:
            return

        buttons_box = QMessageBox.Yes | QMessageBox.No
        mbox = QMessageBox(
            QMessageBox.Question, self.tr(u"soundLab"),
            self.
            tr(u"The file has segmentation data stored. Do you want to load it?"
               ), buttons_box, self)
        result = mbox.exec_()

        if result == QMessageBox.Yes:
            for i, e in enumerate(elements):
                self.segmentManager.add_element(i, e[0], e[1])

            self.widget.elements = self.segmentManager.elements
            self.segmentManager.measure_parameters_and_classify()
            self.widget.graph()

    def __addContextMenuActions(self):
        """
        Add the context menu actions into the widget in the creation process of the window
        :return:
        """
        separator, separator1, separator2, separator3, separator4 = [
            QAction(self) for _ in xrange(5)
        ]

        for sep in [separator, separator1, separator2, separator3, separator4]:
            sep.setSeparator(True)

        # region Context Menu Actions

        self.widget.createContextCursor([
            # parameters manipulation actions
            self.actionMeditions,
            self.actionView_Parameters,
            self.actionAddElement,
            separator2,

            # Zoom Actions
            self.actionZoomIn,
            self.actionZoom_out,
            self.actionZoom_out_entire_file,
            separator1,

            # widgets visibility actions
            self.actionCombined,
            self.actionOscilogram,
            self.actionSpectogram,
            separator,

            # change tools actions
            self.actionZoom_Cursor,
            self.actionPointer_Cursor,
            self.actionRectangular_Cursor,
            separator3,

            # elements select/deselect
            self.actionDeselect_Elements,
            self.actionDelete_Selected_Elements,
            self.actionSelectedElement_Correlation,
            self.actionClassify,
            separator4
        ])
        # endregion
        # add the action to switch visualization mode on  the parameter table
        self.tableParameterOscilogram.setContextMenuPolicy(
            QtCore.Qt.ActionsContextMenu)

        parameter_tables_action_group = QActionGroup(
            self.tableParameterOscilogram)
        actions = [
            self.actionGroupNone, self.actionGroupByLocation,
            self.actionGroupByParameter
        ]

        for act in actions:
            parameter_tables_action_group.addAction(act)
            self.tableParameterOscilogram.addAction(act)

    def configureToolBarActionsGroups(self):
        """
        :return:
        """
        sep = QAction(self)
        sep.setSeparator(True)

        # region Segmentation and Transformations actions
        segm_transf_actions = QActionGroup(self)
        segm_transf_actions_list = [
            self.actionDetection, self.actionTwo_Dimensional_Graphs,
            self.actionDelete_Selected_Elements, self.actionDeselect_Elements,
            self.actionAddElement, self.actionParameter_Measurement, sep
        ]

        for act in segm_transf_actions_list:
            act.setActionGroup(segm_transf_actions)

        # endregion

        # add to the customizable sound lab toolbar first than the default actions
        # addActionGroup(actionGroup, name)
        self.toolBar.addActionGroup(segm_transf_actions,
                                    u"Segments and Parameters")

        # apply the common sound lab window toolbar actions
        SoundLabWindow.configureToolBarActionsGroups(self)

    # endregion

    # region Two Dimensional Graphs

    @pyqtSlot()
    def on_actionTwo_Dimensional_Graphs_triggered(self):
        """
        Creates a new two dimensional window for analysis.
        :return:
        """
        # a two dim window must be created after
        # segment detection and parameters measurement
        if self.segmentManager.rowCount == 0 or len(
                self.segmentManager.parameterColumnNames) == 0:
            QMessageBox.warning(
                QMessageBox(), self.tr(u"Error"),
                self.tr(
                    u"The two dimensional analyses window requires at least "
                    u"one detected element with one parameter measurement."))
            return

        wnd = TwoDimensionalAnalisysWindow(self, self.segmentManager)

        # connect the signals for update the new two dim window actions
        wnd.elementSelected.connect(self.select_element)
        wnd.elementsClassified.connect(
            lambda indexes_list, classification: self.segmentManager.
            set_manual_elements_classification(indexes_list, classification))

        # load the workspace in the new two dimensional window
        if self.workSpace:
            wnd.load_workspace(self.workSpace)

        # if any previous windows was opened then update in the new one the selected element
        if len(self.two_dim_windows) > 0:
            wnd.select_element(self.two_dim_windows[0].selectedElementIndex)

        # add the new window to the current opened windows
        self.two_dim_windows.append(wnd)

    def clear_two_dim_windows(self):
        """
        Close the two dimensional windows and clear the list of two dim windows
        :return:
        """
        # close the opened windows
        for window in self.two_dim_windows:
            window.close()
            del window

        # clear the list of two dimensional windows
        self.two_dim_windows = []

    # endregion

    # region Save Measurements

    @pyqtSlot()
    def on_actionMeditions_triggered(self):
        """
        Save to disc the measurement made by the window to the elements detected.
        :param name: The name of the file to save the data
        :param table: The table with the parameter to save into excel
        :return: True if successfully False otherwise
        """
        file_name = unicode(
            QFileDialog.getSaveFileName(
                self, self.tr(u"Save parameters as excel file"),
                os.path.join(self.workSpace.lastOpenedFolder,
                             str(self.widget.signalName) + ".xls"), "*.xls"))
        # save the data of table
        if not file_name:
            return False

        # if successfully selected the path save the excel book of data
        try:

            wb = xlwt.Workbook()
            ws = wb.add_sheet("Elements Measurements")
            self.write_data(ws, self.tableParameterOscilogram)
            wb.save(file_name)

            self.segmentManager.save_data_on_db()

        except Exception as ex:
            print("Error saving the excel file. " + ex.message)
            return False

        # open user manual file
        os.system("start " + file_name)
        return True

    @pyqtSlot()
    def on_actionSound_File_Segmentation_triggered(self):
        """
        Save the signal into file. Store the segmentation values
        (list of tuples start,end with the indexes of detected element)
        :return:
        """
        if self.segmentManager.rowCount == 0:
            return

        self.widget.save_segments_into_signal()

        # save the signal to file
        if self.widget.signalFilePath:
            self.widget.save()
        else:
            file_name = unicode(
                QFileDialog.getSaveFileName(
                    self, self.tr(u"Save signal"),
                    os.path.join(self.workSpace.lastOpenedFolder,
                                 str(self.widget.signalName)), u"*.wav"))
            if file_name:
                self.widget.save(file_name)
                self.widget.signalFilePath = file_name

    def set_signal_file(self, file_path=''):
        """
        Update the data of the current signal file path origin in the widget
        (if any)
        :param file_path:
        :return:
        """
        self.widget.signalFilePath = file_path

    def write_data(self, ws, table_parameter):
        """
        Write the data from the table into an excel file stylesheet.
        :param ws:WorkSheet object from xwlt module for interacts with excell files.
        :param table_parameter: QTableWidget with the information of the data to save.
        """
        # write headers into the document
        headers = []
        for pos in xrange(table_parameter.columnCount()):
            header_item = table_parameter.takeHorizontalHeaderItem(pos)
            if header_item is not None:
                headers.append(str(header_item.text()))

        for index, header in enumerate(headers):
            ws.write(0, index, header, self.EXCEL_STYLE_HEADER)

        # write data into the document
        for i in xrange(1, table_parameter.model().rowCount() + 1):
            for j in xrange(table_parameter.model().columnCount()):
                item = table_parameter.item(i - 1, j)
                if item is not None:
                    cell_data = str(item.data(Qt.DisplayRole).toString())

                    ws.write(i, j, cell_data, self.EXCEL_STYLE_BODY)

        # ws object must be part of a Work book that would be saved later
        ws.write(table_parameter.model().rowCount() + 3, 0,
                 unicode(self.tr(u"duetto-Sound Lab")),
                 self.EXCEL_STYLE_COPYRIGHT)

    # endregion

    # region Close and Exit

    @pyqtSlot()
    def on_actionExit_triggered(self):
        """
        Process the exit action requested by the user.
        :return:
        """
        self.close()

    def closeEvent(self, event):
        """
        Event that manages the close of the window.
        Intercepts the close event for save changes.
        :param event:
        :return:
        """

        mbox = QMessageBox(
            QMessageBox.Question, self.tr(u"Save measurements"),
            self.tr(u"Do you want to save the parameters of " +
                    unicode(self.widget.signalName)),
            QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel, self)

        # if the signal was playing must be stopped
        self.widget.stop()
        self.segmentManager.stop_processing()

        # if there is a measurement made and parameters measured that could be saved
        if self.tableParameterOscilogram.rowCount() > 0:

            result = mbox.exec_()

            # check if the user decision about save is discard and continue working
            if result == QMessageBox.Cancel or (
                    result == QMessageBox.Yes
                    and not self.on_actionMeditions_triggered()):
                event.ignore()
                return

            self.clear_two_dim_windows()

    # endregion

    # region Elements Selection

    @pyqtSlot()
    def on_actionDelete_Selected_Elements_triggered(self):
        """
        Delete the element under selection.
        the selection is the area under the zoom cursor if the zoom cursor is selected
        or the visible area otherwise.
        :return:
        """

        # delete the elements on the widget and get the indexes for update
        deleted_elements = self.widget.selected_elements_interval()

        self.widget.delete_selected_elements()

        if deleted_elements is None:
            return

        start_removed_index, end_removed_index = deleted_elements

        if start_removed_index is not None and start_removed_index >= 0 \
           and end_removed_index < self.segmentManager.rowCount:

            # updates the detected elements
            self.segmentManager.delete_elements(start_removed_index,
                                                end_removed_index)

            for wnd in self.two_dim_windows:
                wnd.update_data(self.segmentManager)

        # deselect the elements on the widget
        self.on_actionDeselect_Elements_triggered()

    @pyqtSlot()
    def on_actionDelete_All_triggered(self):
        """
        Removes all the detected elements on the widget
        :return:
        """
        # clear the segments
        self.segmentManager.elements = []

        # removes the widget elements
        self.widget.elements = []
        self.widget.graph()

    @pyqtSlot()
    def on_actionAddElement_triggered(self):
        """
        Try to add the selected region on the widget as a new element
        Performs the manual segmentation.
        :return:
        """
        element_added_index = self.widget.mark_region_as_element()

        if element_added_index is None:
            return

        element = self.widget.get_element(element_added_index)

        self.segmentManager.add_element(element_added_index, element.indexFrom,
                                        element.indexTo)

        self.update_two_dim_windows()

        self.widget.graph()

        self.__set_parameter_window_visible()

    @pyqtSlot()
    def on_actionDeselect_Elements_triggered(self):
        """
        Deselects the selected element in the widget and in the
        two dimensional windows opened.
        :return:
        """
        self.widget.deselect_element()

        for wnd in self.two_dim_windows:
            wnd.deselect_element()

        # the table remains equal as before for efficiency (do not update for deselect)

    def select_element(self, element_index, column=0):
        """
        Callback that is executed for update the element that is selected.
        An element is selected by the user ad must be updated in all visible representations
        like table parameter, two dimensional windows, and graphs.
        :param element_index: index of the element selected
        :param column: parameter provided to reuse this method as callback of
        the event selected cell in the QTableWidget. Useless in this application context.
        """
        # select the element in the table of parameters
        self.tableParameterOscilogram.selectRow(element_index +
                                                self.parameter_table_row_shift)

        # in the widget...
        self.widget.select_element(element_index)

        # in the opened two dimensional windows...
        for wnd in self.two_dim_windows:
            wnd.select_element(element_index)

        # and in the cross-correlation windows
        for i in xrange(len(self._cross_correlation_windows)):
            wnd = self._cross_correlation_windows[i]
            if wnd.isHidden():
                del self._cross_correlation_windows[i]
            else:
                wnd.select_element(element_index)

        # show the image if the element is classified
        try:
            classification = self.segmentManager.get_segment_classification(
                element_index)
            image = classification.get_image()
            if image:
                self.show_image_on_element(element_index, image)

            self.update_status_bar(classification.get_full_description())

        except Exception as ex:
            pass

    @pyqtSlot()
    def on_actionSelectedElement_Correlation_triggered(self):
        signal = self.widget.selected_element_signal()
        if not signal:
            QMessageBox.warning(QMessageBox(), self.tr(u"Error"),
                                self.tr(u"There is no selected element."))
            return

        dialog = CrossCorrelationDialog(self, self.widget, signal,
                                        self.TABLE_ROW_COLOR_ODD,
                                        self.TABLE_ROW_COLOR_EVEN)

        self._cross_correlation_windows.append(dialog)
        dialog.elementSelected.connect(self.select_element)
        dialog.show()

    @pyqtSlot()
    def on_actionClassify_triggered(self):
        """
        Open the classification dialog for update the categories and values
        in which could be classified a segment.
        """
        # create and open the dialog to edit the classification categories
        selection = self.widget.selected_elements_interval()

        if selection is None:
            QMessageBox.warning(QMessageBox(), self.tr(u"Warning"),
                                self.tr(u"There is no selection made."))
            return

        index_from, index_to = selection
        classification_dialog = ManualClassificationDialog()

        if classification_dialog.exec_():
            classification = classification_dialog.get_classification()
            self.segmentManager.set_manual_elements_classification(
                xrange(index_from, index_to + 1), classification)

    # endregion

    # region Classification

    def show_image_on_element(self, element_index, image):
        """
        show a toast with the specie image (if any and if is identified) of
        the element at element index position
        :param element_index:
        :return:
        """
        toast = ToastWidget(self, back_image=image, width=100, heigth=100)

        element = self.segmentManager.elements[element_index]
        min_x, max_x = self.widget.get_visible_region()

        x = element.indexFrom - min_x + (element.indexTo -
                                         element.indexFrom) / 2.0
        x = x * self.widget.width() * 1.0 / (max_x - min_x)

        toast.move(
            self.widget.mapToGlobal(
                QPoint(x - toast.width() / 2.0,
                       (self.widget.height() - toast.height()) / 2.0)))

        toast.disappear()

    @pyqtSlot()
    def on_actionCross_correlation_triggered(self):
        """
        Opens the cross-correlation dialog (after selecting a file containing a reference segment) to check each
        segment's match with a reference segment.
        """
        file_name = QFileDialog.getOpenFileName(
            self,
            self.tr(u"Select the file of a reference segment"),
            directory=self.workSpace.lastOpenedFile,
            filter=self.tr(u"Wave Files") + u"(*.wav);;All Files(*)")
        if file_name:
            dialog = CrossCorrelationDialog(self, self.widget, file_name,
                                            self.TABLE_ROW_COLOR_ODD,
                                            self.TABLE_ROW_COLOR_EVEN)

            self._cross_correlation_windows.append(dialog)
            dialog.elementSelected.connect(self.select_element)
            dialog.show()

    # endregion

    # region Detection

    def set_progress_bar_visibility(self, visibility=True):
        """
        Show the progress bar in the middle of the widget.
        Used when a high time demanding task is going to be made to
        show to the user it's progress.
        :return:
        """
        if not visibility:
            self.windowProgressDetection.setVisible(False)

        else:
            width, height = self.width(), self.height()
            x, y = self.widget.x(), self.widget.y()
            progress_bar_height = height / 20.0

            self.windowProgressDetection.resize(width / 3.0,
                                                progress_bar_height)
            self.windowProgressDetection.move(
                x + width / 3.0, y + height / 2.0 + progress_bar_height / 2.0)
            self.windowProgressDetection.setVisible(True)

        self.repaint()

    @pyqtSlot()
    def on_actionDetection_triggered(self):
        """
        Method that execute the detection
        """

        # there is an on going detection been made
        if self.windowProgressDetection.isVisible():
            QMessageBox.warning(
                QMessageBox(), self.tr(u"Error"),
                self.tr(u"There is an on going detection in progress."))
            return

        elementsDetectorDialog = ElemDetectSettingsDialog(
            self, self.widget.signal, self.segmentManager)
        elementsDetectorDialog.modifyParametersMeasurement.connect(
            lambda: self.on_actionParameter_Measurement_triggered())
        elementsDetectorDialog.load_workspace(self.workSpace)

        try:
            if elementsDetectorDialog.exec_():
                # the detection dialog is a factory of segmentation,
                # parameter parameters and classification concrete implementations

                # get the segmentation, classification and parameters methods
                self.segmentManager.detector_adapter = elementsDetectorDialog.detector
                self.segmentManager.classifier_adapter = elementsDetectorDialog.classifier
                self.segmentManager.parameters = self.parameter_manager.parameter_list(
                )

                self.windowProgressDetection.setValue(0)
                self.set_progress_bar_visibility(True)

                # execute the detection
                self.segmentManager.detect_elements()

        except Exception as e:
            print("detection errors: " + e.message)

            self.windowProgressDetection.setValue(100)
            self.set_progress_bar_visibility(False)

    def segmentation_finished(self):
        """
        Callback to execute when the segmentation segmentation_thread finished.
        :return:
        """
        self.windowProgressDetection.setValue(90)

        # put the elements detected into the widget to visualize them
        self.widget.elements = self.segmentManager.elements

        # shows the detection elements first and later the measurements as lazy load
        self.widget.draw_elements()

        self.windowProgressDetection.setValue(100)
        self.set_progress_bar_visibility(False)

        # measure the parameters over elements detected
        QTimer.singleShot(50,
                          self.segmentManager.measure_parameters_and_classify)

    def measurement_finished(self):
        """
        Callback to execute when the measurement has finished.
        :return:
        """
        # must be refreshed the widget because the parameter measurement
        # may include visual items into the graph
        self.widget.draw_elements()
        self.update_two_dim_windows()
        self.update_parameter_table()
        self.__set_parameter_window_visible()

    def __set_parameter_window_visible(self):
        # set the parameter window to visible state after measurements
        if not self.tableParameterOscilogram.isVisible():
            self.actionView_Parameters.setChecked(True)
            self.dockWidgetParameterTableOscilogram.setVisible(True)

    def update_two_dim_windows(self):
        # update the measured data on the two dimensional opened windows
        for wnd in self.two_dim_windows:
            wnd.load_data(self.segmentManager)

    def groupTableParametersChanged(self):

        grouped_params = self.actionGroupByParameter.isChecked()
        grouped_locations = self.actionGroupByLocation.isChecked()
        grouped_none = self.actionGroupNone.isChecked()

        self.parameter_table_row_shift = 0 if grouped_none else 2

        if grouped_none:
            self.parameter_table_sort_type = self.PARAMETER_SORT_NONE

        elif grouped_params:
            self.parameter_table_sort_type = self.PARAMETER_SORT_BY_PARAMETER

        elif grouped_locations:
            self.parameter_table_sort_type = self.PARAMETER_SORT_BY_LOCATION

        self.update_parameter_table()
        self.on_actionDeselect_Elements_triggered()

    # endregion

    # region Parameter Table

    def _group_measurements(self, parameters):
        """
        group the measurements for display in table
        :param parameters: the parameters measurements to group
        :return: list of groups (list) grouped by location or parameter
        accord to the instance variable  parameter_table_sort_type
        """
        groups = []

        def group_item_selector(param):
            if self.parameter_table_sort_type == self.PARAMETER_SORT_BY_LOCATION:
                return "-" if param.time_location is None else param.time_location.name
            return param.name

        param_positions = [(parameters[i], i) for i in xrange(len(parameters))]

        comparer = lambda x, y: cmp(group_item_selector(x[0]),
                                    group_item_selector(y[0]))

        param_positions.sort(cmp=comparer)

        index = 0
        while index < len(param_positions):
            current_group = []
            current_item = group_item_selector(param_positions[index][0])

            while index < len(parameters) and group_item_selector(
                    param_positions[index][0]) == current_item:
                current_group.append(param_positions[index][0])
                current_item = group_item_selector(param_positions[index][0])
                index += 1

            groups.append(current_group)

        return groups, [t[1] for t in param_positions]

    def _get_table_widget_item_centered(self, text):
        """
        return a TableWidgetItem with the text supplied and centered vertical and  horizontally
        :param text: text of the item
        :return:
        """
        item = QTableWidgetItem(unicode(text))
        item.setTextAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
        return item

    def __fill_parameter_table_measurements(self, param_indexes):
        """
        Fill the values of the table parameters using the param indexes
        from the parameter sort defined by user. Is a helper method of the update parameter table
        :param param_indexes:
        :return:
        """

        # update every measurement [row, column] position
        for i in xrange(self.segmentManager.rowCount):
            for j in xrange(self.segmentManager.columnCount):
                # the last columns aren't for parameters but for classification
                param_index = param_indexes[j] if j < len(param_indexes) else j

                item = self._get_table_widget_item_centered(
                    self.segmentManager[i, param_index])

                # color options for the rows of the table
                item.setBackgroundColor(self.TABLE_ROW_COLOR_ODD if
                                        (i + self.parameter_table_row_shift) %
                                        2 == 0 else self.TABLE_ROW_COLOR_EVEN)

                self.tableParameterOscilogram.setItem(
                    i + self.parameter_table_row_shift, j, item)

    def __fill_parameter_table_grouped_columns_headers(self, params_groups,
                                                       indexes):
        # set the span value to group the param names columns and their names
        column_index = 0

        parameters = []
        for g in params_groups:
            parameters.extend(g)

        for j in xrange(len(parameters)):
            if self.parameter_table_sort_type == self.PARAMETER_SORT_BY_LOCATION:
                text = parameters[j].name
            else:
                text = "-" if parameters[j].time_location is None else unicode(
                    parameters[j].time_location.name)

            item = self._get_table_widget_item_centered(text)
            self.tableParameterOscilogram.setItem(1, j, item)

        # set the group names on the row 0
        for param_group in params_groups:
            span_size = len(param_group)
            if span_size > 1:
                self.tableParameterOscilogram.setSpan(0, column_index, 1,
                                                      span_size)

            if self.parameter_table_sort_type == self.PARAMETER_SORT_BY_PARAMETER:
                text = param_group[0].name
            else:
                text = "-" if param_group[0].time_location is None else unicode(
                    param_group[0].time_location.name)

            item = self._get_table_widget_item_centered(text)
            self.tableParameterOscilogram.setItem(0, column_index, item)
            column_index += span_size

        # put the classification category headers
        for index, category_classif in enumerate(
                self.segmentManager.classificationColumnNames):
            item = self._get_table_widget_item_centered(category_classif)
            self.tableParameterOscilogram.setItem(0, column_index + index,
                                                  item)

    def update_parameter_table(self):
        """
        Method that updates the parameter table to visualize
        the data of the detected segments, their measured parameters and classification
        :return:
        """
        parameters = self.segmentManager.parameters

        self.tableParameterOscilogram.clear()
        self.tableParameterOscilogram.setRowCount(
            self.segmentManager.rowCount + self.parameter_table_row_shift)
        self.tableParameterOscilogram.setColumnCount(
            self.segmentManager.columnCount)

        header_labels = self.segmentManager.columnNames

        if self.parameter_table_sort_type != self.PARAMETER_SORT_NONE:
            # get the list of parameters grouped by type
            params_groups, indexes = self._group_measurements(parameters)

            header_labels = ["" for _ in self.segmentManager.columnNames]

            # set the vertical names of rows. Parameters on first row and locations on second
            vertical_headers = [
                "Parameter" if s == 0 else
                "Location" if s == 1 else str(s -
                                              self.parameter_table_row_shift +
                                              1)
                for s in xrange(self.segmentManager.rowCount +
                                self.parameter_table_row_shift)
            ]

            if self.parameter_table_sort_type == self.PARAMETER_SORT_BY_LOCATION:
                swap = vertical_headers[0]
                vertical_headers[0] = vertical_headers[1]
                vertical_headers[1] = swap

            self.tableParameterOscilogram.setVerticalHeaderLabels(
                vertical_headers)
            self.__fill_parameter_table_grouped_columns_headers(
                params_groups, indexes)
            self.__fill_parameter_table_measurements(indexes)

        else:
            self.__fill_parameter_table_measurements(xrange(len(parameters)))

        self.tableParameterOscilogram.setHorizontalHeaderLabels(header_labels)

        # connect the table selection with the selection of an element
        cell_pressed_element = lambda index, col: self.select_element(
            index - self.parameter_table_row_shift, col)
        self.tableParameterOscilogram.cellPressed.connect(cell_pressed_element)

        self.tableParameterOscilogram.resizeColumnsToContents()
        self.tableParameterOscilogram.resizeRowsToContents()

    # endregion

    # region Visual Elements

    @pyqtSlot()
    def on_actionView_Parameters_triggered(self):
        """
        Changes the visibility on the window of the parameter table.
        The parameter table is where the detected segments and its measured parameters
        are displayed.
        """
        self.dockWidgetParameterTableOscilogram.setVisible(
            self.actionView_Parameters.isChecked())

    @pyqtSlot()
    def on_actionTemporal_Elements_triggered(self):
        """
        Temporal Elements are the elements that are visible on the oscilogram graph.
        This method allows to change its visibility
        """
        visibility = self.actionTemporal_Elements.isChecked()

        self.widget.visual_items_visibility.oscilogram_items_visible = visibility
        self.widget.draw_elements()

        for x in [
                self.actionTemporal_Figures, self.actionTemporal_Numbers,
                self.actionTemporal_Parameters
        ]:
            x.setEnabled(visibility)

    @pyqtSlot()
    def on_actionTemporal_Figures_triggered(self):
        self.widget.visual_items_visibility.oscilogram_figures_visible = self.actionTemporal_Figures.isChecked(
        )
        self.widget.draw_elements()

    @pyqtSlot()
    def on_actionTemporal_Parameters_triggered(self):
        self.widget.visual_items_visibility.oscilogram_parameters_visible = self.actionTemporal_Parameters.isChecked(
        )
        self.widget.draw_elements()

    @pyqtSlot()
    def on_actionTemporal_Numbers_triggered(self):
        """
        Change visibility of the numbers of the detected segments on the oscilogram graph

        """
        self.widget.visual_items_visibility.oscilogram_text_visible = self.actionTemporal_Numbers.isChecked(
        )
        self.widget.draw_elements()

    @pyqtSlot()
    def on_actionSpectral_Elements_triggered(self):
        """
        Spectral Elements are the elements that are visible on the spectrogram graph.
        This method allows to change its visibility
        """
        visibility = self.actionSpectral_Elements.isChecked()

        self.widget.visual_items_visibility.spectrogram_items_visible = visibility
        self.widget.draw_elements()

        for x in [
                self.actionSpectral_Numbers, self.actionSpectral_Figures,
                self.actionSpectral_Parameters
        ]:
            x.setEnabled(visibility)

    @pyqtSlot()
    def on_actionSpectral_Figures_triggered(self):
        self.widget.visual_items_visibility.spectrogram_figures_visible = self.actionSpectral_Figures.isChecked(
        )
        self.widget.draw_elements()

    @pyqtSlot()
    def on_actionSpectral_Parameters_triggered(self):
        self.widget.visual_items_visibility.spectrogram_parameters_visible = self.actionSpectral_Parameters.isChecked(
        )
        self.widget.draw_elements()

    @pyqtSlot()
    def on_actionSpectral_Numbers_triggered(self):
        """
        Change visibility of the numbers of the detected segments on the oscilogram graph

        """
        self.widget.visual_items_visibility.spectrogram_text_visible = self.actionSpectral_Numbers.isChecked(
        )
        self.widget.draw_elements()

    # endregion

    def update_parameters(self, parameter_manager):
        """
        Updates the parameters to measure from a change in their selection.
        :param parameter_manager:
        :return:
        """
        self.widget.release_items(parameter_items=True, elements_items=False)

        # update the segment manager that measure the new params
        self.segmentManager.parameters = parameter_manager.parameter_list()

        self.measurement_finished()

    @pyqtSlot()
    def on_actionParameter_Measurement_triggered(self):

        # check for previously parameters measurements to save
        # there is measured parameters when the list of their names has at least one element
        if len(self.segmentManager.parameterColumnNames) > 0:

            mbox = QMessageBox(
                QMessageBox.Question, self.tr(u"soundLab"),
                self.tr(
                    u"You are going to change the parameters to measure."
                    u"All your previously selected measurements will be lost."
                    u"Do you want to save measurements first?"),
                QMessageBox.Yes | QMessageBox.No, self)

            if mbox.exec_() == QMessageBox.Yes:
                self.on_actionMeditions_triggered()

        param_window = ParametersWindow(self,
                                        self.parameter_manager,
                                        self.widget.signal,
                                        workspace=self.workSpace)

        param_window.parameterChangeFinished.connect(
            lambda p: self.update_parameters(p))

        param_window.show()

    def load_workspace(self, workspace):
        """
        Method that loads the workspace to update visual options from main window.
        :param workspace:
        """
        self.workSpace = workspace
        self.widget.load_workspace(workspace)

        # update workspace on every window
        for wnd in self.two_dim_windows:
            wnd.load_workspace(self.workSpace)