Ejemplo n.º 1
0
class EditorWidget(QtGui.QWidget):
    """
    Main class of the sequence editor
    """

    print_signal = QtCore.pyqtSignal(unicode)

    def __init__(self, parent=None):
        # Setup UI
        super(EditorWidget, self).__init__(parent)
        self.ui = Ui_PySequenceEditor()
        self.ui.setupUi(self)

        # Customize UI
        self.ui.main_splitter.setStretchFactor(1, 1)
        self.ui.center_splitter.setStretchFactor(0, 1)
        self.logging_tab = LoggingTabWidget(parent=self)
        self.logging_tab.setTabPosition(QtGui.QTabWidget.South)
        self.logging_tab.log_table.verticalHeader().setDefaultSectionSize(20)
        self.logging_tab.resize(0, 0)
        self.ui.center_splitter.addWidget(self.logging_tab)
        self.ui.tab_widget.clear()
        self.control_widget = ControlWidget(parent=self)
        self.ui.horizontalLayout.insertWidget(13, self.control_widget)

        # Monkey Patching
        def dropEvent(self, event):
            QtGui.QTreeWidget.dropEvent(self, event)
            self.currentItemChanged.emit(None, None)
        sub_editor = self.ui.subsequence_editor
        sub_editor.dropEvent = types.MethodType(dropEvent, sub_editor)

        # Stream redirection
        self.save_stdout = sys.stdout
        self.save_stderr = sys.stderr
        safe_write = lambda arg: self.print_signal.emit(arg)
        safe_write.write = safe_write
        sys.stdout = sys.stderr = safe_write
        self.print_signal.connect(self.logging_tab.write)

        # Sequence Engine
        self.control_widget.enable_path_request()
        self.control_widget.path_requested.connect(self.on_path_requested)
        self.control_widget.log_signal.connect(self.log)

        # Property editor initialization
        self.block_model = BlockModel(self, parent=self.ui.property_editor)
        self.block_model.model_changed.connect(partial(self.set_changed, True))
        self.ui.property_editor.setModel(self.block_model)

        # Create block Mapping
        self.block_mapping = {"Begin/End":   {'Begin':  XBM.BEGIN,
                                              'End'  :  XBM.END},
                              "Time":        {'Init':   XBM.TIMEINIT,
                                              'Wait':   XBM.WAIT},
                              "Branch":      {'Branch': XBM.BRANCH},
                              "Subsequence": {'Macro':  XBM.MACRO}}
        action_list = get_action_list()
        self.module_to_name = {module: name for name, module in action_list}
        for name, module in action_list:
            family = module.split(".")[-2].capitalize()
            self.block_mapping.setdefault(family, {})[name] = (XBM.ACTION, module)


        # Block list initialization
        self.ui.block_list.setHeaderLabels(['Block list'])
        self.fill_block_list()
        self.ui.block_list.expandAll()
        self.ui.block_list.resizeColumnToContents(0)
        self.ui.block_list.expandToDepth(0)

        # Sequence initialization
        self.current_sequence = XMLSequence("New sequence")

        # Signals connection
        self.ui.open_button.clicked.connect(self.on_open)
        self.ui.new_button.clicked.connect(self.on_new)
        self.ui.save_button.clicked.connect(self.on_save)
        self.ui.save_as_button.clicked.connect(self.on_save_as)
        self.ui.undo_button.clicked.connect(self.on_undo)
        self.ui.redo_button.clicked.connect(self.on_redo)
        self.ui.about_button.clicked.connect(self.on_about)
        self.ui.zoom_slider.valueChanged.connect(self.on_change_zoom_value)
        self.ui.block_list.itemClicked.connect(self.on_click_family)
        self.ui.tab_widget.tabCloseRequested.connect(self.on_close_tab)
        self.ui.tab_widget.currentChanged.connect(self.update_zoom_slider)
        sub_editor = self.ui.subsequence_editor
        sub_editor.itemDoubleClicked.connect(self.subsequence_double_clicked)
        sub_editor.itemActivated.connect(self.subsequence_activated)
        sub_editor.customContextMenuRequested.connect(self.context_requested)
        sub_editor.currentItemChanged.connect(self.subsequence_changed)
        QtGui.QShortcut(QtGui.QKeySequence.SelectAll, self, self.on_select_all)

        # Update
        self.file_path = None
        self.changed = False
        self.set_changed(False)
        self.update()

        # Logging
        self.log(u'Sequence editor initialized.')


        self.signal_handler = SignalHandler(logging.DEBUG)
        self.signal_handler.signal.connect(self.handle_log_record)
        add_log_handler(self.signal_handler)


    def handle_log_record(self, log_record):
        """
        Handle log record from the logging module
        """
        if log_record.levelno != logging.DEBUG:
            return
        sub_editor = self.ui.subsequence_editor
        flags = QtCore.Qt.MatchContains | QtCore.Qt.MatchRecursive
        seq_item = sub_editor.findItems(u"{}".format(log_record.sequenceID),
                                        flags)[0]
        if not seq_item:
            return
        block_id = log_record.ID
        bes = log_record.msg
        for block in seq_item.scene.block_list:
            if block.xml_block.block_id == block_id:
                block.set_execution_state(bes)
                break


    #### Generate block list ####

    def fill_block_list(self):
        """
        Fill the block list with common blocks and action blocks
        """
        for family in sorted(self.block_mapping):
            family_item = QtGui.QTreeWidgetItem(self.ui.block_list, [family])
            family_item.setFlags(QtCore.Qt.ItemIsEnabled)

            for model in sorted(self.block_mapping[family]):
                model_item = QtGui.QTreeWidgetItem(family_item, [model])
                model_item.setFlags(QtCore.Qt.ItemIsSelectable|
                                   QtCore.Qt.ItemIsEnabled|
                                   QtCore.Qt.ItemIsDragEnabled)
                data = self.block_mapping[family][model]
                if not isinstance(data, tuple):
                    model = data
                url = os.path.join(IMAGES_DIR, model)
                image = QtGui.QPixmap(url)
                if image.isNull():
                    url = os.path.join(IMAGES_DIR, 'Undefined')
                    image = QtGui.QPixmap(url)
                image = image.scaled(64, 64,
                                     QtCore.Qt.KeepAspectRatio,
                                     QtCore.Qt.SmoothTransformation)
                model_item.setIcon(0, QtGui.QIcon(image))

    ##### Update method #####

    def update(self):
        """
        Get the up-to-date data model and update the graphics
        """
        # Clear tabs
        self.ui.tab_widget.clear()
        # Subsequence Editor
        self.ui.subsequence_editor.clear()
        SequenceItem(self.ui.subsequence_editor, self.current_sequence, self)
        self.ui.subsequence_editor.expandAll()

    ##### File handling signals #####

    def on_path_requested(self):
        if self.changed:
            msg = "Source not saved \nSave before loading?"
            res = QtGui.QMessageBox.question(self, 'Save before loading', msg,
                                            QtGui.QMessageBox.Yes,
                                            QtGui.QMessageBox.Cancel)
            if res == QtGui.QMessageBox.Cancel or not self.on_save():
                return
        self.control_widget.load(self.file_path)

        sub_editor = self.ui.subsequence_editor
        flags = QtCore.Qt.MatchContains | QtCore.Qt.MatchRecursive
        for item in sub_editor.findItems(u"", flags):
            item.scene.reset_execution_state()

    def on_new(self):
        """
        Create a new sequence
        """
        # Reset control
        if not self.control_widget.reset():
            return
        self.control_widget.disable()
        # Create new sequence
        self.file_path = None
        self.set_changed(False)
        self.current_sequence = XMLSequence("New sequence", depth=0,
                                            level=0, execution=False)
        # Update
        self.update()
        self.log('NEW : New sequence')

    def on_open(self):
        """
        Display a dialogue to open a sequence
        """
        # Stop control widget
        if not self.control_widget.reset():
            return
        # Get the filename
        filename = unicode(QtGui.QFileDialog().getOpenFileName(self, "Open" ,
                                                               ".", "*.xml"))
        if filename == u"":
            return
        return self.open(filename)

    def open(self, filename):
        """
        Open a sequence from an XML file
        """
        # Open the sequence
        self.file_path = os.path.abspath(filename)
        file_name = os.path.basename(self.file_path)
        self.log(u'OPEN : {}'.format(file_name))
        try:
            self.current_sequence = parse_sequence_file(self.file_path,
                                                        execution=False)
        except StandardError as error:
            self.log('ERROR : '+ repr(error))
            self.on_new()
            return
        # Update
        self.update()
        # No changes
        self.set_changed(False)

    def on_save(self):
        """
        Save changed of the current sequence
        """
        # Already saved case
        if not self.changed and self.file_path:
            return
        # No file path case
        if not self.file_path:
            filename = unicode(QtGui.QFileDialog.getSaveFileName(self, 'Save',
                                                                 '.', '*.xml'))
            if filename == u'':
                return False
            self.file_path = os.path.abspath(filename)
        # Save the sequence
        self.set_changed(False)
        self.current_sequence.xml_export(self.file_path)
        file_name = os.path.basename(self.file_path)
        sequence_id = self.current_sequence.sequence_id
        self.log(u'SAVE : {} ({})'.format(file_name, sequence_id))
        return True

    def on_save_as(self):
        """
        Save the current sequence in an xml file
        """
        # Started engine case
        if not self.control_widget.reset():
            return
        # Get the new path
        filename = unicode(QtGui.QFileDialog.getSaveFileName(self, 'Save as',
                                                             '.', '*.xml'))
        if filename == u'':
            return False
        # Save the sequence
        self.set_changed(False)
        self.file_path = filename
        self.current_sequence.xml_export(self.file_path)
        file_name = os.path.basename(self.file_path)
        sequence_id = self.current_sequence.sequence_id
        self.log(u'SAVE AS : {} ({})'.format(file_name, sequence_id))
        return True

    #### About button signal ####

    def on_about(self):
        """
        Display "About" informations
        """
        msg = u'''
        <b>Sequence Editor</b><br/>
        <br/>
        Version 1.0.0
        <p>
            Sequence Editor is an application for designing,
            editing and testing action block sequences.
        </p>
        <br/>
        <br/>
        <b>Author</b> : <a href="http://www.nexeya.fr/">NEXEYA SYSTEM</a>
        '''
        url = QtCore.QString(u":/logos/logos/nexeya_logo.png")
        image = QtGui.QPixmap(url).scaled(128,
                                          128,
                                          QtCore.Qt.KeepAspectRatio,
                                          QtCore.Qt.SmoothTransformation)
        about = QtGui.QMessageBox(QtGui.QMessageBox.Information, "About", msg)
        about.setIconPixmap(image)
        mbi = QtGui.QStyle.SP_MessageBoxInformation
        icon = QtGui.QApplication.style().standardIcon(mbi)
        about.setWindowIcon(icon)
        about.exec_()

    #### Scene signals ####

    def on_select_all(self):
        """
        Select all items in scene
        """
        for item in self.drawing_scene.items():
            item.setSelected(True)

    def on_change_zoom_value(self, value):
        """
        Set a new zoom value
        """
        if value > 91 and value < 109:
            value = 100
        self.ui.zoom_slider.setValue(value)
        self.ui.zoom_label.setText("{}%".format(value))
        current_area = self.get_current_sequence_item().scene.parent()
        current_area.resetTransform()
        current_area.scale(float(value)/100, float(value)/100)

    def update_zoom_slider(self):
        """
        Update the zoom slider
        """
        current_item = self.get_current_sequence_item()
        if not current_item:
            return
        value = current_item.scene.parent().transform().m11()*100
        self.ui.zoom_slider.setValue(value)

    #### Block list signals ####

    @staticmethod
    def on_click_family(item, _):
        """
        Toogle the item expandation
        """
        if item.childCount():
            item.setExpanded(not item.isExpanded())

    #### Tab widget signals ####

    def on_close_tab(self, index):
        """
        Close a tab
        """
        self.ui.tab_widget.removeTab(index)

    #### Sequence Browser signals ####

    @staticmethod
    def subsequence_double_clicked(item, _):
        """
        Handle the subsequence double click
        """
        item.double_clicked = True

    @staticmethod
    def subsequence_activated(item, _):
        """
        Handle the subsequence activation
        """
        if item.double_clicked:
            item.double_clicked = False
            item.on_edit()
        else:
            item.on_rename()

    def subsequence_changed(self, old, new):
        """
        Update sequence tree when data changed
        """
        if old is None and new is None:
            super_root = self.ui.subsequence_editor.invisibleRootItem()
            super_root.child(0).check_tree()

    def context_requested(self, point):
        """
        Display a context menu when requested
        """
        item = self.ui.subsequence_editor.itemAt(point)
        if not item:
            return
        menu = item.context_requested()
        menu.exec_(self.ui.subsequence_editor.mapToGlobal(point))

    ##### Handle modifications #####

    def get_current_sequence_item(self):
        """
        Get the current sequence item
        """
        sub_editor = self.ui.subsequence_editor
        flags = QtCore.Qt.MatchContains | QtCore.Qt.MatchRecursive
        for item in sub_editor.findItems(u"", flags):
            if item.is_current():
                return item

    def on_undo(self):
        """
        Undo the last action of the current tab
        """
        return self.get_current_sequence_item().on_undo()

    def on_redo(self):
        """
        Redo the last action of the current tab
        """
        return self.get_current_sequence_item().on_redo()

    def set_changed(self, value, global_change=False):
        """
        Update the main widget depending on whether or not the file is UtD
        If value is False, the file is up-to-date, True otherwise
        """
        self.changed = value
        # Undo redo mecanism
        if value and not global_change:
            current_sub_item = self.get_current_sequence_item()
            if current_sub_item:
                current_sub_item.set_changed()
        # Enable the load button
        if self.file_path or self.changed:
            self.control_widget.enable()
        # Set name to display
        if self.file_path:
            name = os.path.basename(self.file_path)
        else:
            name = self.current_sequence.sequence_id
        title = 'Sequence editor - '
        # Set the title format
        if self.changed:
            title += '*' + name + '*'
        else:
            title += name
        # Set the window title
        self.setWindowTitle(title)

    #### Log method ####

    def log(self, msg, end="\n"):
        """
        Method to properly log a message
        """
        return self.logging_tab.log(msg, end)

    #### Close Event ####

    def closeEvent(self, event):
        """
        Override closeEvent to handle secial cases
        """
        # Stop control widget
        if not self.control_widget.reset():
            return event.ignore()
        # Sequence not saved case
        if self.changed:
            msg = "Source not saved \nSave before closing?"
            res = QtGui.QMessageBox.question(self, 'Save before closing', msg,
                                            QtGui.QMessageBox.Yes,
                                            QtGui.QMessageBox.No,
                                            QtGui.QMessageBox.Cancel)
            if res == QtGui.QMessageBox.Cancel or \
               (res == QtGui.QMessageBox.Yes and not self.on_save()):
                return event.ignore()
        # Put the original streams back in place
        sys.stdout = self.save_stdout
        sys.stderr = self.save_stderr
Ejemplo n.º 2
0
class RunnerWidget(QtGui.QWidget):
    """
    Widget for running and monitoring sequences
    """
    size_changed = QtCore.pyqtSignal()
    hspace = 10
    vspace = 5
    base_size = 800,300

    def __init__(self, *args, **kwargs):
        # Init GroupBox
        super(RunnerWidget, self).__init__(*args, **kwargs)
        self.resize(*self.base_size)
        self.saved_height = self.base_size[1]

        # Fist line
        self.vbox_layout = QtGui.QVBoxLayout(self)
        hbox_layout = QtGui.QHBoxLayout()
        self.filename_edit = QtGui.QLineEdit(self)
        self.select_button = QtGui.QPushButton(u'Select', self)
        self.edit_button = QtGui.QPushButton(u'Edit', self)
        self.new_button = QtGui.QPushButton(u'New', self)
        hbox_layout.addWidget(self.filename_edit, 1)
        hbox_layout.addWidget(self.select_button)
        hbox_layout.addSpacing(self.hspace)
        line = QtGui.QFrame(self)
        line.setFrameShape(QtGui.QFrame.VLine)
        line.setFrameShadow(QtGui.QFrame.Sunken)
        hbox_layout.addWidget(line)
        hbox_layout.addSpacing(self.hspace)
        hbox_layout.addWidget(self.edit_button)
        hbox_layout.addWidget(self.new_button)
        self.vbox_layout.addLayout(hbox_layout)
        self.vbox_layout.addSpacing(self.vspace)

        # Second line
        hbox_layout = QtGui.QHBoxLayout()
        hbox_layout.addWidget(QtGui.QLabel(u"Display Console:"))
        self.check_box = QtGui.QCheckBox(self)
        self.check_box.setChecked(True)
        hbox_layout.addWidget(self.check_box)
        hbox_layout.addStretch(1)
        hbox_layout.addSpacing(self.hspace)
        hbox_layout.addWidget(QtGui.QLabel(u"Sequence Control :"))
        hbox_layout.addSpacing(self.hspace)

        # Create control widget
        self.control_widget = ControlWidget(parent=self)
        self.control_widget.enable_path_request()
        self.control_widget.disable()
        hbox_layout.addWidget(self.control_widget)

        # Create logging tab
        self.logging_tab = LoggingTabWidget("CONSOLE INITIALIZED",
                                            parent=self)
        self.logging_tab.setTabPosition(QtGui.QTabWidget.South)
        self.logging_tab.log_table.verticalHeader().setDefaultSectionSize(20)
        self.logging_tab.setVisible(True)
        self.vbox_layout.addLayout(hbox_layout)
        self.vbox_layout.addSpacing(self.vspace)
        self.vbox_layout.addWidget(self.logging_tab)
        self.setLayout(self.vbox_layout)

        # Connect signals
        self.select_button.clicked.connect(self.on_select)
        self.check_box.stateChanged.connect(self.on_display_console)
        self.control_widget.log_signal.connect(self.logging_tab.log)
        self.control_widget.path_requested.connect(self.on_path_requested)
        self.filename_edit.textChanged.connect(self.on_path_changed)
        self.new_button.clicked.connect(self.open_editor)
        def open_with_file(_):
            return self.open_editor(True, self.filename_edit.text())
        self.edit_button.clicked.connect(open_with_file)
        if self.isWindow():
            self.size_changed.connect(self.custom_resize)

    def on_select(self):
        """
        Select a sequence
        """
        path = os.getcwd()
        filename = unicode(QtGui.QFileDialog.getOpenFileName(self,
                                                             'Select Sequence',
                                                             path, '*.xml'))
        if filename == u"":
            return
        self.filename_edit.setText(filename)

    def on_path_requested(self):
        """
        Load a file with the filename in the LineEdit
        """
        self.control_widget.load(self.filename_edit.text())

    def on_path_changed(self, text):
        """
        Disable the control widget if the given filename is emprty
        """
        self.control_widget.disable()
        if text:
            self.control_widget.enable()

    def set_filename(self, filename):
        """
        Set the given filname in the LineEdit
        """
        filename = os.path.abspath(filename)
        self.filename_edit.setText(filename)

    def on_display_console(self, boolean):
        """
        Display or hide the logging tab widget with a given boolean
        """
        self.logging_tab.setVisible(boolean)
        self.vbox_layout.itemAt(3).changeSize(0, bool(boolean)*self.vspace)
        self.vbox_layout.activate()
        self.size_changed.emit()

    def custom_resize(self):
        """
        Resize the widget consering the visibility of the logging tab widget
        """
        width = self.size().width()
        height = self.size().height()
        if self.logging_tab.isVisible():
            height = self.saved_height
        else:
            self.saved_height = height
            height = 0
        self.resize(width, height)

    def open_editor(self, boolean, filename=""):
        """
        Open the editor in a subprocess
        The given filename is used to open a sequence
        """
        cmd = u'python -m sequence.editor '
        if filename:
            cmd += ' ' + filename
        cmd = cmd.encode(locale.getpreferredencoding())
        try:
            subprocess.Popen(cmd, shell=True)
        except OSError as e:
            print e.strerror

    def set_display_console(self, boolean):
        """
        Set the console display mode
        """
        self.check_box.setChecked(boolean)
Ejemplo n.º 3
0
class EditorWidget(QtGui.QWidget):
    """
    Main class of the sequence editor
    """

    print_signal = QtCore.pyqtSignal(unicode)

    def __init__(self, parent=None):
        # Setup UI
        super(EditorWidget, self).__init__(parent)
        self.ui = Ui_PySequenceEditor()
        self.ui.setupUi(self)

        # Customize UI
        self.ui.main_splitter.setStretchFactor(1, 1)
        self.ui.center_splitter.setStretchFactor(0, 1)
        self.logging_tab = LoggingTabWidget(parent=self)
        self.logging_tab.setTabPosition(QtGui.QTabWidget.South)
        self.logging_tab.log_table.verticalHeader().setDefaultSectionSize(20)
        self.logging_tab.resize(0, 0)
        self.ui.center_splitter.addWidget(self.logging_tab)
        self.ui.tab_widget.clear()
        self.control_widget = ControlWidget(parent=self)
        self.ui.horizontalLayout.insertWidget(13, self.control_widget)

        # Monkey Patching
        def dropEvent(self, event):
            QtGui.QTreeWidget.dropEvent(self, event)
            self.currentItemChanged.emit(None, None)

        sub_editor = self.ui.subsequence_editor
        sub_editor.dropEvent = types.MethodType(dropEvent, sub_editor)

        # Stream redirection
        self.save_stdout = sys.stdout
        self.save_stderr = sys.stderr
        safe_write = lambda arg: self.print_signal.emit(arg)
        safe_write.write = safe_write
        sys.stdout = sys.stderr = safe_write
        self.print_signal.connect(self.logging_tab.write)

        # Sequence Engine
        self.control_widget.enable_path_request()
        self.control_widget.path_requested.connect(self.on_path_requested)
        self.control_widget.log_signal.connect(self.log)

        # Property editor initialization
        self.block_model = BlockModel(self, parent=self.ui.property_editor)
        self.block_model.model_changed.connect(partial(self.set_changed, True))
        self.ui.property_editor.setModel(self.block_model)

        # Create block Mapping
        self.block_mapping = {
            "Begin/End": {
                'Begin': XBM.BEGIN,
                'End': XBM.END
            },
            "Time": {
                'Init': XBM.TIMEINIT,
                'Wait': XBM.WAIT
            },
            "Branch": {
                'Branch': XBM.BRANCH
            },
            "Subsequence": {
                'Macro': XBM.MACRO
            }
        }
        action_list = get_action_list()
        self.module_to_name = {module: name for name, module in action_list}
        for name, module in action_list:
            family = module.split(".")[-2].capitalize()
            self.block_mapping.setdefault(family,
                                          {})[name] = (XBM.ACTION, module)

        # Block list initialization
        self.ui.block_list.setHeaderLabels(['Block list'])
        self.fill_block_list()
        self.ui.block_list.expandAll()
        self.ui.block_list.resizeColumnToContents(0)
        self.ui.block_list.expandToDepth(0)

        # Sequence initialization
        self.current_sequence = XMLSequence("New sequence")

        # Signals connection
        self.ui.open_button.clicked.connect(self.on_open)
        self.ui.new_button.clicked.connect(self.on_new)
        self.ui.save_button.clicked.connect(self.on_save)
        self.ui.save_as_button.clicked.connect(self.on_save_as)
        self.ui.undo_button.clicked.connect(self.on_undo)
        self.ui.redo_button.clicked.connect(self.on_redo)
        self.ui.about_button.clicked.connect(self.on_about)
        self.ui.zoom_slider.valueChanged.connect(self.on_change_zoom_value)
        self.ui.block_list.itemClicked.connect(self.on_click_family)
        self.ui.tab_widget.tabCloseRequested.connect(self.on_close_tab)
        self.ui.tab_widget.currentChanged.connect(self.update_zoom_slider)
        sub_editor = self.ui.subsequence_editor
        sub_editor.itemDoubleClicked.connect(self.subsequence_double_clicked)
        sub_editor.itemActivated.connect(self.subsequence_activated)
        sub_editor.customContextMenuRequested.connect(self.context_requested)
        sub_editor.currentItemChanged.connect(self.subsequence_changed)
        QtGui.QShortcut(QtGui.QKeySequence.SelectAll, self, self.on_select_all)

        # Update
        self.file_path = None
        self.changed = False
        self.set_changed(False)
        self.update()

        # Logging
        self.log(u'Sequence editor initialized.')

        self.signal_handler = SignalHandler(logging.DEBUG)
        self.signal_handler.signal.connect(self.handle_log_record)
        add_log_handler(self.signal_handler)

    def handle_log_record(self, log_record):
        """
        Handle log record from the logging module
        """
        if log_record.levelno != logging.DEBUG:
            return
        sub_editor = self.ui.subsequence_editor
        flags = QtCore.Qt.MatchContains | QtCore.Qt.MatchRecursive
        seq_item = sub_editor.findItems(u"{}".format(log_record.sequenceID),
                                        flags)[0]
        if not seq_item:
            return
        block_id = log_record.ID
        bes = log_record.msg
        for block in seq_item.scene.block_list:
            if block.xml_block.block_id == block_id:
                block.set_execution_state(bes)
                break

    #### Generate block list ####

    def fill_block_list(self):
        """
        Fill the block list with common blocks and action blocks
        """
        for family in sorted(self.block_mapping):
            family_item = QtGui.QTreeWidgetItem(self.ui.block_list, [family])
            family_item.setFlags(QtCore.Qt.ItemIsEnabled)

            for model in sorted(self.block_mapping[family]):
                model_item = QtGui.QTreeWidgetItem(family_item, [model])
                model_item.setFlags(QtCore.Qt.ItemIsSelectable
                                    | QtCore.Qt.ItemIsEnabled
                                    | QtCore.Qt.ItemIsDragEnabled)
                data = self.block_mapping[family][model]
                if not isinstance(data, tuple):
                    model = data
                url = os.path.join(IMAGES_DIR, model)
                image = QtGui.QPixmap(url)
                if image.isNull():
                    url = os.path.join(IMAGES_DIR, 'Undefined')
                    image = QtGui.QPixmap(url)
                image = image.scaled(64, 64, QtCore.Qt.KeepAspectRatio,
                                     QtCore.Qt.SmoothTransformation)
                model_item.setIcon(0, QtGui.QIcon(image))

    ##### Update method #####

    def update(self):
        """
        Get the up-to-date data model and update the graphics
        """
        # Clear tabs
        self.ui.tab_widget.clear()
        # Subsequence Editor
        self.ui.subsequence_editor.clear()
        SequenceItem(self.ui.subsequence_editor, self.current_sequence, self)
        self.ui.subsequence_editor.expandAll()

    ##### File handling signals #####

    def on_path_requested(self):
        if self.changed:
            msg = "Source not saved \nSave before loading?"
            res = QtGui.QMessageBox.question(self, 'Save before loading', msg,
                                             QtGui.QMessageBox.Yes,
                                             QtGui.QMessageBox.Cancel)
            if res == QtGui.QMessageBox.Cancel or not self.on_save():
                return
        self.control_widget.load(self.file_path)

        sub_editor = self.ui.subsequence_editor
        flags = QtCore.Qt.MatchContains | QtCore.Qt.MatchRecursive
        for item in sub_editor.findItems(u"", flags):
            item.scene.reset_execution_state()

    def on_new(self):
        """
        Create a new sequence
        """
        # Reset control
        if not self.control_widget.reset():
            return
        self.control_widget.disable()
        # Create new sequence
        self.file_path = None
        self.set_changed(False)
        self.current_sequence = XMLSequence("New sequence",
                                            depth=0,
                                            level=0,
                                            execution=False)
        # Update
        self.update()
        self.log('NEW : New sequence')

    def on_open(self):
        """
        Display a dialogue to open a sequence
        """
        # Stop control widget
        if not self.control_widget.reset():
            return
        # Get the filename
        filename = unicode(QtGui.QFileDialog().getOpenFileName(
            self, "Open", ".", "*.xml"))
        if filename == u"":
            return
        return self.open(filename)

    def open(self, filename):
        """
        Open a sequence from an XML file
        """
        # Open the sequence
        self.file_path = os.path.abspath(filename)
        file_name = os.path.basename(self.file_path)
        self.log(u'OPEN : {}'.format(file_name))
        try:
            self.current_sequence = parse_sequence_file(self.file_path,
                                                        execution=False)
        except StandardError as error:
            self.log('ERROR : ' + repr(error))
            self.on_new()
            return
        # Update
        self.update()
        # No changes
        self.set_changed(False)

    def on_save(self):
        """
        Save changed of the current sequence
        """
        # Already saved case
        if not self.changed and self.file_path:
            return
        # No file path case
        if not self.file_path:
            filename = unicode(
                QtGui.QFileDialog.getSaveFileName(self, 'Save', '.', '*.xml'))
            if filename == u'':
                return False
            self.file_path = os.path.abspath(filename)
        # Save the sequence
        self.set_changed(False)
        self.current_sequence.xml_export(self.file_path)
        file_name = os.path.basename(self.file_path)
        sequence_id = self.current_sequence.sequence_id
        self.log(u'SAVE : {} ({})'.format(file_name, sequence_id))
        return True

    def on_save_as(self):
        """
        Save the current sequence in an xml file
        """
        # Started engine case
        if not self.control_widget.reset():
            return
        # Get the new path
        filename = unicode(
            QtGui.QFileDialog.getSaveFileName(self, 'Save as', '.', '*.xml'))
        if filename == u'':
            return False
        # Save the sequence
        self.set_changed(False)
        self.file_path = filename
        self.current_sequence.xml_export(self.file_path)
        file_name = os.path.basename(self.file_path)
        sequence_id = self.current_sequence.sequence_id
        self.log(u'SAVE AS : {} ({})'.format(file_name, sequence_id))
        return True

    #### About button signal ####

    def on_about(self):
        """
        Display "About" informations
        """
        msg = u'''
        <b>Sequence Editor</b><br/>
        <br/>
        Version 1.0.0
        <p>
            Sequence Editor is an application for designing,
            editing and testing action block sequences.
        </p>
        <br/>
        <br/>
        <b>Author</b> : <a href="http://www.nexeya.fr/">NEXEYA SYSTEM</a>
        '''
        url = QtCore.QString(u":/logos/logos/nexeya_logo.png")
        image = QtGui.QPixmap(url).scaled(128, 128, QtCore.Qt.KeepAspectRatio,
                                          QtCore.Qt.SmoothTransformation)
        about = QtGui.QMessageBox(QtGui.QMessageBox.Information, "About", msg)
        about.setIconPixmap(image)
        mbi = QtGui.QStyle.SP_MessageBoxInformation
        icon = QtGui.QApplication.style().standardIcon(mbi)
        about.setWindowIcon(icon)
        about.exec_()

    #### Scene signals ####

    def on_select_all(self):
        """
        Select all items in scene
        """
        for item in self.drawing_scene.items():
            item.setSelected(True)

    def on_change_zoom_value(self, value):
        """
        Set a new zoom value
        """
        if value > 91 and value < 109:
            value = 100
        self.ui.zoom_slider.setValue(value)
        self.ui.zoom_label.setText("{}%".format(value))
        current_area = self.get_current_sequence_item().scene.parent()
        current_area.resetTransform()
        current_area.scale(float(value) / 100, float(value) / 100)

    def update_zoom_slider(self):
        """
        Update the zoom slider
        """
        current_item = self.get_current_sequence_item()
        if not current_item:
            return
        value = current_item.scene.parent().transform().m11() * 100
        self.ui.zoom_slider.setValue(value)

    #### Block list signals ####

    @staticmethod
    def on_click_family(item, _):
        """
        Toogle the item expandation
        """
        if item.childCount():
            item.setExpanded(not item.isExpanded())

    #### Tab widget signals ####

    def on_close_tab(self, index):
        """
        Close a tab
        """
        self.ui.tab_widget.removeTab(index)

    #### Sequence Browser signals ####

    @staticmethod
    def subsequence_double_clicked(item, _):
        """
        Handle the subsequence double click
        """
        item.double_clicked = True

    @staticmethod
    def subsequence_activated(item, _):
        """
        Handle the subsequence activation
        """
        if item.double_clicked:
            item.double_clicked = False
            item.on_edit()
        else:
            item.on_rename()

    def subsequence_changed(self, old, new):
        """
        Update sequence tree when data changed
        """
        if old is None and new is None:
            super_root = self.ui.subsequence_editor.invisibleRootItem()
            super_root.child(0).check_tree()

    def context_requested(self, point):
        """
        Display a context menu when requested
        """
        item = self.ui.subsequence_editor.itemAt(point)
        if not item:
            return
        menu = item.context_requested()
        menu.exec_(self.ui.subsequence_editor.mapToGlobal(point))

    ##### Handle modifications #####

    def get_current_sequence_item(self):
        """
        Get the current sequence item
        """
        sub_editor = self.ui.subsequence_editor
        flags = QtCore.Qt.MatchContains | QtCore.Qt.MatchRecursive
        for item in sub_editor.findItems(u"", flags):
            if item.is_current():
                return item

    def on_undo(self):
        """
        Undo the last action of the current tab
        """
        return self.get_current_sequence_item().on_undo()

    def on_redo(self):
        """
        Redo the last action of the current tab
        """
        return self.get_current_sequence_item().on_redo()

    def set_changed(self, value, global_change=False):
        """
        Update the main widget depending on whether or not the file is UtD
        If value is False, the file is up-to-date, True otherwise
        """
        self.changed = value
        # Undo redo mecanism
        if value and not global_change:
            current_sub_item = self.get_current_sequence_item()
            if current_sub_item:
                current_sub_item.set_changed()
        # Enable the load button
        if self.file_path or self.changed:
            self.control_widget.enable()
        # Set name to display
        if self.file_path:
            name = os.path.basename(self.file_path)
        else:
            name = self.current_sequence.sequence_id
        title = 'Sequence editor - '
        # Set the title format
        if self.changed:
            title += '*' + name + '*'
        else:
            title += name
        # Set the window title
        self.setWindowTitle(title)

    #### Log method ####

    def log(self, msg, end="\n"):
        """
        Method to properly log a message
        """
        return self.logging_tab.log(msg, end)

    #### Close Event ####

    def closeEvent(self, event):
        """
        Override closeEvent to handle secial cases
        """
        # Stop control widget
        if not self.control_widget.reset():
            return event.ignore()
        # Sequence not saved case
        if self.changed:
            msg = "Source not saved \nSave before closing?"
            res = QtGui.QMessageBox.question(self, 'Save before closing', msg,
                                             QtGui.QMessageBox.Yes,
                                             QtGui.QMessageBox.No,
                                             QtGui.QMessageBox.Cancel)
            if res == QtGui.QMessageBox.Cancel or \
               (res == QtGui.QMessageBox.Yes and not self.on_save()):
                return event.ignore()
        # Put the original streams back in place
        sys.stdout = self.save_stdout
        sys.stderr = self.save_stderr