Ejemplo n.º 1
0
def set_combo_with_value(combo: QComboBox, value):
    """
    Searches all items of the given combo for the given value and changes the current index to that one.
    :param combo:
    :param value:
    :return:
    """
    combo.setCurrentIndex(combo.findData(value))
Ejemplo n.º 2
0
    def _create_from_until_combo(self, row: RowWidgets, cur_serial: int, on_func: Callable) -> QComboBox:
        combo = QComboBox(row.parent)
        combo.setMinimumWidth(100)

        combo.addItem('', 0)
        sorted_date_serials = sorted(x.serial for x in self._contacts_model.iter_dates())
        for date_serial in sorted_date_serials:
            date = self._contacts_model.get_date(date_serial)
            item_text = self._create_vague_date_combo_text(date)
            combo.addItem(item_text, date.serial)

        cur_index = combo.findData(cur_serial)
        combo.setCurrentIndex(cur_index)
        combo.currentIndexChanged.connect(on_func)
        combo.row = row
        return combo
Ejemplo n.º 3
0
    def _create_val_combo(self, row: RowWidgets) -> QComboBox:
        combo = QComboBox(row.parent)

        # completer = QCompleter(word_list, self)
        # completer.setCaseSensitivity(Qt.CaseInsensitive)
        # combo.setCompleter(completer)
        # combo.setEditable(True)

        ref_map = self._create_ref_map(row.attr)
        combo.addItem('', 0)
        current_data = 0
        for title in sorted(ref_map.keys()):
            contact = ref_map[title]
            if row.fact is not None and contact.serial == int(row.fact.value):
                current_data = contact.serial
            combo.addItem(title, contact.serial)
        cur_index = combo.findData(current_data)
        combo.setCurrentIndex(cur_index)
        combo.currentIndexChanged.connect(self.on_val_combo_index_changed)
        combo.row = row
        return combo
Ejemplo n.º 4
0
class ConfigWindow(QDialog):
    # noinspection SpellCheckingInspection
    LOCALES = {
        'en': 'English',
        'nl': 'Nederlands'
    }

    def __init__(self, gui_window: mainWindow.MainWindow, device_manager: logic.DeviceManager):
        QDialog.__init__(self, gui_window)
        self.setModal(True)
        self.setWindowTitle(QCoreApplication.translate('configs', 'Configs', 'title'))
        self._device_manager = device_manager

        layout = QGridLayout()
        layout.addWidget(QLabel(QCoreApplication.translate('configs', 'Open on startup'), self), 0, 0)
        self._open_on_startup = QCheckBox(self)
        self._open_on_startup.setChecked(device_manager.open_on_start)
        self._open_on_startup.stateChanged.connect(self._open_on_startup_changed)
        layout.addWidget(self._open_on_startup, 0, 1)

        layout.addWidget(QLabel(QCoreApplication.translate('configs', 'Locale'), self), 1, 0)
        self._locale_select = QComboBox(self)
        self._locale_select.setToolTip(QCoreApplication.translate('configs', 'Restart required'))
        for locale_key, locale_value in ConfigWindow.LOCALES.items():
            self._locale_select.addItem(locale_value, userData=locale_key)
        self._locale_select.setCurrentIndex(self._locale_select.findData(device_manager.locale))
        self._locale_select.currentIndexChanged.connect(self._locale_changed)
        layout.addWidget(self._locale_select, 1, 1)

        self.setLayout(layout)

    def _open_on_startup_changed(self, state):
        self._device_manager.open_on_start = state == Qt.Checked

    def _locale_changed(self, index):
        self._device_manager.locale = self._locale_select.itemData(index)

    def closeEvent(self, event):
        event.ignore()
        self.hide()
Ejemplo n.º 5
0
class RequirementEditor:
    _editor: Union[None, ResourceRequirementEditor, ArrayRequirementEditor,
                   TemplateRequirementEditor]

    def __init__(self,
                 parent: QWidget,
                 parent_layout: QVBoxLayout,
                 resource_database: ResourceDatabase,
                 *,
                 on_remove=None):

        self.parent = parent
        self.parent_layout = parent_layout
        self.resource_database = resource_database
        self._editor = None
        self._last_resource = None
        self._last_items = ()

        self.line_layout = QHBoxLayout()
        self.line_layout.setAlignment(Qt.AlignLeft)
        self.parent_layout.addLayout(self.line_layout)

        if on_remove is not None:
            self.remove_button = QtWidgets.QToolButton(parent)
            self.remove_button.setText("X")
            self.remove_button.setMaximumWidth(20)
            self.remove_button.clicked.connect(on_remove)
            self.line_layout.addWidget(self.remove_button)
        else:
            self.remove_button = None

        self.requirement_type_combo = QComboBox(parent)
        self.requirement_type_combo.addItem("Resource", ResourceRequirement)
        self.requirement_type_combo.addItem("Or", RequirementOr)
        self.requirement_type_combo.addItem("And", RequirementAnd)
        if resource_database.requirement_template:
            self.requirement_type_combo.addItem("Template",
                                                RequirementTemplate)
        self.requirement_type_combo.setMaximumWidth(75)
        self.requirement_type_combo.activated.connect(
            self._on_change_requirement_type)
        self.line_layout.addWidget(self.requirement_type_combo)

    def create_specialized_editor(self, requirement: Requirement):
        self.requirement_type_combo.setCurrentIndex(
            self.requirement_type_combo.findData(type(requirement)))

        if isinstance(requirement, ResourceRequirement):
            self._editor = ResourceRequirementEditor(self.parent,
                                                     self.line_layout,
                                                     self.resource_database,
                                                     requirement)

        elif isinstance(requirement, (RequirementOr, RequirementAnd)):
            self._editor = ArrayRequirementEditor(self.parent,
                                                  self.parent_layout,
                                                  self.resource_database,
                                                  requirement)

        elif isinstance(requirement, RequirementTemplate):
            self._editor = TemplateRequirementEditor(self.parent,
                                                     self.line_layout,
                                                     self.resource_database,
                                                     requirement)

        else:
            raise RuntimeError(
                f"Unknown requirement type: {type(requirement)} - {requirement}"
            )

    def _on_change_requirement_type(self):
        current_requirement = self.current_requirement
        self._editor.deleteLater()

        if isinstance(current_requirement, ResourceRequirement):
            self._last_resource = current_requirement

        elif isinstance(current_requirement, (RequirementOr, RequirementAnd)):
            self._last_items = current_requirement.items

        elif isinstance(current_requirement, RequirementTemplate):
            pass

        else:
            raise RuntimeError(
                f"Unknown requirement type: {type(current_requirement)} - {current_requirement}"
            )

        new_class = self.requirement_type_combo.currentData()
        if new_class == ResourceRequirement:
            if self._last_resource is None:
                new_requirement = _create_default_resource_requirement(
                    self.resource_database)
            else:
                new_requirement = self._last_resource
        elif new_class == RequirementTemplate:
            new_requirement = _create_default_template_requirement(
                self.resource_database)
        else:
            new_requirement = new_class(self._last_items)

        self.create_specialized_editor(new_requirement)

    def deleteLater(self):
        if self.remove_button is not None:
            self.remove_button.deleteLater()

        self.requirement_type_combo.deleteLater()

        if self._editor is not None:
            self._editor.deleteLater()

    @property
    def current_requirement(self) -> Requirement:
        return self._editor.current_requirement
Ejemplo n.º 6
0
class SimpleSelector(object):

    # Constants
    LIST_BOX_HEIGHT = utils.dpiScale(100)
    EXPRESSION_BUTTON_WIDTH = utils.dpiScale(50)
    INVERSE_STRING = maya.stringTable['y_simpleSelector.kInverse']
    SELECT_STRING = maya.stringTable['y_simpleSelector.kSelect']
    CREATE_EXPRESSION_STRING = maya.stringTable[
        'y_simpleSelector.kCreateExpression']
    DRAG_DROP_FILTER_STRING = maya.stringTable[
        'y_simpleSelector.kDragDropFilter']

    # Corresponding data model type.
    kModelType = selector.SimpleSelector.kTypeName

    def __init__(self, selector):
        self.expression = ""
        self._blockChangeMessages = False
        self._selector = selector
        self.filterType = None
        self.addToCollectionGroupBoxLayout = None
        self.includeExpressionWidgets = None
        self.includeExpression = None
        self.customFilterEdit = None
        self.filtersGroupBoxLayout = None
        self.staticSelector = None

    def build(self, layout, populate=True):
        self._setupFilterGroupBox(layout)
        self._setupAddToCollectionGroupBox(layout)
        if populate:
            self.populateFields()

    def displayType(self):
        """Return the user-visible display type string.

        By default this is the same for all objects of a selector class."""
        return self.kDisplayType

    def _setupExpression(self, expressionLabel, expressionChangedCB,
                         expressionFinishedCB):
        expressionWidget = QWidget()
        expressionLayout = QHBoxLayout()
        expressionLayout.setContentsMargins(0, 0, 0, 0)
        expressionLayout.setSpacing(utils.dpiScale(2))
        expressionWidget.setLayout(expressionLayout)
        expression = QLineEdit()
        tip = maya.stringTable['y_simpleSelector.kExpressionTooltipStr']
        tip += maya.stringTable['y_simpleSelector.kExpressionTooltipStr1']
        tip += maya.stringTable['y_simpleSelector.kExpressionTooltipStr2']
        tip += maya.stringTable['y_simpleSelector.kExpressionTooltipStr3']
        tip += maya.stringTable['y_simpleSelector.kExpressionTooltipStr4']
        tip += maya.stringTable['y_simpleSelector.kExpressionTooltipStr5']
        tip += maya.stringTable['y_simpleSelector.kExpressionTooltipStr6']
        tip += maya.stringTable['y_simpleSelector.kExpressionTooltipStr7']
        tip += maya.stringTable['y_simpleSelector.kExpressionTooltipStr8']
        tip += maya.stringTable['y_simpleSelector.kExpressionTooltipStr9']
        tip += maya.stringTable['y_simpleSelector.kExpressionTooltipStr10']
        tip += maya.stringTable['y_simpleSelector.kExpressionTooltipStr11']
        tip += maya.stringTable['y_simpleSelector.kExpressionTooltipStr12']
        tip += maya.stringTable['y_simpleSelector.kExpressionTooltipStr13']
        tip += maya.stringTable['y_simpleSelector.kExpressionTooltipStr14']
        expression.setToolTip(tip)

        expression.textChanged.connect(expressionChangedCB)

        # editingFinished is triggered for both enter key pressed and focused lost.
        expression.editingFinished.connect(expressionFinishedCB)
        expression.returnPressed.connect(lambda: expression.clearFocus())

        expression.setPlaceholderText(self.CREATE_EXPRESSION_STRING)
        expressionLayout.addWidget(expression)

        selectButton = QPushButton(self.SELECT_STRING)
        selectButton.setToolTip(
            maya.stringTable['y_simpleSelector.kExpressionSelectTooltipStr'])
        selectButton.setMinimumWidth(self.EXPRESSION_BUTTON_WIDTH)
        selectButton.clicked.connect(self.selectIncludeExpression)
        expressionLayout.addWidget(selectButton)

        self.addToCollectionGroupBoxLayout.addRow(expressionLabel,
                                                  expressionWidget)
        return expressionWidget, expression

    def _setupIncludeExpression(self):
        includeExpressionLabel = IncludeExpressionLabel(
            maya.stringTable['y_simpleSelector.kInclude'])
        includeExpressionWidget, self.includeExpression = \
            self._setupExpression(includeExpressionLabel, self.includeExpressionChanged, self.includeExpressionEntered)
        self.includeExpressionWidgets = [includeExpressionWidget]
        self.expression = self._selector.getPattern()

    @abstractmethod
    def _getFilterEnum(self):
        pass

    def _setupFilterUI(self, layout):
        filterUIWidget = QWidget()
        filterUILayout = QHBoxLayout()
        filterUILayout.setContentsMargins(0, 0, 0, 0)
        filterUILayout.setSpacing(utils.dpiScale(2))
        filterUIWidget.setLayout(filterUILayout)

        #setup and add the filter combo box.
        self.filterType = QComboBox()
        self.filterType.setSizeAdjustPolicy(
            QComboBox.AdjustToMinimumContentsLengthWithIcon)
        sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
        sizePolicy.setHorizontalStretch(
            10)  # stronger than the spacer added below
        self.filterType.setSizePolicy(sizePolicy)
        self.filterType.setMinimumContentsLength(0)
        self.filterType.setMaximumWidth(200)
        self.filterType.setToolTip(
            maya.stringTable['y_simpleSelector.kFiltersToolTip'])

        for ftype in self._selector.getAvailableFilters():
            self.filterType.addItem(
                QIcon(RenderSetupDelegate.getFilterIcon(ftype)),
                selector.Filters.filterUIName(ftype), ftype)

        # get the current filter value and set the index of the combo box appropriately.
        filter = self._selector.getFilterType()
        self.filterType.currentIndexChanged[str].connect(
            self._filterTypeChanged)
        filterUILayout.addWidget(self.filterType)

        filterUILayout.addStretch()
        filterUILayout.addSpacing(4)

        self.customFilterEdit = CollectionFilterLineEdit()
        self.customFilterEdit.returnPressed.connect(
            lambda: self.customFilterEdit.clearFocus())
        self.customFilterEdit.setPlaceholderText(self.DRAG_DROP_FILTER_STRING)
        self.customFilterEdit.editingFinished.connect(self.customFilterEntered)
        showCustomFilter = (filter == selector.Filters.kCustom)

        layout.addRow("", filterUIWidget)
        layout.addRow(maya.stringTable['y_simpleSelector.kByTypeFilter'],
                      self.customFilterEdit)

    @ifNotBlockChangeMessages
    def _filterTypeChanged(self, value):
        filterNameMap = {
            selector.Filters.filterUIName(ftype): ftype
            for ftype in self._selector.getAvailableFilters()
        }
        self._selector.setFilterType(filterNameMap[value])

    def _setupStaticSelector(self):
        staticSelectionWidget = QWidget()
        staticSelectionLayout = QHBoxLayout()
        staticSelectionLayout.setContentsMargins(0, 0, 0, 0)
        staticSelectionLayout.setSpacing(utils.dpiScale(2))
        self.staticSelector = CollectionStaticSelectionWidget(self._selector)
        self.staticSelector.setFixedHeight(self.LIST_BOX_HEIGHT)
        staticSelectionLayout.addWidget(self.staticSelector)

        # Add the drag/drop buttons
        dragDropButtonLayout = QVBoxLayout()
        dragDropButtonLayout.setSpacing(utils.dpiScale(2))
        dragDropButtonLayout.setContentsMargins(0, 0, 0, 0)
        addButton = QPushButton(maya.stringTable['y_simpleSelector.kAdd'])
        addButton.setToolTip(
            maya.stringTable['y_simpleSelector.kStaticAddTooltipStr'])
        addButton.setMinimumWidth(self.EXPRESSION_BUTTON_WIDTH)
        addButton.clicked.connect(self.staticSelector.addEntry)
        dragDropButtonLayout.addWidget(addButton)
        removeButton = QPushButton(
            maya.stringTable['y_simpleSelector.kRemove'])
        removeButton.setToolTip(
            maya.stringTable['y_simpleSelector.kStaticRemoveTooltipStr'])
        removeButton.setMinimumWidth(self.EXPRESSION_BUTTON_WIDTH)
        removeButton.clicked.connect(self.staticSelector.removeEntry)
        dragDropButtonLayout.addWidget(removeButton)
        dragDropButtonLayout.addStretch(1)
        selectButton = QPushButton(self.SELECT_STRING)
        selectButton.setToolTip(
            maya.stringTable['y_simpleSelector.kStaticSelectTooltipStr'])
        selectButton.setMinimumWidth(self.EXPRESSION_BUTTON_WIDTH)
        selectButton.clicked.connect(self.selectStaticEntries)
        dragDropButtonLayout.addWidget(selectButton)
        dragDropButtonWidget = QWidget()
        staticSelectionLayout.addWidget(dragDropButtonWidget)
        dragDropButtonWidget.setLayout(dragDropButtonLayout)
        staticSelectionWidget.setLayout(staticSelectionLayout)
        self.addToCollectionGroupBoxLayout.addRow("", staticSelectionWidget)

    def _setupSelectAllButton(self):
        selectAllButtonWidget = QWidget()
        selectAllButtonLayout = QHBoxLayout()
        selectAllButtonLayout.setContentsMargins(0, 0, 0, 0)
        selectAllButtonLayout.setSpacing(utils.dpiScale(2))
        selectAllButton = QPushButton(
            maya.stringTable['y_simpleSelector.kSelectAll'])
        selectAllButtonLayout.addStretch(1)
        selectAllButtonLayout.addWidget(selectAllButton)
        selectAllButtonLayout.addStretch(1)
        selectAllButtonLayout.addSpacing(self.EXPRESSION_BUTTON_WIDTH)
        selectAllButtonWidget.setLayout(selectAllButtonLayout)
        self.addToCollectionGroupBoxLayout.addRow("", selectAllButtonWidget)

    def _setupFilterGroupBox(self, layout):
        filterGroupBox = QGroupBox(
            maya.stringTable['y_simpleSelector.kCollectionFilters'])
        font = QFont()
        font.setBold(True)
        filterGroupBox.setFont(font)
        filterGroupBox.setContentsMargins(0, utils.dpiScale(12), 0, 0)
        self.filtersGroupBoxLayout = Layout()
        self.filtersGroupBoxLayout.setVerticalSpacing(utils.dpiScale(2))

        self._setupFilterUI(self.filtersGroupBoxLayout)

        filterGroupBox.setLayout(self.filtersGroupBoxLayout)
        layout.addWidget(filterGroupBox)

    def _setupAddToCollectionGroupBox(self, layout):
        addToCollectionGroupBox = QGroupBox(
            maya.stringTable['y_simpleSelector.kAddToCollection'])
        font = QFont()
        font.setBold(True)
        addToCollectionGroupBox.setFont(font)
        addToCollectionGroupBox.setContentsMargins(0, utils.dpiScale(12), 0, 0)
        self.addToCollectionGroupBoxLayout = Layout()
        self.addToCollectionGroupBoxLayout.setVerticalSpacing(
            utils.dpiScale(2))

        self._setupIncludeExpression()
        self._setupStaticSelector()

        addToCollectionGroupBox.setLayout(self.addToCollectionGroupBoxLayout)
        layout.addWidget(addToCollectionGroupBox)

    def includeExpressionChanged(self, text):
        self.expression = text

    @ifNotBlockChangeMessages
    def includeExpressionEntered(self):
        self._selector.setPattern(self.expression)

    @ifNotBlockChangeMessages
    def customFilterEntered(self):
        customFilterText = self.customFilterEdit.text()
        self._selector.setCustomFilterValue(customFilterText)

    @guard.member('_blockChangeMessages', True)
    def populateFields(self):
        filter = self._selector.getFilterType()
        filterIndex = self.filterType.findData(filter)
        self.filterType.setCurrentIndex(filterIndex)

        showCustomFilter = (filter == selector.Filters.kCustom)
        customFilterText = self._selector.getCustomFilterValue()
        self.customFilterEdit.setText(customFilterText)
        self.customFilterEdit.setVisible(showCustomFilter)
        self.filtersGroupBoxLayout.labelForField(
            self.customFilterEdit).setVisible(showCustomFilter)

        expressionText = self._selector.getPattern()
        self.includeExpression.setText(expressionText)

        self.staticSelector.populate()

    def selectIncludeExpression(self):
        cmds.select(list(self._selector.getDynamicNames()),
                    add=False,
                    noExpand=True)

    def selectStaticEntries(self):
        self.staticSelector.selectMembers()
        cmds.select(list(self._selector.getStaticNames()),
                    add=False,
                    noExpand=True)

    def highlight(self, names):
        dynamicNames = self._selector.getDynamicNames()
        if next((name for name in names if name in dynamicNames),
                None) is not None:
            self.includeExpression.setSelection(
                0, len(self.includeExpression.text()))
        staticNames = self._selector.getStaticNames()
        self.staticSelector.highlight(
            set(name for name in names if name in staticNames))
Ejemplo n.º 7
0
class QLayerView(QTableView):
    """
    Display the stack of image layers.
    """
    def __init__(self, parent):
        super().__init__(parent)
        self.img = None
        # graphic form to show : it
        # should correspond to the currently selected layer
        self.currentWin = None
        # mouse click event
        self.clicked.connect(self.viewClicked)

        # set behavior and styles
        self.setSelectionBehavior(QAbstractItemView.SelectRows)
        delegate = itemDelegate(parent=self)
        self.setItemDelegate(delegate)
        ic1 = QImage(":/images/resources/eye-icon.png")
        ic2 = QImage(":/images/resources/eye-icon-strike.png")
        delegate.px1 = QPixmap.fromImage(ic1)
        delegate.px2 = QPixmap.fromImage(ic2)
        ic1.invertPixels()
        ic2.invertPixels()
        delegate.inv_px1 = QPixmap.fromImage(ic1)
        delegate.inv_px2 = QPixmap.fromImage(ic2)
        self.setIconSize(QSize(20, 15))
        self.verticalHeader().setMinimumSectionSize(-1)
        self.verticalHeader().setDefaultSectionSize(
            self.verticalHeader().minimumSectionSize())
        self.horizontalHeader().setMinimumSectionSize(40)
        self.horizontalHeader().setDefaultSectionSize(40)

        # drag and drop
        self.setDragDropMode(QAbstractItemView.DragDrop)
        self.setDefaultDropAction(Qt.MoveAction)
        self.setDragDropOverwriteMode(False)
        self.setDragEnabled(True)
        self.setAcceptDrops(True)
        self.setDropIndicatorShown(True)

        ################################
        # layer property GUI :
        # preview, blending mode, opacity, mask color
        ################################
        # Preview option
        # We should use a QListWidget or a custom optionsWidget
        # (cf. utils.py) :  adding it to QVBoxLayout with mode
        # Qt.AlignBottom does not work.
        self.previewOptionBox = QCheckBox('Preview')
        self.previewOptionBox.setMaximumSize(100, 30)

        # View/Preview changed slot
        def m(state):  # state : Qt.Checked Qt.UnChecked
            if self.img is None:
                return
            useThumb = (state == Qt.Checked)
            if useThumb == self.img.useThumb:
                return
            self.img.useThumb = useThumb
            window.updateStatus()
            self.img.cacheInvalidate()
            try:
                QApplication.setOverrideCursor(
                    Qt.WaitCursor
                )  # TODO 18/04/18 waitcursor already called by applytostack
                QApplication.processEvents()
                # update the whole stack
                self.img.layersStack[0].applyToStack()
                self.img.onImageChanged()
            finally:
                QApplication.restoreOverrideCursor()
                QApplication.processEvents()

        self.previewOptionBox.stateChanged.connect(m)
        self.previewOptionBox.setChecked(True)  # m is not triggered

        # title
        titleLabel = QLabel('Layer')
        titleLabel.setMaximumSize(100, 30)

        # opacity slider
        self.opacitySlider = QbLUeSlider(Qt.Horizontal)
        self.opacitySlider.setStyleSheet(
            QbLUeSlider.bLueSliderDefaultBWStylesheet)
        self.opacitySlider.setTickPosition(QSlider.TicksBelow)
        self.opacitySlider.setRange(0, 100)
        self.opacitySlider.setSingleStep(1)
        self.opacitySlider.setSliderPosition(100)

        self.opacityValue = QLabel()
        font = self.opacityValue.font()
        metrics = QFontMetrics(font)
        w = metrics.width("100 ")
        h = metrics.height()
        self.opacityValue.setMinimumSize(w, h)
        self.opacityValue.setMaximumSize(w, h)
        self.opacityValue.setText('100 ')

        # opacity value changed event handler
        def f1():
            self.opacityValue.setText(str('%d ' % self.opacitySlider.value()))

        # opacity slider released event handler
        def f2():
            try:
                layer = self.img.getActiveLayer()
                layer.setOpacity(self.opacitySlider.value())
                layer.applyToStack()
                self.img.onImageChanged()
            except AttributeError:
                return

        self.opacitySlider.valueChanged.connect(f1)
        self.opacitySlider.sliderReleased.connect(f2)

        # mask color slider
        self.maskLabel = QLabel('Mask Color')
        maskSlider = QbLUeSlider(Qt.Horizontal)
        maskSlider.setStyleSheet(QbLUeSlider.bLueSliderDefaultBWStylesheet)
        maskSlider.setTickPosition(QSlider.TicksBelow)
        maskSlider.setRange(0, 100)
        maskSlider.setSingleStep(1)
        maskSlider.setSliderPosition(100)
        self.maskSlider = maskSlider

        self.maskValue = QLabel()
        font = self.maskValue.font()
        metrics = QFontMetrics(font)
        w = metrics.width("100 ")
        h = metrics.height()
        self.maskValue.setMinimumSize(w, h)
        self.maskValue.setMaximumSize(w, h)
        self.maskValue.setText('100 ')
        # masks are disbled by default
        self.maskLabel.setEnabled(False)
        self.maskSlider.setEnabled(False)
        self.maskValue.setEnabled(False)

        # mask value changed event handler
        def g1():
            self.maskValue.setText(str('%d ' % self.maskSlider.value()))

        # mask slider released event handler
        def g2():
            try:
                layer = self.img.getActiveLayer()
                layer.setColorMaskOpacity(self.maskSlider.value() * 255.0 /
                                          100.0)
                layer.applyToStack()
                self.img.onImageChanged()
            except AttributeError:
                return

        self.maskSlider.valueChanged.connect(g1)
        self.maskSlider.sliderReleased.connect(g2)

        # blending mode combo box
        compLabel = QLabel()
        compLabel.setText("Blend")

        self.compositionModeDict = OrderedDict([
            ('Normal', QPainter.CompositionMode_SourceOver),
            ('Plus', QPainter.CompositionMode_Plus),
            ('Multiply', QPainter.CompositionMode_Multiply),
            ('Screen', QPainter.CompositionMode_Screen),
            ('Overlay', QPainter.CompositionMode_Overlay),
            ('Darken', QPainter.CompositionMode_Darken),
            ('Lighten', QPainter.CompositionMode_Lighten),
            ('Color Dodge', QPainter.CompositionMode_ColorDodge),
            ('Color Burn', QPainter.CompositionMode_ColorBurn),
            ('Hard Light', QPainter.CompositionMode_HardLight),
            ('Soft Light', QPainter.CompositionMode_SoftLight),
            ('Difference', QPainter.CompositionMode_Difference),
            ('Exclusion', QPainter.CompositionMode_Exclusion),
            # Type of previous modes is QPainter.CompositionMode (Shiboken enum-type).
            # Next additional modes are not implemented by QPainter:
            ('Luminosity', -1),
            ('color', -2)
        ])

        self.blendingModeCombo = QComboBox()
        for key in self.compositionModeDict:
            self.blendingModeCombo.addItem(key, self.compositionModeDict[key])

        # combo box item changed slot
        def g(ind):
            layer = self.img.getActiveLayer()
            s = self.blendingModeCombo.currentText()
            newMode = self.compositionModeDict[str(s)]
            if newMode == layer.compositionMode:
                return
            layer.compositionMode = newMode
            layer.applyToStack()
            self.img.onImageChanged()

        self.blendingModeCombo.currentIndexChanged.connect(g)

        # layout
        l = QVBoxLayout()
        l.setAlignment(Qt.AlignTop)
        hl0 = QHBoxLayout()
        hl0.addWidget(titleLabel)
        hl0.addStretch(1)
        hl0.addWidget(self.previewOptionBox)
        l.addLayout(hl0)
        hl = QHBoxLayout()
        hl.addWidget(QLabel('Opacity'))
        hl.addWidget(self.opacityValue)
        hl.addWidget(self.opacitySlider)
        l.addLayout(hl)
        hl1 = QHBoxLayout()
        hl1.addWidget(self.maskLabel)
        hl1.addWidget(self.maskValue)
        hl1.addWidget(self.maskSlider)
        l.addLayout(hl1)
        l.setContentsMargins(0, 0, 10, 0)  # left, top, right, bottom
        hl2 = QHBoxLayout()
        hl2.addWidget(compLabel)
        hl2.addWidget(self.blendingModeCombo)
        l.addLayout(hl2)
        for layout in [hl, hl1, hl2]:
            layout.setContentsMargins(5, 0, 0, 0)
        # this layout must be added to the propertyWidget object loaded from blue.ui :
        # we postpone it to in blue.py, after loading the main form.
        self.propertyLayout = l
        # shortcut actions
        self.actionDup = QAction('Duplicate layer', None)
        self.actionDup.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_J))
        self.addAction(self.actionDup)

        def dup():
            row = self.selectedIndexes()[0].row()
            # Stack index
            index = len(self.img.layersStack) - row - 1
            layer = self.img.layersStack[index]
            if layer.isAdjustLayer():
                return
            # add new layer to stack and set it to active
            self.img.dupLayer(index=index)
            # update layer view
            self.setLayers(self.img)

        self.actionDup.triggered.connect(dup)
        self.setWhatsThis("""<b>Layer Stack</b><br>
To <b>toggle the layer visibility</b> click on the Eye icon.<br>
To <b>add a mask</b> use the context menu to enable it and paint pixels with the Mask/Unmask tools in the left pane.<br>
For <b>color mask<b/b>: <br>
    &nbsp; green pixels are masked,<br>
    &nbsp; red pixels are unmasked.<br>
    &nbsp; Other colors correspond to partially masked pixels.<br>
Note that upper visible layers slow down mask edition.<br>
""")  # end of setWhatsThis

    def closeAdjustForms(self, delete=False):
        """
        Close all windows associated with image layers.
        If delete is True (default False), the windows and
        their dock containers are deleted.
        @param delete:
        @type delete: boolean
        """
        if self.img is None:
            return
        stack = self.img.layersStack
        for layer in stack:
            layer.closeView(delete=delete)
        if delete:
            self.currentWin = None
            gc.collect()

    def clear(self, delete=True):
        """
        Reset LayerView and clear the back
        links to image.
        """
        self.closeAdjustForms(delete=delete)
        self.img = None
        self.currentWin = None
        # model = layerModel()
        # model.setColumnCount(3)  # TODO removed 21/01/20 validate
        self.setModel(None)

    def setLayers(self, mImg, delete=False):
        """
        Displays the layer stack of a mImage instance.
        @param mImg: image
        @type mImg: mImage
        @param delete:
        @type delete:
        """
        # close open adjustment windows
        # self.closeAdjustForms()
        self.clear(delete=delete)
        mImg.layerView = self
        # back link to image
        self.img = weakProxy(mImg)
        model = layerModel()
        model.setColumnCount(3)
        l = len(mImg.layersStack)

        # dataChanged event handler : enables edition of layer name
        def f(index1, index2):
            # index1 and index2 should be equal
            # only layer name should be editable
            # dropEvent emit dataChanged when setting item values. f must
            # return immediately from these calls, as they are possibly made with unconsistent data :
            # dragged rows are already removed from layersStack
            # and not yet removed from model.
            if l != self.model().rowCount():
                return
            # only name is editable
            if index1.column() != 1:
                return
            row = index1.row()
            stackIndex = l - row - 1
            mImg.layersStack[stackIndex].name = index1.data()

        model.dataChanged.connect(f)
        for r, lay in enumerate(reversed(mImg.layersStack)):
            try:
                lay.maskSettingsChanged.sig.disconnect()
            except RuntimeError:
                pass
            lay.maskSettingsChanged.sig.connect(self.updateRows)
            items = []
            # col 0 : visibility icon
            if lay.visible:
                item_visible = QStandardItem(
                    QIcon(":/images/resources/eye-icon.png"), "")
            else:
                item_visible = QStandardItem(
                    QIcon(":/images/resources/eye-icon-strike.png"), "")
            items.append(item_visible)
            # col 1 : image icon (for non-adjustment layer only) and name
            if len(lay.name) <= 30:
                name = lay.name
            else:
                name = lay.name[:28] + '...'
            if hasattr(lay, 'inputImg'):
                item_name = QStandardItem(name)
            else:
                # icon with very small dim causes QPainter error
                # QPixmap.fromImage bug ?
                smallImg = lay.resized(50, 50)
                w, h = smallImg.width(), smallImg.height()
                if w < h / 5 or h < w / 5:
                    item_name = QStandardItem(name)
                else:
                    item_name = QStandardItem(
                        QIcon(QPixmap.fromImage(smallImg)), name)
            # set tool tip to full name
            item_name.setToolTip(lay.name)
            items.append(item_name)
            item_mask = QStandardItem('m')
            items.append(item_mask)
            model.appendRow(items)
        self.setModel(model)
        self.horizontalHeader().hide()
        self.verticalHeader().hide()
        header = self.horizontalHeader()
        header.setSectionResizeMode(0, QHeaderView.ResizeToContents)
        header.setSectionResizeMode(1, QHeaderView.ResizeToContents)
        header.setSectionResizeMode(2, QHeaderView.ResizeToContents)
        # lay out  the graphic forms into right pane
        forms = [
            item.view for item in mImg.layersStack
            if getattr(item, 'view', None) is not None
        ]
        for dock in forms:
            if TABBING:
                dockedForms = [item for item in forms if not item.isFloating()]
                dock.show()  # needed to get working tabbing
                if dock not in dockedForms and dock.tabbed:
                    if dockedForms:
                        window.tabifyDockWidget(dockedForms[-1], dock)
                    else:
                        window.addDockWidget(Qt.RightDockWidgetArea, dock)
            dock.setFloating(False)
        # select active layer
        self.selectRow(len(mImg.layersStack) - 1 - mImg.activeLayerIndex)
        activeLayer = mImg.getActiveLayer()
        layerview = activeLayer.view
        if layerview is not None:
            layerview.show()
            if TABBING:
                layerview.raise_()
            # lay out subcontrols of activeLayer
            form = layerview.widget()
            for dk in form.subControls:
                dk.setVisible(form.options[dk.widget().optionName])
                # clean up: we (re)dock all sucontrols
                dk.setFloating(False)  # emit topLevelChanged signal
        self.opacitySlider.setSliderPosition(int(activeLayer.opacity * 100))
        self.maskSlider.setSliderPosition(
            int(activeLayer.colorMaskOpacity * 100.0 / 255.0))
        ind = self.blendingModeCombo.findData(activeLayer.compositionMode)
        self.blendingModeCombo.setCurrentIndex(ind)
        self.previewOptionBox.setChecked(activeLayer.parentImage.useThumb)
        #activeLayer.maskColor
        self.updateForm()
        """                                                   # TODO removed 25/01/20 useless validate
        for item in self.img.layersStack:
            if hasattr(item, 'sourceIndex'):
                combo = item.getGraphicsForm().sourceCombo
                currentText = combo.currentText()
                combo.clear()
                for i, x in enumerate(self.img.layersStack):
                    item.view.widget().sourceCombo.addItem(x.name, i)
                combo.setCurrentIndex(combo.findText(currentText))
        """

    def updateForm(self):
        activeLayer = self.img.getActiveLayer()
        if hasattr(activeLayer, 'view'):
            self.currentWin = activeLayer.view
        if self.currentWin is not None:
            self.currentWin.show()
            self.currentWin.activateWindow()

    def updateRow(self, row):
        minInd, maxInd = self.model().index(row, 0), self.model().index(row, 3)
        self.model().dataChanged.emit(minInd, maxInd)

    def updateRows(self):
        """
        Update all rows.
        """
        count = self.model().rowCount()
        minInd, maxInd = self.model().index(0, 0), self.model().index(
            count - 1, 3)
        self.model().dataChanged.emit(minInd, maxInd)

    def dropEvent(self, event):
        """
        drop event handler : moving layer
        @param event:
        @type event: Qevent
        """
        if event.source() is not self:
            return
        # get selected rows and layers
        rows = set([mi.row() for mi in self.selectedIndexes()])
        rStack = self.img.layersStack[::-1]
        layers = [rStack[i] for i in rows]
        # get target row and layer
        targetRow = self.indexAt(event.pos()).row()
        targetLayer = rStack[targetRow]
        # remove target from selection
        if targetRow in rows:
            rows.discard(targetRow)
        rows = sorted(rows)
        if not rows:
            return
        # if target is below last row insert at the last position
        if targetRow == -1:
            targetRow = self.model().rowCount()
        # mapping of src (row) indices to target indices
        rowMapping = dict()
        for idx, row in enumerate(rows):
            if row < targetRow:
                rowMapping[row] = targetRow + idx
            else:
                rowMapping[row + len(rows)] = targetRow + idx
        # update layerStack using rowMapping
        # insert None items
        for _ in range(len(rows)):
            rStack.insert(targetRow, None)
        # copy moved items to their final place
        for srcRow, tgtRow in sorted(
                rowMapping.items()):  # python 3 iteritems->items
            rStack[tgtRow] = rStack[srcRow]
        # remove moved items from their initial place
        for row in reversed(sorted(
                rowMapping.keys())):  # python 3 iterkeys -> keys
            rStack.pop(row)
        self.img.layersStack = rStack[::-1]
        # update model
        # insert empty rows
        for _ in range(len(rows)):
            result = self.model().insertRow(targetRow, QModelIndex())
        # copy moved rows to their final place
        colCount = self.model().columnCount()
        for srcRow, tgtRow in sorted(
                rowMapping.items()):  # python 3 iteritems->items
            for col in range(0, colCount):
                # CAUTION : setItem calls the data changed event handler (cf. setLayers above)
                self.model().setItem(tgtRow, col,
                                     self.model().takeItem(srcRow, col))
        # remove moved rows from their initial place and keep track of moved items
        movedDict = rowMapping.copy()
        for row in reversed(sorted(
                rowMapping.keys())):  # python 3 iterkeys -> keys
            self.model().removeRow(row)
            for s, t in rowMapping.items():
                if t > row:
                    movedDict[s] -= 1
        ######################################### sanity check
        for r in range(self.model().rowCount()):
            id = self.model().index(r, 1)
            if id.data() != rStack[r].name:
                raise ValueError('Drop Error')
        ########################################
        # reselect moved rows
        sel = sorted(movedDict.values())
        selectionModel = QtCore.QItemSelectionModel(self.model())
        self.setSelectionModel(selectionModel)
        index1 = self.model().index(sel[0], 1)
        index2 = self.model().index(sel[-1], 1)
        itemSelection = QtCore.QItemSelection(index1, index2)
        self.selectionModel().select(
            itemSelection, QtCore.QItemSelectionModel.Rows
            | QtCore.QItemSelectionModel.Select)
        # multiple selection : display no window
        if len(sel) > 1:
            self.currentWin.hide()
            self.currentWin = None
        elif len(sel) == 1:
            self.img.setActiveLayer(len(self.img.layersStack) - sel[0] - 1)
        # update stack
        self.img.layersStack[0].applyToStack()
        self.img.onImageChanged()

    def select(self, row, col):
        """
        select item in view
        @param row:
        @type row:
        @param col:
        @type col:
        @return:
        @rtype:
        """
        model = self.model()
        self.viewClicked(model.index(row, col))

    def viewClicked(self, clickedIndex):
        """
        Mouse clicked event handler.
        @param clickedIndex: 
        @type clickedIndex: QModelIndex
        """
        row = clickedIndex.row()
        rows = set([mi.row() for mi in self.selectedIndexes()])
        # multiple selection : go to top of selection
        m = min(rows)
        if row != m:
            clickedIndex = self.model().index(m, clickedIndex.column())
        layer = self.img.layersStack[-1 - row]
        self.actionDup.setEnabled(not layer.isAdjustLayer())
        # toggle layer visibility
        if clickedIndex.column() == 0:
            # background layer is always visible
            if row == len(self.img.layersStack) - 1:
                return
            # layer.visible = not(layer.visible)
            layer.setVisible(not layer.visible)
            if self.currentWin is not None:
                self.currentWin.setVisible(layer.visible)
                if not layer.visible:
                    self.currentWin = None
            if layer.tool is not None:
                layer.tool.setVisible(layer.visible)
            # update stack
            if layer.visible:
                layer.applyToStack()
            else:
                i = layer.getUpperVisibleStackIndex()
                if i >= 0:
                    layer.parentImage.layersStack[i].applyToStack()
                else:
                    # top layer : update only the presentation layer
                    layer.parentImage.prLayer.execute(l=None, pool=None)
            self.img.onImageChanged()
        # update displayed window and active layer
        activeStackIndex = len(self.img.layersStack) - 1 - row
        activeLayer = self.img.setActiveLayer(activeStackIndex)
        # update color mask slider and label
        self.maskLabel.setEnabled(layer.maskIsSelected)
        self.maskSlider.setEnabled(activeLayer.maskIsSelected)
        self.maskValue.setEnabled(activeLayer.maskIsSelected)
        if self.currentWin is not None:
            # hide sucontrols
            for dk in self.currentWin.widget().subControls:
                dk.hide()
            if self.currentWin.isFloating():
                self.currentWin.hide()
        self.currentWin = None
        if hasattr(activeLayer, "view"):
            self.currentWin = activeLayer.view
        if self.currentWin is not None and activeLayer.visible:
            self.currentWin.show()
            self.currentWin.raise_()
            # display subcontrols
            for dk in self.currentWin.widget().subControls:
                dk.setVisible(
                    self.currentWin.widget().options[dk.widget().optionName])
            # make self.currentWin the active window
            self.currentWin.activateWindow()
        # update opacity and composition mode for current layer
        opacity = int(layer.opacity * 100)
        self.opacityValue.setText(str('%d ' % opacity))
        self.opacitySlider.setSliderPosition(opacity)
        compositionMode = layer.compositionMode
        ind = self.blendingModeCombo.findData(compositionMode)
        self.blendingModeCombo.setCurrentIndex(ind)

        # draw the right rectangle
        window.label.repaint()

    def initContextMenu(self):
        """
        Context menu initialization
        @return:
        @rtype: QMenu
        """
        menu = QMenu()
        # menu.actionReset = QAction('Reset To Default', None)
        # menu.actionLoadImage = QAction('Load New Image', None)
        # multiple selections
        menu.actionMerge = QAction('Merge Lower', None)
        # merge only adjustment layer with image layer
        menu.actionRepositionLayer = QAction('Reposition Layer(s)', None)
        menu.actionColorMaskEnable = QAction('Color', None)
        menu.actionOpacityMaskEnable = QAction('Opacity', None)
        menu.actionClippingMaskEnable = QAction('Clipping', None)
        menu.actionMaskDisable = QAction('Disabled', None)
        menu.actionMaskUndo = QAction('Undo Mask', None)
        menu.actionMaskRedo = QAction('Redo Mask', None)
        menu.actionMaskInvert = QAction('Invert Mask', None)
        menu.actionMaskReset_UM = QAction('Unmask All', None)
        menu.actionMaskReset_M = QAction('Mask All', None)
        menu.actionMaskCopy = QAction('Copy Mask to Clipboard', None)
        menu.actionImageCopy = QAction('Copy Image to Clipboard', None)
        menu.actionMaskPaste = QAction('Paste Mask', None)
        menu.actionImagePaste = QAction('Paste Image', None)
        menu.actionMaskDilate = QAction('Dilate Mask', None)
        menu.actionMaskErode = QAction('Erode Mask', None)
        menu.actionMaskSmooth = QAction('Smooth Mask', None)
        menu.actionMaskBright1 = QAction('Bright 1 Mask', None)
        menu.actionMaskBright2 = QAction('Bright 2 Mask', None)
        menu.actionMaskBright3 = QAction('Bright 3 Mask', None)
        menu.actionMaskDark1 = QAction('Dark 1 Mask', None)
        menu.actionMaskDark2 = QAction('Dark 2 Mask', None)
        menu.actionMaskDark3 = QAction('Dark 3 Mask', None)
        menu.actionMaskMid1 = QAction('Mid 1 Mask', None)
        menu.actionMaskMid2 = QAction('Mid 2 Mask', None)
        menu.actionMaskMid3 = QAction('Mid 3 Mask', None)
        menu.actionMergingFlag = QAction('Merged Layer', None)
        menu.actionMergingFlag.setCheckable(True)
        menu.actionColorMaskEnable.setCheckable(True)
        menu.actionOpacityMaskEnable.setCheckable(True)
        menu.actionClippingMaskEnable.setCheckable(True)
        menu.actionMaskDisable.setCheckable(True)
        ####################
        # Build menu
        ###################
        menu.addAction(menu.actionRepositionLayer)
        menu.addSeparator()
        # layer
        menu.addAction(menu.actionImageCopy)
        menu.addAction(menu.actionImagePaste)
        menu.addAction(menu.actionMergingFlag)
        menu.addSeparator()
        # mask
        menu.subMenuEnable = menu.addMenu('Mask...')
        menu.subMenuEnable.addAction(menu.actionColorMaskEnable)
        menu.subMenuEnable.addAction(menu.actionOpacityMaskEnable)
        menu.subMenuEnable.addAction(menu.actionClippingMaskEnable)
        menu.subMenuEnable.addAction(menu.actionMaskDisable)
        menu.addAction(menu.actionMaskUndo)
        menu.addAction(menu.actionMaskRedo)
        menu.addAction(menu.actionMaskInvert)
        menu.subMenuLum = menu.addMenu('Luminosity Mask...')
        for a in [
                menu.actionMaskBright1, menu.actionMaskBright2,
                menu.actionMaskBright3, menu.actionMaskDark1,
                menu.actionMaskDark2, menu.actionMaskDark3,
                menu.actionMaskMid1, menu.actionMaskMid2, menu.actionMaskMid3
        ]:
            menu.subMenuLum.addAction(a)
        menu.addAction(menu.actionMaskReset_UM)
        menu.addAction(menu.actionMaskReset_M)
        menu.addAction(menu.actionMaskCopy)
        menu.addAction(menu.actionMaskPaste)
        menu.addAction(menu.actionMaskDilate)
        menu.addAction(menu.actionMaskErode)
        menu.addAction(menu.actionMaskSmooth)
        menu.addSeparator()
        # miscellaneous
        # menu.addAction(menu.actionLoadImage)
        # to link actionDup with a shortcut,
        # it must be set in __init__
        menu.addAction(self.actionDup)
        menu.addAction(menu.actionMerge)
        # menu.addAction(menu.actionReset)
        return menu

    def contextMenuEvent(self, event):
        """
        context menu handler
        @param event
        @type event: QContextMenuEvent
        """
        selection = self.selectedIndexes()
        if not selection:
            return
        # get a fresh context menu without connected actions
        # and with state corresponding to the currently clicked layer
        self.cMenu = self.initContextMenu()
        # get current selection
        rows = set([mi.row() for mi in selection])
        rStack = self.img.layersStack[::-1]
        layers = [rStack[r] for r in rows]
        # get current position
        index = self.indexAt(event.pos())
        layerStackIndex = len(self.img.layersStack) - 1 - index.row()
        layer = self.img.layersStack[layerStackIndex]
        lowerVisible = self.img.layersStack[layer.getLowerVisibleStackIndex()]
        lower = self.img.layersStack[layerStackIndex -
                                     1]  # case index == 0 doesn't matter
        # toggle actions
        self.cMenu.actionMergingFlag.setChecked(layer.mergingFlag)
        self.cMenu.actionMerge.setEnabled(not (
            hasattr(layer, 'inputImg') or hasattr(lowerVisible, 'inputImg')))
        self.actionDup.setEnabled(not layer.isAdjustLayer())
        self.cMenu.actionColorMaskEnable.setChecked(layer.maskIsSelected
                                                    and layer.maskIsEnabled)
        self.cMenu.actionOpacityMaskEnable.setChecked(
            (not layer.maskIsSelected) and layer.maskIsEnabled)
        self.cMenu.actionClippingMaskEnable.setChecked(
            layer.isClipping and (layer.maskIsSelected or layer.maskIsEnabled))
        self.cMenu.actionMaskDisable.setChecked(not (
            layer.isClipping or layer.maskIsSelected or layer.maskIsEnabled))
        self.cMenu.actionMaskUndo.setEnabled(layer.historyListMask.canUndo())
        self.cMenu.actionMaskRedo.setEnabled(layer.historyListMask.canRedo())
        self.cMenu.subMenuEnable.setEnabled(len(rows) == 1)
        self.cMenu.actionMaskPaste.setEnabled(
            not QApplication.clipboard().image().isNull())
        self.cMenu.actionImagePaste.setEnabled(
            not QApplication.clipboard().image().isNull())
        self.cMenu.actionMergingFlag.setEnabled(layer.isImageLayer())

        # Event handlers

        def RepositionLayer():
            layer.xOffset, layer.yOffset = 0, 0
            layer.Zoom_coeff = 1.0
            layer.AltZoom_coeff = 1.0
            layer.xAltOffset, layer.yAltOffset = 0, 0
            layer.updatePixmap()
            self.img.onImageChanged()

        def merge():
            layer.merge_with_layer_immediately_below()

        def testUpperVisibility():
            pos = self.img.getStackIndex(layer)
            upperVisible = False
            for i in range(len(self.img.layersStack) - pos - 1):
                if self.img.layersStack[pos + 1 + i].visible:
                    upperVisible = True
                    break
            if upperVisible:
                dlgWarn("Upper visible layers slow down mask edition")
                return True
            return False

        def colorMaskEnable():
            testUpperVisibility()
            layer.maskIsEnabled = True
            layer.maskIsSelected = True
            self.maskLabel.setEnabled(layer.maskIsSelected)
            self.maskSlider.setEnabled(layer.maskIsSelected)
            self.maskValue.setEnabled(layer.maskIsSelected)
            layer.applyToStack()
            self.img.onImageChanged()

        def opacityMaskEnable():
            testUpperVisibility()
            layer.maskIsEnabled = True
            layer.maskIsSelected = False
            self.maskLabel.setEnabled(layer.maskIsSelected)
            self.maskSlider.setEnabled(layer.maskIsSelected)
            self.maskValue.setEnabled(layer.maskIsSelected)
            layer.applyToStack()
            self.img.onImageChanged()

        def clippingMaskEnable():
            layer.maskIsEnabled = True
            layer.maskIsSelected = False
            self.maskLabel.setEnabled(layer.maskIsSelected)
            self.maskSlider.setEnabled(layer.maskIsSelected)
            self.maskValue.setEnabled(layer.maskIsSelected)
            layer.isClipping = True
            layer.applyToStack()
            self.img.onImageChanged()

        def maskDisable():
            layer.maskIsEnabled = False
            layer.maskIsSelected = False
            self.maskLabel.setEnabled(layer.maskIsSelected)
            self.maskSlider.setEnabled(layer.maskIsSelected)
            self.maskValue.setEnabled(layer.maskIsSelected)
            layer.isClipping = False
            layer.applyToStack()
            self.img.onImageChanged()

        def undoMask():
            try:
                layer.mask = layer.historyListMask.undo(
                    saveitem=layer.mask.copy())
                layer.applyToStack()
                self.img.onImageChanged()
            except ValueError:
                pass

        def redoMask():
            try:
                layer.mask = layer.historyListMask.redo()
                layer.applyToStack()
                self.img.onImageChanged()
            except ValueError:
                pass

        def maskInvert():
            layer.invertMask()
            # update mask stack
            layer.applyToStack()
            self.img.onImageChanged()

        def maskReset_UM():
            layer.resetMask(maskAll=False)
            # update mask stack
            for l in self.img.layersStack:
                l.updatePixmap(maskOnly=True)
            self.img.prLayer.execute(l=None, pool=None)
            self.img.onImageChanged()

        def maskReset_M():
            layer.resetMask(maskAll=True)
            # update mask stack
            for l in self.img.layersStack:
                l.updatePixmap(maskOnly=True)
            self.img.prLayer.execute(l=None, pool=None)
            self.img.onImageChanged()

        def maskCopy():
            QApplication.clipboard().setImage(layer.mask)

        def imageCopy():
            QApplication.clipboard().setImage(layer.getCurrentMaskedImage())

        def maskPaste():
            """
            Pastes clipboard to mask and updates the stack. The clipboard image
            is scaled if its size does not match the size of the mask
            """
            cb = QApplication.clipboard()
            if not cb.image().isNull():
                img = cb.image()
                if img.size() == layer.mask.size():
                    layer.mask = img
                else:
                    layer.mask = img.scaled(layer.mask.size())
            layer.applyToStack()
            self.img.prLayer.execute(l=None, pool=None)
            self.img.onImageChanged()

        def imagePaste():
            """
            Pastes clipboard to mask and updates the stack. The clipboard image
            is scaled if its size does not match the size of the mask
            """
            cb = QApplication.clipboard()
            if not cb.image().isNull():
                srcImg = cb.image()
                if srcImg.size() == layer.size():
                    layer.setImage(srcImg)
                else:
                    layer.setImage(srcImg.scaled(layer.size()))
            layer.applyToStack()
            self.img.onImageChanged()

        def maskDilate():
            """
            Increase the masked part of the image
            """
            buf = QImageBuffer(layer.mask)
            buf[:, :, 2] = vImage.maskDilate(buf[:, :, 2])
            for l in self.img.layersStack:
                l.updatePixmap(maskOnly=True)
            self.img.prLayer.update()
            self.img.onImageChanged()

        def maskErode():
            """
            Reduce the masked part of the image
            """
            buf = QImageBuffer(layer.mask)
            buf[:, :, 2] = vImage.maskErode(buf[:, :, 2])
            for l in self.img.layersStack:
                l.updatePixmap(maskOnly=True)
            self.img.prLayer.update()
            self.img.onImageChanged()

        def maskSmooth():
            """
            Smooth the mask boundary
            """
            buf = QImageBuffer(layer.mask)
            buf[:, :, 2] = vImage.maskSmooth(buf[:, :, 2])
            for l in self.img.layersStack:
                l.updatePixmap(maskOnly=True)
            self.img.prLayer.update()
            self.img.onImageChanged()

        def maskBright1():
            layer.setMaskLuminosity(min=128, max=255)
            for l in self.img.layersStack:
                l.updatePixmap(maskOnly=True)
            self.img.prLayer.update()
            self.img.onImageChanged()

        def maskBright2():
            layer.setMaskLuminosity(min=192, max=255)
            for l in self.img.layersStack:
                l.updatePixmap(maskOnly=True)
            self.img.prLayer.update()
            self.img.onImageChanged()

        def maskBright3():
            layer.setMaskLuminosity(min=224, max=255)
            for l in self.img.layersStack:
                l.updatePixmap(maskOnly=True)
            self.img.prLayer.update()
            self.img.onImageChanged()

        def maskDark1():
            layer.setMaskLuminosity(min=0, max=128)

        def maskDark2():
            layer.setMaskLuminosity(min=0, max=64)
            for l in self.img.layersStack:
                l.updatePixmap(maskOnly=True)
            self.img.prLayer.update()
            self.img.onImageChanged()

        def maskDark3():
            layer.setMaskLuminosity(min=0, max=32)
            for l in self.img.layersStack:
                l.updatePixmap(maskOnly=True)
            self.img.prLayer.update()
            self.img.onImageChanged()

        def maskMid1():
            layer.setMaskLuminosity(min=64, max=192)
            for l in self.img.layersStack:
                l.updatePixmap(maskOnly=True)
            self.img.prLayer.update()
            self.img.onImageChanged()

        def maskMid2():
            layer.setMaskLuminosity(min=96, max=160)
            for l in self.img.layersStack:
                l.updatePixmap(maskOnly=True)
            self.img.prLayer.update()
            self.img.onImageChanged()

        def maskMid3():
            layer.setMaskLuminosity(min=112, max=144)
            for l in self.img.layersStack:
                l.updatePixmap(maskOnly=True)
            self.img.prLayer.update()
            self.img.onImageChanged()

        def mergingFlag(flag):
            layer.mergingFlag = flag

        self.cMenu.actionRepositionLayer.triggered.connect(RepositionLayer)
        # self.cMenu.actionLoadImage.triggered.connect(loadImage)
        self.cMenu.actionMerge.triggered.connect(merge)
        self.cMenu.actionColorMaskEnable.triggered.connect(colorMaskEnable)
        self.cMenu.actionOpacityMaskEnable.triggered.connect(opacityMaskEnable)
        self.cMenu.actionClippingMaskEnable.triggered.connect(
            clippingMaskEnable)
        self.cMenu.actionMaskDisable.triggered.connect(maskDisable)
        self.cMenu.actionMaskUndo.triggered.connect(undoMask)
        self.cMenu.actionMaskRedo.triggered.connect(redoMask)
        self.cMenu.actionMaskInvert.triggered.connect(maskInvert)
        self.cMenu.actionMaskReset_UM.triggered.connect(maskReset_UM)
        self.cMenu.actionMaskReset_M.triggered.connect(maskReset_M)
        self.cMenu.actionMaskCopy.triggered.connect(maskCopy)
        self.cMenu.actionMaskPaste.triggered.connect(maskPaste)
        self.cMenu.actionImageCopy.triggered.connect(imageCopy)
        self.cMenu.actionImagePaste.triggered.connect(imagePaste)
        self.cMenu.actionMaskDilate.triggered.connect(maskDilate)
        self.cMenu.actionMaskErode.triggered.connect(maskErode)
        self.cMenu.actionMaskSmooth.triggered.connect(maskSmooth)
        self.cMenu.actionMaskBright1.triggered.connect(maskBright1)
        self.cMenu.actionMaskBright2.triggered.connect(maskBright2)
        self.cMenu.actionMaskBright3.triggered.connect(maskBright3)
        self.cMenu.actionMaskDark1.triggered.connect(maskDark1)
        self.cMenu.actionMaskDark2.triggered.connect(maskDark2)
        self.cMenu.actionMaskDark3.triggered.connect(maskDark3)
        self.cMenu.actionMaskMid1.triggered.connect(maskMid1)
        self.cMenu.actionMaskMid2.triggered.connect(maskMid2)
        self.cMenu.actionMaskMid3.triggered.connect(maskMid3)
        self.cMenu.actionMergingFlag.toggled.connect(mergingFlag)
        self.cMenu.exec_(event.globalPos() - QPoint(400, 0))
        # update table
        for row in rows:
            self.updateRow(row)
Ejemplo n.º 8
0
class QDisasmStatusBar(QFrame):
    """
    Status and control bar for disassembly views
    """
    def __init__(self, disasm_view, parent=None):
        super().__init__(parent)

        self.disasm_view = disasm_view

        # widgets
        self._nav_toolbar: Optional[NavToolbar] = None
        self._function_label: QLabel = None
        self._options_menu: DisasmOptionsMenu = None
        self._view_combo: QComboBox = None

        # information
        self._function = None

        self._init_menu()
        self._init_widgets()

    @property
    def function(self):
        return self._function

    @function.setter
    def function(self, f):
        self._function = f

        self._update_function_address()

    @property
    def function_address(self):
        if self._function is None:
            return None
        return self._function.addr

    #
    # Initialization
    #

    def _init_widgets(self):
        self._nav_toolbar = NavToolbar(
            self.disasm_view.jump_history, self.disasm_view.jump_back,
            self.disasm_view.jump_forward,
            self.disasm_view.jump_to_history_position, True, self)

        # current function
        self._function_label = QLabel()

        self._view_combo = QComboBox(self)
        self._view_combo.addItem("Linear Disassembly", QLinearDisassembly)
        self._view_combo.addItem("Graph Disassembly", QDisassemblyGraph)
        self._view_combo.activated.connect(self._view_combo_changed)
        self.disasm_view.view_visibility_changed.connect(
            self._update_view_combo)
        self._update_view_combo()

        self._disasm_level_combo = QComboBox(self)
        self._disasm_level_combo.addItem("Machine Code",
                                         DisassemblyLevel.MachineCode)
        self._disasm_level_combo.addItem("Lifter IR",
                                         DisassemblyLevel.LifterIR)
        self._disasm_level_combo.addItem("AIL", DisassemblyLevel.AIL)
        self._disasm_level_combo.activated.connect(
            self._disasm_level_combo_changed)
        self.disasm_view.disassembly_level_changed.connect(
            self._update_disasm_level_combo)
        self._update_disasm_level_combo()

        # options button
        option_btn = QPushButton()
        option_btn.setText('Options')
        option_btn.setMenu(self._options_menu.qmenu())

        # Save image button
        saveimage_btn = QPushButton()
        saveimage_btn.setText('Save image...')
        saveimage_btn.clicked.connect(self._on_saveimage_btn_clicked)

        layout = QHBoxLayout()
        layout.setContentsMargins(2, 2, 2, 2)

        layout.addWidget(self._nav_toolbar.qtoolbar())
        layout.addWidget(self._function_label)

        layout.addStretch(0)

        layout.addWidget(saveimage_btn)
        layout.addWidget(self._view_combo)
        layout.addWidget(self._disasm_level_combo)
        layout.addWidget(option_btn)
        layout.setContentsMargins(0, 0, 0, 0)

        self.setLayout(layout)

    def _init_menu(self):
        self._options_menu = DisasmOptionsMenu(self.disasm_view)

    #
    # Private methods
    #

    def _view_combo_changed(self, index: int):
        {
            QLinearDisassembly: self.disasm_view.display_linear_viewer,
            QDisassemblyGraph: self.disasm_view.display_disasm_graph
        }[self._view_combo.itemData(index)]()

    def _update_view_combo(self):
        graph_type = type(self.disasm_view.current_graph)
        index = self._view_combo.findData(graph_type)
        self._view_combo.setCurrentIndex(index)

    def _disasm_level_combo_changed(self, index: int):
        new_level = self._disasm_level_combo.itemData(index)
        self.disasm_view.set_disassembly_level(new_level)

    def _update_disasm_level_combo(self):
        new_level = self.disasm_view.disassembly_level
        index = self._disasm_level_combo.findData(new_level)
        self._disasm_level_combo.setCurrentIndex(index)

    def _update_function_address(self):
        if self.function_address is not None:
            self._function_label.setText("Function %x" % self.function_address)

    def _on_saveimage_btn_clicked(self):

        filename, folder = QFileDialog.getSaveFileName(
            self, 'Save image...', '', 'PNG Files (*.png);;Bitmaps (*.bmp)')
        if not filename or not folder:
            return

        self.disasm_view.save_image_to(os.path.join(folder, filename))
Ejemplo n.º 9
0
class QLayerView(QTableView):
    """
    Display the stack of image layers.
    """
    def __init__(self, parent):
        super(QLayerView, self).__init__(parent)
        self.img = None
        # graphic form to show : it
        # should correspond to the currently selected layer
        self.currentWin = None
        # mouse click event
        self.clicked.connect(self.viewClicked)

        # set behavior and styles
        self.setSelectionBehavior(QAbstractItemView.SelectRows)
        delegate = itemDelegate(parent=self)
        self.setItemDelegate(delegate)
        ic1 = QImage(":/images/resources/eye-icon.png")
        ic2 = QImage(":/images/resources/eye-icon-strike.png")
        delegate.px1 = QPixmap.fromImage(ic1)
        delegate.px2 = QPixmap.fromImage(ic2)
        ic1.invertPixels()
        ic2.invertPixels()
        delegate.inv_px1 = QPixmap.fromImage(ic1)
        delegate.inv_px2 = QPixmap.fromImage(ic2)
        self.setIconSize(QSize(20, 15))
        self.verticalHeader().setMinimumSectionSize(-1)
        self.verticalHeader().setDefaultSectionSize(
            self.verticalHeader().minimumSectionSize())
        self.horizontalHeader().setMinimumSectionSize(40)
        self.horizontalHeader().setDefaultSectionSize(40)

        # drag and drop
        self.setDragDropMode(QAbstractItemView.DragDrop)
        self.setDefaultDropAction(Qt.MoveAction)
        self.setDragDropOverwriteMode(False)
        self.setDragEnabled(True)
        self.setAcceptDrops(True)
        self.setDropIndicatorShown(True)

        ################################
        # layer property GUI :
        # preview, blending mode, opacity, mask color
        ################################
        # Preview option
        # We should use a QListWidget or a custom optionsWidget
        # (cf. utils.py) :  adding it to QVBoxLayout with mode
        # Qt.AlignBottom does not work.
        self.previewOptionBox = QCheckBox('Preview')
        self.previewOptionBox.setMaximumSize(100, 30)

        # View/Preview changed event handler
        def m(state):  # state : Qt.Checked Qt.UnChecked
            if self.img is None:
                return
            self.img.useThumb = (state == Qt.Checked)
            window.updateStatus()
            self.img.cacheInvalidate()
            for layer in self.img.layersStack:
                layer.autoclone = True  # auto update cloning layers
                layer.knitted = False
            try:
                QApplication.setOverrideCursor(
                    Qt.WaitCursor
                )  # TODO 18/04/18 waitcursor is called by applytostack?
                QApplication.processEvents()
                # update the whole stack
                self.img.layersStack[0].applyToStack()
                self.img.onImageChanged()  # TODO added 30/11/18 validate

            finally:
                for layer in self.img.layersStack:
                    layer.autoclone = False  # reset flags
                    layer.knitted = False
                QApplication.restoreOverrideCursor()
                QApplication.processEvents()
            # window.label.repaint()  # TODO removed 30/11/18 replaced by onImageChange above

        self.previewOptionBox.stateChanged.connect(m)
        self.previewOptionBox.setChecked(True)  # m is not triggered

        # title
        titleLabel = QLabel('Layer')
        titleLabel.setMaximumSize(100, 30)

        # opacity slider
        self.opacitySlider = QbLUeSlider(Qt.Horizontal)
        self.opacitySlider.setStyleSheet(
            QbLUeSlider.bLueSliderDefaultBWStylesheet)
        self.opacitySlider.setTickPosition(QSlider.TicksBelow)
        self.opacitySlider.setRange(0, 100)
        self.opacitySlider.setSingleStep(1)
        self.opacitySlider.setSliderPosition(100)

        self.opacityValue = QLabel()
        font = self.opacityValue.font()
        metrics = QFontMetrics(font)
        w = metrics.width("100 ")
        h = metrics.height()
        self.opacityValue.setMinimumSize(w, h)
        self.opacityValue.setMaximumSize(w, h)
        self.opacityValue.setText('100 ')

        # opacity value changed event handler
        def f1():
            self.opacityValue.setText(str('%d ' % self.opacitySlider.value()))

        # opacity slider released event handler
        def f2():
            try:
                layer = self.img.getActiveLayer()
                layer.setOpacity(self.opacitySlider.value())
                layer.applyToStack()
                self.img.onImageChanged()
            except AttributeError:
                return

        self.opacitySlider.valueChanged.connect(f1)
        self.opacitySlider.sliderReleased.connect(f2)

        # mask color slider
        maskSlider = QbLUeSlider(Qt.Horizontal)
        maskSlider.setStyleSheet(QbLUeSlider.bLueSliderDefaultBWStylesheet)
        maskSlider.setTickPosition(QSlider.TicksBelow)
        maskSlider.setRange(0, 100)
        maskSlider.setSingleStep(1)
        maskSlider.setSliderPosition(100)
        self.maskSlider = maskSlider

        self.maskValue = QLabel()
        font = self.maskValue.font()
        metrics = QFontMetrics(font)
        w = metrics.width("100 ")
        h = metrics.height()
        self.maskValue.setMinimumSize(w, h)
        self.maskValue.setMaximumSize(w, h)
        self.maskValue.setText('100 ')

        # mask value changed event handler
        def g1():
            self.maskValue.setText(str('%d ' % self.maskSlider.value()))

        # mask slider released event handler
        def g2():
            try:
                layer = self.img.getActiveLayer()
                layer.setColorMaskOpacity(self.maskSlider.value())
                layer.applyToStack()
                self.img.onImageChanged()
            except AttributeError:
                return

        self.maskSlider.valueChanged.connect(g1)
        self.maskSlider.sliderReleased.connect(g2)

        # blending mode combo box
        compLabel = QLabel()
        compLabel.setText("Blend")

        self.compositionModeDict = OrderedDict([
            ('Normal', QPainter.CompositionMode_SourceOver),
            ('Plus', QPainter.CompositionMode_Plus),
            ('Multiply', QPainter.CompositionMode_Multiply),
            ('Screen', QPainter.CompositionMode_Screen),
            ('Overlay', QPainter.CompositionMode_Overlay),
            ('Darken', QPainter.CompositionMode_Darken),
            ('Lighten', QPainter.CompositionMode_Lighten),
            ('Color Dodge', QPainter.CompositionMode_ColorDodge),
            ('Color Burn', QPainter.CompositionMode_ColorBurn),
            ('Hard Light', QPainter.CompositionMode_HardLight),
            ('Soft Light', QPainter.CompositionMode_SoftLight),
            ('Difference', QPainter.CompositionMode_Difference),
            ('Exclusion', QPainter.CompositionMode_Exclusion)
        ])
        self.blendingModeCombo = QComboBox()
        for key in self.compositionModeDict:
            self.blendingModeCombo.addItem(key, self.compositionModeDict[key])

        # combo box item chosen event handler
        def g(ind):
            s = self.blendingModeCombo.currentText()
            try:
                layer = self.img.getActiveLayer()
                layer.compositionMode = self.compositionModeDict[str(s)]
                layer.applyToStack()
                self.img.onImageChanged()
            except AttributeError:
                return

        self.blendingModeCombo.currentIndexChanged.connect(g)
        # self.blendingModeCombo.activated.connect(g)  # TODO activated changed to currentIndexChanged 08/10/18 validate

        #layout
        l = QVBoxLayout()
        l.setAlignment(Qt.AlignTop)
        hl0 = QHBoxLayout()
        hl0.addWidget(titleLabel)
        hl0.addStretch(1)
        hl0.addWidget(self.previewOptionBox)
        l.addLayout(hl0)
        hl = QHBoxLayout()
        hl.addWidget(QLabel('Opacity'))
        hl.addWidget(self.opacityValue)
        hl.addWidget(self.opacitySlider)
        l.addLayout(hl)
        hl1 = QHBoxLayout()
        hl1.addWidget(QLabel('Mask Color'))
        hl1.addWidget(self.maskValue)
        hl1.addWidget(self.maskSlider)
        l.addLayout(hl1)
        l.setContentsMargins(0, 0, 10, 0)  # left, top, right, bottom
        hl2 = QHBoxLayout()
        hl2.addWidget(compLabel)
        hl2.addWidget(self.blendingModeCombo)
        l.addLayout(hl2)
        for layout in [hl, hl1, hl2]:
            layout.setContentsMargins(5, 0, 0, 0)
        # this layout must be added to the propertyWidget object loaded from blue.ui :
        # we postpone it after loading of the main form, in blue.py.
        self.propertyLayout = l

        # shortcut actions
        self.actionDup = QAction('Duplicate layer', None)
        self.actionDup.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_J))
        self.addAction(self.actionDup)

        def dup():
            row = self.selectedIndexes()[0].row()
            # Stack index
            index = len(self.img.layersStack) - row - 1
            layer = self.img.layersStack[index]
            if layer.isAdjustLayer():
                return
            # add new layer to stack and set it to active
            self.img.dupLayer(index=index)
            # update layer view
            self.setLayers(self.img)

        self.actionDup.triggered.connect(dup)
        self.setWhatsThis("""<b>Layer Stack</b>
To <b>toggle the layer visibility</b> click on the Eye icon.<br>
To <b>add a mask</b> use the context menu to enable it and paint pixels with the Mask/Unmask tools in the left pane.<br>
For <b>color mask<b/b>: <br>
    &nbsp; green pixels are masked,<br>
    &nbsp; red pixels are unmasked.<br>
Note that upper visible layers slow down mask edition.<br>
""")  # end of setWhatsThis

    """
    def setEnabled(self, value):  # TODO removed 30/11/18
        super(QLayerView, self).setEnabled(value)  
        if not self.isEnabled():
            self.setStatusTip('Close adjustment form %s to enable Layers' % self.currentWin.windowTitle())
        else:
            self.setStatusTip('')
    """

    def closeAdjustForms(self, delete=False):
        """
        Close all layer forms. If delete is True (default False),
        the forms and their dock containers are deleted.
        @param delete:
        @type delete: boolean
        """
        if self.img is None:
            return
        stack = self.img.layersStack
        for layer in stack:
            if hasattr(layer, "view"):
                if layer.view is not None:
                    dock = layer.view
                    if delete:
                        form = dock.widget()
                        # remove back link
                        form.layer = None
                        # QtGui1.window.removeDockWidget(dock)
                        form.setAttribute(Qt.WA_DeleteOnClose)
                        form.close()
                        dock.setAttribute(Qt.WA_DeleteOnClose)
                        dock.close()
                        layer.view = None
                    elif not TABBING:  # tabbed forms should not be closed
                        dock.close()
        if delete:
            self.currentWin = None
            gc.collect()

    def clear(self, delete=True):
        """
        Reset LayerView and clear the back
        links to image.
        """
        self.closeAdjustForms(delete=delete)
        self.img = None
        self.currentWin = None
        model = layerModel()
        model.setColumnCount(3)
        self.setModel(None)

    def setLayers(self, mImg, delete=False):
        """
        Displays the layer stack of mImg
        @param mImg: image
        @type mImg: mImage
        """
        # close open adjustment windows
        #self.closeAdjustForms()
        self.clear(delete=delete)
        mImg.layerView = self
        # back link to image
        self.img = weakProxy(mImg)
        model = layerModel()
        model.setColumnCount(3)
        l = len(mImg.layersStack)

        # dataChanged event handler : enables edition of layer name
        def f(index1, index2):
            # index1 and index2 should be equal
            # only layer name should be editable
            # dropEvent emit dataChanged when setting item values. f must
            # return immediately from these calls, as they are possibly made with unconsistent data :
            # dragged rows are already removed from layersStack
            # and not yet removed from model.
            if l != self.model().rowCount():
                return
            # only name is editable
            if index1.column() != 1:
                return
            row = index1.row()
            stackIndex = l - row - 1
            mImg.layersStack[stackIndex].name = index1.data()

        model.dataChanged.connect(f)
        for r, lay in enumerate(reversed(mImg.layersStack)):
            items = []
            # col 0 : visibility icon
            if lay.visible:
                item_visible = QStandardItem(
                    QIcon(":/images/resources/eye-icon.png"), "")
            else:
                item_visible = QStandardItem(
                    QIcon(":/images/resources/eye-icon-strike.png"), "")
            items.append(item_visible)
            # col 1 : image icon (for non-adjustment layeronly) and name
            if len(lay.name) <= 30:
                name = lay.name
            else:
                name = lay.name[:28] + '...'
            if hasattr(lay, 'inputImg'):
                item_name = QStandardItem(name)
            else:
                # icon with very small dim causes QPainter error
                # QPixmap.fromImage bug ?
                smallImg = lay.resize(50 * 50)
                w, h = smallImg.width(), smallImg.height()
                if w < h / 5 or h < w / 5:
                    item_name = QStandardItem(name)
                else:
                    item_name = QStandardItem(
                        QIcon(QPixmap.fromImage(smallImg)), name)
            # set tool tip to full name
            item_name.setToolTip(lay.name)
            items.append(item_name)
            item_mask = QStandardItem('m')
            items.append(item_mask)
            model.appendRow(items)
        self.setModel(model)
        self.horizontalHeader().hide()
        self.verticalHeader().hide()
        header = self.horizontalHeader()
        header.setSectionResizeMode(0, QHeaderView.ResizeToContents)
        header.setSectionResizeMode(1, QHeaderView.ResizeToContents)
        header.setSectionResizeMode(2, QHeaderView.ResizeToContents)
        # select active layer
        self.selectRow(len(mImg.layersStack) - 1 - mImg.activeLayerIndex)
        layerview = mImg.getActiveLayer().view  # TODO added 25/11/18
        if layerview is not None:
            layerview.show()
            if TABBING:
                layerview.raise_()
        self.updateForm()
        for item in self.img.layersStack:
            if hasattr(item, 'sourceIndex'):
                combo = item.getGraphicsForm().sourceCombo
                currentText = combo.currentText()
                combo.clear()
                for i, x in enumerate(self.img.layersStack):
                    item.view.widget().sourceCombo.addItem(x.name, i)
                combo.setCurrentIndex(combo.findText(currentText))

    def updateForm(self):
        activeLayer = self.img.getActiveLayer()
        if hasattr(activeLayer, 'view'):
            self.currentWin = activeLayer.view
        if self.currentWin is not None:
            self.currentWin.show()
            self.currentWin.activateWindow()

    def updateRow(self, row):
        minInd, maxInd = self.model().index(row, 0), self.model().index(row, 3)
        self.model().dataChanged.emit(minInd, maxInd)

    def dropEvent(self, event):
        """
        drop event handler : moving layer
        @param event:
        @type event: Qevent
        """
        if event.source() is not self:
            return
        # get selected rows and layers
        rows = set([mi.row() for mi in self.selectedIndexes()])
        rStack = self.img.layersStack[::-1]
        layers = [rStack[i] for i in rows]
        linked = any(l.group for l in layers)
        if linked and len(rows) > 1:
            return
        # get target row and layer
        targetRow = self.indexAt(event.pos()).row()
        targetLayer = rStack[targetRow]
        if linked:
            if layers[0].group is not targetLayer.group:
                return
        if bool(targetLayer.group) != linked:
            return
        # remove target from selection
        if targetRow in rows:
            rows.discard(targetRow)
        rows = sorted(rows)
        if not rows:
            return
        # if target is below last row insert at the last position
        if targetRow == -1:
            targetRow = self.model().rowCount()
        # mapping of src (row) indices to target indices
        rowMapping = dict()
        for idx, row in enumerate(rows):
            if row < targetRow:
                rowMapping[row] = targetRow + idx
            else:
                rowMapping[row + len(rows)] = targetRow + idx
        # update layerStack using rowMapping
        # insert None items
        for _ in range(len(rows)):
            rStack.insert(targetRow, None)
        # copy moved items to their final place
        for srcRow, tgtRow in sorted(
                rowMapping.items()):  # python 3 iteritems->items
            rStack[tgtRow] = rStack[srcRow]
        # remove moved items from their initial place
        for row in reversed(sorted(
                rowMapping.keys())):  # python 3 iterkeys -> keys
            rStack.pop(row)
        self.img.layersStack = rStack[::-1]
        # update model
        # insert empty rows
        for _ in range(len(rows)):
            result = self.model().insertRow(targetRow, QModelIndex())
        # copy moved rows to their final place
        colCount = self.model().columnCount()
        for srcRow, tgtRow in sorted(
                rowMapping.items()):  # python 3 iteritems->items
            for col in range(0, colCount):
                # CAUTION : setItem calls the data changed event handler (cf. setLayers above)
                self.model().setItem(tgtRow, col,
                                     self.model().takeItem(srcRow, col))
        # remove moved rows from their initial place and keep track of moved items
        movedDict = rowMapping.copy()
        for row in reversed(sorted(
                rowMapping.keys())):  # python 3 iterkeys -> keys
            self.model().removeRow(row)
            for s, t in rowMapping.items():
                if t > row:
                    movedDict[s] -= 1
        ######################################### sanity check
        for r in range(self.model().rowCount()):
            id = self.model().index(r, 1)
            if id.data() != rStack[r].name:
                raise ValueError('Drop Error')
        ########################################
        # reselect moved rows
        sel = sorted(movedDict.values())
        selectionModel = QtCore.QItemSelectionModel(self.model())
        self.setSelectionModel(selectionModel)
        index1 = self.model().index(sel[0], 1)
        index2 = self.model().index(sel[-1], 1)
        itemSelection = QtCore.QItemSelection(index1, index2)
        self.selectionModel().select(
            itemSelection, QtCore.QItemSelectionModel.Rows
            | QtCore.QItemSelectionModel.Select)
        # multiple selection : display no window
        if len(sel) > 1:
            self.currentWin.hide()
            self.currentWin = None
        elif len(sel) == 1:
            self.img.setActiveLayer(len(self.img.layersStack) - sel[0] - 1)
        # update stack
        self.img.layersStack[0].applyToStack()
        self.img.onImageChanged()

    def select(self, row, col):
        """
        select item in view
        @param row:
        @type row:
        @param col:
        @type col:
        @return:
        @rtype:
        """
        model = self.model()
        self.viewClicked(model.index(row, col))

    def viewClicked(self, clickedIndex):
        """
        Mouse clicked event handler.
        @param clickedIndex: 
        @type clickedIndex: QModelIndex
        """
        row = clickedIndex.row()
        rows = set([mi.row() for mi in self.selectedIndexes()])
        #multiple selection : go to top of selection
        m = min(rows)
        if row != m:
            clickedIndex = self.model().index(m, clickedIndex.column())
        layer = self.img.layersStack[-1 - row]
        self.actionDup.setEnabled(not layer.isAdjustLayer())
        # toggle layer visibility
        if clickedIndex.column() == 0:
            # background layer is always visible
            if row == len(self.img.layersStack) - 1:
                return
            #layer.visible = not(layer.visible)
            layer.setVisible(not (layer.visible))
            if self.currentWin is not None:
                self.currentWin.setVisible(layer.visible)
                if not layer.visible:
                    self.currentWin = None
            if layer.tool is not None:
                layer.tool.setVisible(layer.visible)
            # update stack
            if layer.visible:
                layer.applyToStack()
            else:
                i = layer.getUpperVisibleStackIndex()
                if i >= 0:
                    layer.parentImage.layersStack[i].applyToStack()
                else:
                    # top layer : update only the presentation layer
                    layer.parentImage.prLayer.execute(l=None, pool=None)
            self.img.onImageChanged()
        # update displayed window and active layer
        activeStackIndex = len(self.img.layersStack) - 1 - row
        activeLayer = self.img.setActiveLayer(activeStackIndex)
        if self.currentWin is not None:
            if not self.currentWin.isFloating():
                #self.currentWin.hide()
                self.currentWin = None
        if hasattr(self.img.layersStack[activeStackIndex], "view"):
            self.currentWin = self.img.layersStack[activeStackIndex].view
        if self.currentWin is not None and activeLayer.visible:
            self.currentWin.show()
            self.currentWin.raise_()
            # make self.currentWin the active window
            self.currentWin.activateWindow()
        # update opacity and composition mode for current layer
        opacity = int(layer.opacity * 100)
        self.opacityValue.setText(str('%d ' % opacity))
        self.opacitySlider.setSliderPosition(opacity)
        compositionMode = layer.compositionMode
        ind = self.blendingModeCombo.findData(compositionMode)
        self.blendingModeCombo.setCurrentIndex(ind)
        # draw the right rectangle
        window.label.repaint()

    def initContextMenu(self):
        """
        return the context menu
        @return:
        @rtype: QMenu
        """
        menu = QMenu()
        menu.actionReset = QAction('Reset To Default', None)
        menu.actionLoadImage = QAction('Load New Image', None)
        menu.actionGroupSelection = QAction('Group Selection', None)
        menu.actionAdd2Group = QAction('Add to Group', None)
        # Active layer is not in a group or right clicked layer is in a group

        menu.actionUnGroup = QAction('Ungroup', None)

        # multiple selections
        menu.actionMerge = QAction('Merge Lower', None)
        # merge only adjustment layer with image layer

        # don't dup adjustment layers
        menu.actionUnselect = QAction('Unselect All', None)

        menu.actionRepositionLayer = QAction('Reposition Layer(s)', None)
        menu.actionColorMaskEnable = QAction('Color Mask', None)
        menu.actionOpacityMaskEnable = QAction('Opacity Mask', None)
        menu.actionClippingMaskEnable = QAction('Clipping Mask', None)
        menu.actionMaskDisable = QAction('Disable Mask', None)
        menu.actionMaskInvert = QAction('Invert Mask', None)
        menu.actionMaskReset = QAction('Clear Mask', None)
        menu.actionMaskCopy = QAction('Copy Mask to Clipboard', None)
        menu.actionImageCopy = QAction('Copy Image to Clipboard', None)
        menu.actionMaskPaste = QAction('Paste Mask', None)
        menu.actionImagePaste = QAction('Paste Image', None)
        menu.actionMaskDilate = QAction('Dilate Mask', None)
        menu.actionMaskErode = QAction('Erode Mask', None)
        menu.actionColorMaskEnable.setCheckable(True)
        menu.actionOpacityMaskEnable.setCheckable(True)
        menu.actionClippingMaskEnable.setCheckable(True)
        menu.actionMaskDisable.setCheckable(True)
        ####################
        # Build menu
        ###################
        # group/ungroup
        menu.addAction(menu.actionAdd2Group)
        menu.addAction(menu.actionGroupSelection)
        menu.addAction(menu.actionUnGroup)
        menu.addSeparator()
        menu.addAction(menu.actionUnselect)
        menu.addSeparator()
        menu.addAction(menu.actionRepositionLayer)
        menu.addSeparator()
        # layer
        menu.addAction(menu.actionImageCopy)
        menu.addAction(menu.actionImagePaste)
        menu.addSeparator()
        # mask
        menu.subMenuEnable = menu.addMenu('Mask...')
        menu.subMenuEnable.addAction(menu.actionColorMaskEnable)
        menu.subMenuEnable.addAction(menu.actionOpacityMaskEnable)
        menu.subMenuEnable.addAction(menu.actionClippingMaskEnable)
        menu.subMenuEnable.addAction(menu.actionMaskDisable)
        menu.addAction(menu.actionMaskInvert)
        menu.addAction(menu.actionMaskReset)
        menu.addAction(menu.actionMaskCopy)
        menu.addAction(menu.actionMaskPaste)
        menu.addAction(menu.actionMaskDilate)
        menu.addAction(menu.actionMaskErode)
        menu.addSeparator()
        # miscellaneous
        menu.addAction(menu.actionLoadImage)
        # to link actionDup with a shortcut,
        # it must be set in __init__
        menu.addAction(self.actionDup)
        menu.addAction(menu.actionMerge)
        menu.addAction(menu.actionReset)
        return menu

    def contextMenuEvent(self, event):
        """
        context menu handler
        @param event
        @type event: QContextMenuEvent
        """
        selection = self.selectedIndexes()
        if not selection:
            return
        # get fresh context menu
        self.cMenu = self.initContextMenu()
        # get current selection
        rows = set([mi.row() for mi in selection])
        rStack = self.img.layersStack[::-1]
        layers = [rStack[r] for r in rows]
        group = []  # TODO added 5/11/18 validate
        if layers:
            group = layers[0].group
        for l in layers:
            # different groups
            if l.group and group:
                if l.group is not group:
                    dlgWarn("Select a single group")
                    return
        # get current position
        index = self.indexAt(event.pos())
        layerStackIndex = len(self.img.layersStack) - 1 - index.row()
        layer = self.img.layersStack[layerStackIndex]
        lowerVisible = self.img.layersStack[layer.getLowerVisibleStackIndex()]
        lower = self.img.layersStack[layerStackIndex -
                                     1]  # case index == 0 doesn't matter
        # toggle actions
        self.cMenu.actionGroupSelection.setEnabled(not (len(rows) < 2 or any(
            l.group for l in layers)))
        self.cMenu.actionAdd2Group.setEnabled(not (group or layer.group))
        self.cMenu.actionUnGroup.setEnabled(bool(layer.group))
        self.cMenu.actionMerge.setEnabled(not (
            hasattr(layer, 'inputImg') or hasattr(lowerVisible, 'inputImg')))
        self.actionDup.setEnabled(not layer.isAdjustLayer())
        self.cMenu.actionColorMaskEnable.setChecked(layer.maskIsSelected
                                                    and layer.maskIsEnabled)
        self.cMenu.actionOpacityMaskEnable.setChecked(
            (not layer.maskIsSelected) and layer.maskIsEnabled)
        self.cMenu.actionClippingMaskEnable.setChecked(
            layer.isClipping and (layer.maskIsSelected or layer.maskIsEnabled))
        self.cMenu.actionMaskDisable.setChecked(not (
            layer.isClipping or layer.maskIsSelected or layer.maskIsEnabled))
        self.cMenu.actionUnselect.setEnabled(layer.rect is None)
        self.cMenu.subMenuEnable.setEnabled(len(rows) == 1)
        self.cMenu.actionMaskPaste.setEnabled(
            not QApplication.clipboard().image().isNull())
        self.cMenu.actionImagePaste.setEnabled(
            not QApplication.clipboard().image().isNull())

        # Event handlers
        def f():
            self.opacitySlider.show()

        def unselectAll():
            layer.rect = None

        def RepositionLayer():
            layer.xOffset, layer.yOffset = 0, 0
            layer.Zoom_coeff = 1.0
            layer.AltZoom_coeff = 1.0
            layer.xAltOffset, layer.yAltOffset = 0, 0
            layer.updatePixmap()
            self.img.onImageChanged()

        def loadImage():
            return  # TODO 26/06/18 action to remove from menu? replaced by new image layer
            filename = openDlg(window)
            img = QImage(filename)
            layer.thumb = None
            layer.setImage(img)

        def add2Group():
            layer.group = group
            layer.mask = group[0].mask
            layer.maskIsEnabled = True
            layer.maskIsSelected = True

        def groupSelection():
            layers = [rStack[i] for i in sorted(rows)]
            if any(l.group for l in layers):
                dlgWarn("Some layers are already grouped. Ungroup first")
                return
            mask = layers[0].mask
            for l in layers:
                l.group = layers
                l.mask = mask
                l.maskIsEnabled = True
                l.maskIsSelected = False

        def unGroup():
            group = layer.group.copy()
            for l in group:
                l.unlinkMask()

        def merge():
            layer.merge_with_layer_immediately_below()

        def testUpperVisibility():
            pos = self.img.getStackIndex(layer)
            upperVisible = False
            for i in range(len(self.img.layersStack) - pos - 1):
                if self.img.layersStack[pos + 1 + i].visible:
                    upperVisible = True
                    break
            if upperVisible:
                dlgWarn("Upper visible layers slow down mask edition")
                return True
            return False

        def colorMaskEnable():
            testUpperVisibility()
            layer.maskIsEnabled = True
            layer.maskIsSelected = True
            layer.applyToStack()
            self.img.onImageChanged()

        def opacityMaskEnable():
            testUpperVisibility()
            layer.maskIsEnabled = True
            layer.maskIsSelected = False
            layer.applyToStack()
            self.img.onImageChanged()

        def clippingMaskEnable():
            layer.maskIsEnabled = True
            layer.maskIsSelected = False
            layer.isClipping = True
            layer.applyToStack()
            self.img.onImageChanged()

        def maskDisable():
            layer.maskIsEnabled = False
            layer.maskIsSelected = False
            layer.isClipping = False  # TODO added 28/11/18
            layer.applyToStack()
            self.img.onImageChanged()

        def maskInvert():
            layer.invertMask()
            # update mask stack
            layer.applyToStack()
            #for l in self.img.layersStack:
            #l.updatePixmap(maskOnly=True)
            self.img.onImageChanged()

        def maskReset():
            layer.resetMask()
            # update mask stack
            for l in self.img.layersStack:
                l.updatePixmap(maskOnly=True)
            self.img.onImageChanged()

        def maskCopy():
            QApplication.clipboard().setImage(layer.mask)

        def imageCopy():
            QApplication.clipboard().setImage(layer.getCurrentMaskedImage())

        def maskPaste():
            """
            Pastes clipboard to mask and updates the stack. The clipboard image
            is scaled if its size does not match the size of the mask
            """
            cb = QApplication.clipboard()
            if not cb.image().isNull():
                img = cb.image()
                if img.size() == layer.mask.size():
                    layer.mask = img
                else:
                    layer.mask = img.scaled(layer.mask.size())
            layer.applyToStack()
            self.img.onImageChanged()

        def imagePaste():
            """
            Pastes clipboard to mask and updates the stack. The clipboard image
            is scaled if its size does not match the size of the mask
            """
            cb = QApplication.clipboard()
            if not cb.image().isNull():
                srcImg = cb.image()
                if srcImg.size() == layer.size():
                    layer.setImage(srcImg)
                else:
                    layer.setImage(srcImg.scaled(layer.size()))
            layer.applyToStack()
            self.img.onImageChanged()

        def maskDilate():
            kernel = np.ones((5, 5), np.uint8)
            buf = QImageBuffer(layer.mask)
            # CAUTION erode decreases values (min filter), so it extends the masked part of the image
            buf[:, :, 2] = cv2.erode(buf[:, :, 2], kernel, iterations=1)
            for l in self.img.layersStack:
                l.updatePixmap(maskOnly=True)
            self.img.onImageChanged()

        def maskErode():
            kernel = np.ones((5, 5), np.uint8)
            buf = QImageBuffer(layer.mask)
            # CAUTION dilate increases values (max filter), so it reduces the masked part of the image
            buf[:, :, 2] = cv2.dilate(buf[:, :, 2], kernel, iterations=1)
            for l in self.img.layersStack:
                l.updatePixmap(maskOnly=True)
            self.img.onImageChanged()

        def layerReset():
            view = layer.getGraphicsForm()
            if hasattr(view, 'reset'):
                view.reset()

        self.cMenu.actionRepositionLayer.triggered.connect(RepositionLayer)
        self.cMenu.actionUnselect.triggered.connect(unselectAll)
        self.cMenu.actionLoadImage.triggered.connect(loadImage)
        self.cMenu.actionAdd2Group.triggered.connect(add2Group)
        self.cMenu.actionGroupSelection.triggered.connect(groupSelection)
        self.cMenu.actionUnGroup.triggered.connect(unGroup)
        self.cMenu.actionMerge.triggered.connect(merge)
        self.cMenu.actionColorMaskEnable.triggered.connect(colorMaskEnable)
        self.cMenu.actionOpacityMaskEnable.triggered.connect(opacityMaskEnable)
        self.cMenu.actionClippingMaskEnable.triggered.connect(
            clippingMaskEnable)
        self.cMenu.actionMaskDisable.triggered.connect(maskDisable)
        self.cMenu.actionMaskInvert.triggered.connect(maskInvert)
        self.cMenu.actionMaskReset.triggered.connect(maskReset)
        self.cMenu.actionMaskCopy.triggered.connect(maskCopy)
        self.cMenu.actionMaskPaste.triggered.connect(maskPaste)
        self.cMenu.actionImageCopy.triggered.connect(imageCopy)
        self.cMenu.actionImagePaste.triggered.connect(imagePaste)
        self.cMenu.actionMaskDilate.triggered.connect(maskDilate)
        self.cMenu.actionMaskErode.triggered.connect(maskErode)
        self.cMenu.actionReset.triggered.connect(layerReset)
        self.cMenu.exec_(event.globalPos())
        # update table
        for row in rows:
            self.updateRow(row)
Ejemplo n.º 10
0
    def _init_load_options_tab(self, tab):
        arch_layout = QHBoxLayout()
        arch_caption = QLabel(self)
        arch_caption.setText('Architecture:')
        arch_caption.setSizePolicy(
            QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed))
        arch_layout.addWidget(arch_caption)
        arch_combo = QComboBox(self)
        for arch in archinfo.all_arches:
            addendum = ' (P-code Engine)' if hasattr(arch,
                                                     'pcode_arch') else ''
            arch_combo.addItem(
                f'{arch.bits}b {arch.name} ({arch.memory_endness[-2:]}){addendum}',
                str(arch))
        index = arch_combo.findData(str(self.arch))
        arch_combo.setCurrentIndex(index)
        arch_layout.addWidget(arch_combo)
        self.option_widgets['arch'] = arch_combo

        if self.is_blob:
            blob_layout = QGridLayout()

            # load address
            base_addr_caption = QLabel(self)
            base_addr_caption.setText('Base Address:')
            blob_layout.addWidget(base_addr_caption, 1, 0)
            base_addr = QLineEdit(self)
            base_addr.setText('0')
            blob_layout.addWidget(base_addr, 1, 1)
            self.option_widgets['base_addr'] = base_addr

            # entry address
            entry_addr_caption = QLabel(self)
            entry_addr_caption.setText('Entry Address:')
            blob_layout.addWidget(entry_addr_caption, 2, 0)
            entry_addr = QLineEdit(self)
            entry_addr.setText('0')
            blob_layout.addWidget(entry_addr, 2, 1)
            self.option_widgets['entry_addr'] = entry_addr

        # load debug symbols
        load_debug_info = QCheckBox()
        load_debug_info.setText("Load debug information if available")
        load_debug_info.setChecked(True)
        self.option_widgets['load_debug_info'] = load_debug_info

        # auto load libs

        auto_load_libs = QCheckBox()
        auto_load_libs.setText(
            "Automatically load all libraries (slow, not recommended)")
        auto_load_libs.setChecked(False)
        self.option_widgets['auto_load_libs'] = auto_load_libs

        # dependencies list

        dep_group = QGroupBox("Dependencies")
        dep_list = QListWidget()
        self.option_widgets['dep_list'] = dep_list

        sublayout = QVBoxLayout()
        sublayout.addWidget(dep_list)
        dep_group.setLayout(sublayout)

        layout = QVBoxLayout()
        if self.is_blob:
            layout.addLayout(blob_layout)
        layout.addLayout(arch_layout)
        layout.addWidget(load_debug_info)
        layout.addWidget(auto_load_libs)
        layout.addWidget(dep_group)
        layout.addStretch(0)

        frame = QFrame(self)
        frame.setLayout(layout)
        tab.addTab(frame, "Loading Options")
Ejemplo n.º 11
0
class TallyDock(PlotterDock):

    def __init__(self, model, font_metric, parent=None):
        super().__init__(model, font_metric, parent)

        self.setAllowedAreas(QtCore.Qt.RightDockWidgetArea)

        # Dock maps for tally information
        self.tally_map = {}
        self.filter_map = {}
        self.score_map = {}
        self.nuclide_map = {}

        # Tally selector
        self.tallySelectorLayout = QFormLayout()
        self.tallySelector = QComboBox(self)
        self.tallySelector.currentTextChanged[str].connect(
            self.main_window.editSelectedTally)
        self.tallySelectorLayout.addRow(self.tallySelector)
        self.tallySelectorLayout.setLabelAlignment(QtCore.Qt.AlignLeft)
        self.tallySelectorLayout.setFieldGrowthPolicy(
            QFormLayout.AllNonFixedFieldsGrow)

        # Add selector to its own box
        self.tallyGroupBox = QGroupBox('Selected Tally')
        self.tallyGroupBox.setLayout(self.tallySelectorLayout)

        # Create submit button
        self.applyButton = QPushButton("Apply Changes")
        self.applyButton.setMinimumHeight(self.font_metric.height() * 1.6)
        self.applyButton.clicked.connect(self.main_window.applyChanges)

        # Color options section
        self.tallyColorForm = ColorForm(self.model, self.main_window, 'tally')
        self.scoresGroupBox = Expander(title="Scores:")
        self.scoresListWidget = QListWidget()
        self.nuclidesListWidget = QListWidget()

        # Main layout
        self.dockLayout = QVBoxLayout()
        self.dockLayout.addWidget(QLabel("Tallies"))
        self.dockLayout.addWidget(HorizontalLine())
        self.dockLayout.addWidget(self.tallyGroupBox)
        self.dockLayout.addStretch()
        self.dockLayout.addWidget(HorizontalLine())
        self.dockLayout.addWidget(self.tallyColorForm)
        self.dockLayout.addWidget(HorizontalLine())
        self.dockLayout.addWidget(self.applyButton)

        # Create widget for dock and apply main layout
        self.scroll = QScrollArea()
        self.scroll.setWidgetResizable(True)
        self.widget = QWidget()
        self.widget.setLayout(self.dockLayout)
        self.scroll.setWidget(self.widget)
        self.setWidget(self.scroll)

    def _createFilterTree(self, spatial_filters):
        av = self.model.activeView
        tally = self.model.statepoint.tallies[av.selectedTally]
        filters = tally.filters

        # create a tree for the filters
        self.treeLayout = QVBoxLayout()
        self.filterTree = QTreeWidget()
        self.treeLayout.addWidget(self.filterTree)
        self.treeExpander = Expander("Filters:", layout=self.treeLayout)
        self.treeExpander.expand()  # start with filters expanded

        header = QTreeWidgetItem(["Filters"])
        self.filterTree.setHeaderItem(header)
        self.filterTree.setItemHidden(header, True)
        self.filterTree.setColumnCount(1)
        self.filterTree.itemChanged.connect(self.updateFilters)

        self.filter_map = {}
        self.bin_map = {}

        for tally_filter in filters:
            filter_label = str(type(tally_filter)).split(".")[-1][:-2]
            filter_item = QTreeWidgetItem(self.filterTree, (filter_label,))
            self.filter_map[tally_filter] = filter_item

            # make checkable
            if not spatial_filters:
                filter_item.setFlags(QtCore.Qt.ItemIsUserCheckable)
                filter_item.setToolTip(0, "Only tallies with spatial filters are viewable.")
            else:
                filter_item.setFlags(filter_item.flags() | QtCore.Qt.ItemIsTristate | QtCore.Qt.ItemIsUserCheckable)
            filter_item.setCheckState(0, QtCore.Qt.Unchecked)

            # all mesh bins are selected by default and not shown in the dock
            if isinstance(tally_filter, openmc.MeshFilter):
                filter_item.setCheckState(0, QtCore.Qt.Checked)
                filter_item.setFlags(QtCore.Qt.ItemIsUserCheckable)
                filter_item.setToolTip(0, "All Mesh bins are selected automatically")
                continue

            def _bin_sort_val(bin):
                if isinstance(bin, Iterable) and all([isinstance(val, float) for val in bin]):
                    return np.sum(bin)
                else:
                    return bin

            for bin in sorted(tally_filter.bins, key=_bin_sort_val):
                item = QTreeWidgetItem(filter_item, [str(bin),])
                if not spatial_filters:
                    item.setFlags(QtCore.Qt.ItemIsUserCheckable)
                    item.setToolTip(0, "Only tallies with spatial filters are viewable.")
                else:
                    item.setFlags(item.flags() | QtCore.Qt.ItemIsUserCheckable)
                item.setCheckState(0, QtCore.Qt.Unchecked)

                bin = bin if not isinstance(bin, Iterable) else tuple(bin)
                self.bin_map[tally_filter, bin] = item

            # start with all filters selected if spatial filters are present
            if spatial_filters:
                filter_item.setCheckState(0, QtCore.Qt.Checked)

    def selectFromModel(self):
        cv = self.model.currentView
        self.selectedTally(cv.selectedTally)

    def selectTally(self, tally_label=None):
        # using active view to populate tally options live
        av = self.model.activeView

        # reset form layout
        for i in reversed(range(self.tallySelectorLayout.count())):
            self.tallySelectorLayout.itemAt(i).widget().setParent(None)

        # always re-add the tally selector to the layout
        self.tallySelectorLayout.addRow(self.tallySelector)
        self.tallySelectorLayout.addRow(HorizontalLine())

        if tally_label is None or tally_label == "None" or tally_label == "":
            av.selectedTally = None
            self.score_map = None
            self.nuclide_map = None
            self.filter_map = None
            av.tallyValue = "Mean"
        else:
            # get the tally
            tally = self.model.statepoint.tallies[av.selectedTally]

            # populate filters
            filter_types = {type(f) for f in tally.filters}
            spatial_filters = bool(filter_types.intersection(_SPATIAL_FILTERS))

            if not spatial_filters:
                self.filter_description = QLabel("(No Spatial Filters)")
                self.tallySelectorLayout.addRow(self.filter_description)

            self._createFilterTree(spatial_filters)

            self.tallySelectorLayout.addRow(self.treeExpander)
            self.tallySelectorLayout.addRow(HorizontalLine())

            # value selection
            self.tallySelectorLayout.addRow(QLabel("Value:"))
            self.valueBox = QComboBox(self)
            self.values = tuple(_TALLY_VALUES.keys())
            for value in self.values:
                self.valueBox.addItem(value)
            self.tallySelectorLayout.addRow(self.valueBox)
            self.valueBox.currentTextChanged[str].connect(
                self.main_window.editTallyValue)
            self.updateTallyValue()

            if not spatial_filters:
                self.valueBox.setEnabled(False)
                self.valueBox.setToolTip("Only tallies with spatial filters are viewable.")

            # scores
            self.score_map = {}
            self.scoresListWidget.itemClicked.connect(
                self.main_window.updateScores)
            self.score_map.clear()
            self.scoresListWidget.clear()

            sorted_scores = sorted(tally.scores)
            # always put total first if present
            if 'total' in sorted_scores:
                idx = sorted_scores.index('total')
                sorted_scores.insert(0, sorted_scores.pop(idx))

            for score in sorted_scores:
                ql = QListWidgetItem()
                ql.setText(score.capitalize())
                ql.setCheckState(QtCore.Qt.Unchecked)
                if not spatial_filters:
                    ql.setFlags(QtCore.Qt.ItemIsUserCheckable)
                else:
                    ql.setFlags(ql.flags() | QtCore.Qt.ItemIsUserCheckable)
                    ql.setFlags(ql.flags() & ~QtCore.Qt.ItemIsSelectable)
                self.score_map[score] = ql
                self.scoresListWidget.addItem(ql)

            # select the first score item by default
            for item in self.score_map.values():
                item.setCheckState(QtCore.Qt.Checked)
                break
            self.updateScores()

            self.scoresGroupBoxLayout = QVBoxLayout()
            self.scoresGroupBoxLayout.addWidget(self.scoresListWidget)
            self.scoresGroupBox = Expander("Scores:", layout=self.scoresGroupBoxLayout)
            self.tallySelectorLayout.addRow(self.scoresGroupBox)

            # nuclides
            self.nuclide_map = {}
            self.nuclidesListWidget.itemClicked.connect(self.main_window.updateNuclides)
            self.nuclide_map.clear()
            self.nuclidesListWidget.clear()

            sorted_nuclides = sorted(tally.nuclides)
            # always put total at the top
            if 'total' in sorted_nuclides:
                idx = sorted_nuclides.index('total')
                sorted_nuclides.insert(0, sorted_nuclides.pop(idx))

            for nuclide in sorted_nuclides:
                ql = QListWidgetItem()
                ql.setText(nuclide.capitalize())
                ql.setCheckState(QtCore.Qt.Unchecked)
                if not spatial_filters:
                    ql.setFlags(QtCore.Qt.ItemIsUserCheckable)
                else:
                    ql.setFlags(ql.flags() | QtCore.Qt.ItemIsUserCheckable)
                    ql.setFlags(ql.flags() & ~QtCore.Qt.ItemIsSelectable)
                self.nuclide_map[nuclide] = ql
                self.nuclidesListWidget.addItem(ql)

            # select the first nuclide item by default
            for item in self.nuclide_map.values():
                item.setCheckState(QtCore.Qt.Checked)
                break
            self.updateNuclides()

            self.nuclidesGroupBoxLayout = QVBoxLayout()
            self.nuclidesGroupBoxLayout.addWidget(self.nuclidesListWidget)
            self.nuclidesGroupBox = Expander("Nuclides:", layout=self.nuclidesGroupBoxLayout)
            self.tallySelectorLayout.addRow(self.nuclidesGroupBox)

    def updateMinMax(self):
        self.tallyColorForm.updateMinMax()

    def updateTallyValue(self):
        cv = self.model.currentView
        idx = self.valueBox.findText(cv.tallyValue)
        self.valueBox.setCurrentIndex(idx)

    def updateSelectedTally(self):
        cv = self.model.currentView
        idx = 0
        if cv.selectedTally:
            idx = self.tallySelector.findData(cv.selectedTally)
        self.tallySelector.setCurrentIndex(idx)

    def updateFilters(self):
        applied_filters = defaultdict(tuple)
        for f, f_item in self.filter_map.items():
            if type(f) == openmc.MeshFilter:
                continue

            filter_checked = f_item.checkState(0)
            if filter_checked != QtCore.Qt.Unchecked:
                selected_bins = []
                for idx, b in enumerate(f.bins):
                    b = b if not isinstance(b, Iterable) else tuple(b)
                    bin_checked = self.bin_map[(f, b)].checkState(0)
                    if bin_checked == QtCore.Qt.Checked:
                        selected_bins.append(idx)
                applied_filters[f] = tuple(selected_bins)

            self.model.appliedFilters = applied_filters

    def updateScores(self):
        applied_scores = []
        for score, score_box in self.score_map.items():
            if score_box.checkState() == QtCore.Qt.CheckState.Checked:
                applied_scores.append(score)
        self.model.appliedScores = tuple(applied_scores)

        if not applied_scores:
            # if no scores are selected, enable all scores again
            for score, score_box in self.score_map.items():
                sunits = _SCORE_UNITS.get(score, _REACTION_UNITS)
                empty_item = QListWidgetItem()
                score_box.setFlags(empty_item.flags() | QtCore.Qt.ItemIsUserCheckable)
                score_box.setFlags(empty_item.flags() & ~QtCore.Qt.ItemIsSelectable)
        elif 'total' in applied_scores:
            self.model.appliedScores = ('total',)
            # if total is selected, disable all other scores
            for score, score_box in self.score_map.items():
                if score != 'total':
                    score_box.setFlags(QtCore.Qt.ItemIsUserCheckable)
                    score_box.setToolTip("De-select 'total' to enable other scores")
        else:
            # get units of applied scores
            selected_units = _SCORE_UNITS.get(applied_scores[0], _REACTION_UNITS)
            # disable scores with incompatible units
            for score, score_box in self.score_map.items():
                sunits = _SCORE_UNITS.get(score, _REACTION_UNITS)
                if sunits != selected_units:
                    score_box.setFlags(QtCore.Qt.ItemIsUserCheckable)
                    score_box.setToolTip("Score is incompatible with currently selected scores")
                else:
                    score_box.setFlags(score_box.flags() | QtCore.Qt.ItemIsUserCheckable)
                    score_box.setFlags(score_box.flags() & ~QtCore.Qt.ItemIsSelectable)

    def updateNuclides(self):
        applied_nuclides = []
        for nuclide, nuclide_box in self.nuclide_map.items():
            if nuclide_box.checkState() == QtCore.Qt.CheckState.Checked:
                applied_nuclides.append(nuclide)
        self.model.appliedNuclides = tuple(applied_nuclides)

        if 'total' in applied_nuclides:
            self.model.appliedNuclides = ['total',]
            for nuclide, nuclide_box in self.nuclide_map.items():
                if nuclide != 'total':
                    nuclide_box.setFlags(QtCore.Qt.ItemIsUserCheckable)
                    nuclide_box.setToolTip("De-select 'total' to enable other nuclides")
        elif not applied_nuclides:
            # if no nuclides are selected, enable all nuclides again
            for nuclide, nuclide_box in self.nuclide_map.items():
                empty_item = QListWidgetItem()
                nuclide_box.setFlags(empty_item.flags() | QtCore.Qt.ItemIsUserCheckable)
                nuclide_box.setFlags(empty_item.flags() & ~QtCore.Qt.ItemIsSelectable)

    def update(self):

        # update the color form
        self.tallyColorForm.update()

        if self.model.statepoint:
            self.tallySelector.clear()
            self.tallySelector.setEnabled(True)
            self.tallySelector.addItem("None")
            for idx, tally in enumerate(self.model.statepoint.tallies.values()):
                if tally.name == "":
                    self.tallySelector.addItem('Tally {}'.format(tally.id), userData=tally.id)
                else:
                    self.tallySelector.addItem('Tally {} "{}"'.format(tally.id, tally.name), userData=tally.id)
                self.tally_map[idx] = tally
            self.updateSelectedTally()
            self.updateMinMax()
        else:
            self.tallySelector.clear()
            self.tallySelector.setDisabled(True)
Ejemplo n.º 12
0
class WidgetConfig(QGroupBox):
    def __init__(self):
        super(WidgetConfig, self).__init__()

        HEIGHT = 30

        grid = QGridLayout()

        # 使用默认摄像头复选框
        self.check_camera = QCheckBox('Use default camera')
        self.check_camera.setChecked(True)
        self.check_camera.stateChanged.connect(self.slot_check_camera)

        grid.addWidget(self.check_camera, 0, 0, 1, 3)  # 一行三列

        # 选择视频文件
        label_video = QLabel('Source')
        self.line_video = QLineEdit()
        self.line_video.setFixedHeight(HEIGHT)
        self.line_video.setEnabled(False)
        self.line_video.setText(GLOBAL.config.get('video', ''))
        self.line_video.editingFinished.connect(
            lambda: GLOBAL.record_config({'video': self.line_video.text()}))

        self.btn_video = QPushButton('...')
        self.btn_video.setFixedWidth(40)
        self.btn_video.setFixedHeight(HEIGHT)
        self.btn_video.setEnabled(False)
        self.btn_video.clicked.connect(self.choose_video_file)

        self.slot_check_camera()

        grid.addWidget(label_video, 1, 0)
        grid.addWidget(self.line_video, 1, 1)
        grid.addWidget(self.btn_video, 1, 2)

        # 选择权重文件
        label_weights = QLabel('Weights')
        self.line_weights = QLineEdit()
        self.line_weights.setFixedHeight(HEIGHT)
        self.line_weights.setText(GLOBAL.config.get('weights', ''))
        self.line_weights.editingFinished.connect(lambda: GLOBAL.record_config(
            {'weights': self.line_weights.text()}
        ))

        self.btn_weights = QPushButton('...')
        self.btn_weights.setFixedWidth(40)
        self.btn_weights.setFixedHeight(HEIGHT)
        self.btn_weights.clicked.connect(self.choose_weights_file)

        grid.addWidget(label_weights, 2, 0)
        grid.addWidget(self.line_weights, 2, 1)
        grid.addWidget(self.btn_weights, 2, 2)

        # 是否使用GPU
        label_device = QLabel('CUDA device')
        self.line_device = QLineEdit('cpu')
        self.line_device.setText(GLOBAL.config.get('device', 'cpu'))
        self.line_device.setPlaceholderText('cpu or 0 or 0,1,2,3')
        self.line_device.setFixedHeight(HEIGHT)
        self.line_device.editingFinished.connect(lambda: GLOBAL.record_config(
            {'device': self.line_device.text()}
        ))

        grid.addWidget(label_device, 3, 0)
        grid.addWidget(self.line_device, 3, 1, 1, 2)

        # 设置图像大小
        label_size = QLabel('Img Size')
        self.combo_size = QComboBox()
        self.combo_size.setFixedHeight(HEIGHT)
        self.combo_size.setStyleSheet(
            'QAbstractItemView::item {height: 40px;}')
        self.combo_size.setView(QListView())
        self.combo_size.addItem('320', 320)
        self.combo_size.addItem('416', 416)
        self.combo_size.addItem('480', 480)
        self.combo_size.addItem('544', 544)
        self.combo_size.addItem('640', 640)
        self.combo_size.setCurrentIndex(
            self.combo_size.findData(GLOBAL.config.get('img_size', 480)))
        self.combo_size.currentIndexChanged.connect(lambda: GLOBAL.record_config(
            {'img_size': self.combo_size.currentData()}))

        grid.addWidget(label_size, 4, 0)
        grid.addWidget(self.combo_size, 4, 1, 1, 2)

        # 设置置信度阈值
        label_conf = QLabel('Confidence')
        self.spin_conf = QDoubleSpinBox()
        self.spin_conf.setFixedHeight(HEIGHT)
        self.spin_conf.setDecimals(1)
        self.spin_conf.setRange(0.1, 0.9)
        self.spin_conf.setSingleStep(0.1)
        self.spin_conf.setValue(GLOBAL.config.get('conf_thresh', 0.5))
        self.spin_conf.valueChanged.connect(lambda: GLOBAL.record_config(
            {'conf_thresh': round(self.spin_conf.value(), 1)}
        ))

        grid.addWidget(label_conf, 5, 0)
        grid.addWidget(self.spin_conf, 5, 1, 1, 2)

        # 设置IOU阈值
        label_iou = QLabel('IOU')
        self.spin_iou = QDoubleSpinBox()
        self.spin_iou.setFixedHeight(HEIGHT)
        self.spin_iou.setDecimals(1)
        self.spin_iou.setRange(0.1, 0.9)
        self.spin_iou.setSingleStep(0.1)
        self.spin_iou.setValue(GLOBAL.config.get('iou_thresh', 0.5))
        self.spin_iou.valueChanged.connect(lambda: GLOBAL.record_config(
            {'iou_thresh': round(self.spin_iou.value(), 1)}
        ))

        grid.addWidget(label_iou, 6, 0)
        grid.addWidget(self.spin_iou, 6, 1, 1, 2)

        # class-agnostic NMS
        self.check_agnostic = QCheckBox('Agnostic')
        self.check_agnostic.setChecked(GLOBAL.config.get('agnostic', True))
        self.check_agnostic.stateChanged.connect(lambda: GLOBAL.record_config(
            {'agnostic': self.check_agnostic.isChecked()}
        ))

        grid.addWidget(self.check_agnostic, 7, 0, 1, 3)  # 一行三列

        # augmented inference
        self.check_augment = QCheckBox('Augment')
        self.check_augment.setChecked(GLOBAL.config.get('augment', True))
        self.check_augment.stateChanged.connect(lambda: GLOBAL.record_config(
            {'augment': self.check_augment.isChecked()}
        ))

        grid.addWidget(self.check_augment, 8, 0, 1, 3)  # 一行三列

        # 视频录制
        self.check_record = QCheckBox('Record video')
        grid.addWidget(self.check_record, 9, 0, 1, 3)  # 一行三列

        self.setLayout(grid)  # 设置布局

    def slot_check_camera(self):
        check = self.check_camera.isChecked()
        GLOBAL.record_config({'use_camera': check})  # 保存配置
        if check:
            self.line_video.setEnabled(False)
            self.btn_video.setEnabled(False)
        else:
            self.line_video.setEnabled(True)
            self.btn_video.setEnabled(True)

    def choose_weights_file(self):
        """从系统中选择权重文件"""
        file = QFileDialog.getOpenFileName(self, "Pre-trained YOLOv5 Weights", "./",
                                           "Weights Files (*.pt);;All Files (*)")
        if file[0] != '':
            self.line_weights.setText(file[0])
            GLOBAL.record_config({'weights': file[0]})

    def choose_video_file(self):
        """从系统中选择视频文件"""
        file = QFileDialog.getOpenFileName(self, "Video Files", "./",
                                           "Video Files (*)")
        if file[0] != '':
            self.line_video.setText(file[0])
            GLOBAL.record_config({'video': file[0]})

    def save_config(self):
        """保存当前的配置到配置文件"""
        config = {
            'use_camera': self.check_camera.isChecked(),
            'video': self.line_video.text(),
            'weights': self.line_weights.text(),
            'device': self.line_device.text(),
            'img_size': self.combo_size.currentData(),
            'conf_thresh': round(self.spin_conf.value(), 1),
            'iou_thresh': round(self.spin_iou.value(), 1),
            'agnostic': self.check_agnostic.isChecked(),
            'augment': self.check_augment.isChecked()
        }
        GLOBAL.record_config(config)