Exemple #1
0
class StyleDesigner(QWidget):

    saveStyle = Signal(str, dict)

    symbols = {
        'o': 'circle',
        's': 'square',
        't': 'triangle',
        'd': 'diamond',
        '+': 'plus',
        't1': 'triangle up',
        't2': 'triangle right',
        't3': 'triangle left',
        'p': 'pentagon',
        'h': 'hexagon',
        'star': 'star',
        'x': 'cross',
        'arrow_up': 'arrow up',
        'arrow_right': 'arrow right',
        'arrow_down': 'arrow down',
        'arrow_left': 'arrow left',
        'crosshair': 'crosshair'
    }

    @classmethod
    @property
    def reverseSymbolDict(cls):
        return {value: key for key, value in cls.symbols.items()}

    def __init__(self,
                 name=None,
                 style=None,
                 styleKeys=None,
                 symbolKeys=[],
                 invalidNames=[]):
        super().__init__()

        if style is None and styleKeys is None:
            raise ValueError(
                "StyleDesigner needs either style dict or list of style keys.")

        if style is not None:
            styleKeys = self.style.keys()

        self.invalidNames = invalidNames
        # self.editing = False

        self.layout = QGridLayout()

        self.nameEdit = QLineEdit()
        if name is not None:
            self.setName(name)
        self.layout.addWidget(self.nameEdit, 0, 0, 1, -1)

        listHeight = None

        self.colours = {}
        row = 1
        for key in styleKeys:
            if key == "highlightPoint":
                label = "Highlight point"
            else:
                label = key.capitalize()
            colourName = QLabel(label)
            colourValue = ColourButton()
            self.colours[key] = colourValue
            self.layout.addWidget(colourName, row, 0)
            self.layout.addWidget(colourValue, row, 1)
            if key in symbolKeys:
                symbolList = self._createSymbolList()
                self.layout.addWidget(symbolList, row, 2)
                if listHeight is None:
                    listHeight = symbolList.sizeHint().height()
            self.colours[key].clicked.connect(self.setColour)
            row += 1

        if listHeight is not None:
            for rowNum in range(self.layout.rowCount()):
                item = self.layout.itemAtPosition(rowNum, 1)
                item.widget().height = listHeight

        # TODO both save and cancel buttons currently don't do anything
        self.saveButton = QPushButton("Save theme")
        self.cancelButton = QPushButton("Cancel")
        # self.cancelButton.setVisible(False)
        self.layout.addWidget(self.saveButton, row,
                              1)  # use 'row' value from above
        self.layout.addWidget(self.cancelButton, row, 2)
        self.setLayout(self.layout)

        self.saveButton.clicked.connect(self._saveStyle)
        self.setEditMode(False)

        if style is not None:
            self.setStyle(style)

        self.validateTimer = QTimer()
        self.validateTimer.setSingleShot(True)
        self.validateTimer.setInterval(50)
        self.validateTimer.timeout.connect(self._validate)
        self.nameEdit.textChanged.connect(self.validateTimer.start)
        self._validate()

    @property
    def name(self):
        return self.nameEdit.text()

    @property
    def invalidNames(self):
        return self._invalidNames

    @invalidNames.setter
    def invalidNames(self, names):
        names = [name.lower() for name in names]
        self._invalidNames = names

    def appendInvalidName(self, name):
        self.invalidNames.append(name.lower())

    def setName(self, name):
        self.nameEdit.setText(name.lower())

    def setStyle(self, style, name=None):
        for key, value in style.items():
            widget = self.colours[key]
            widget.setColour(value['colour'])
        if name is not None:
            self.setName(name)

    def getStyle(self):
        style = {}
        for row in range(1,
                         self.layout.rowCount() -
                         1):  # don't need first and last from layout
            key = self.layout.itemAtPosition(row, 0).widget().text().lower()
            colour = self.layout.itemAtPosition(row, 1).widget().colour

            # turn 'highlight point' back into 'highlightPoint'
            first, *rest = key.split(' ')
            key = first + ''.join([s.capitalize() for s in rest])
            style[key] = colour

            symbol = self.layout.itemAtPosition(row, 2)
            if symbol is not None:
                symbol = symbol.widget().currentText().lower()
                style[f"{key}Symbol"] = self.reverseSymbolDict[symbol]
        return self.name, style

    def _saveStyle(self):
        self.saveStyle.emit(*self.getStyle())
        self.setEditMode(False)

    def _validate(self):
        if not self.editing:
            name = self.nameEdit.text().lower()
            if name in self.invalidNames:
                self.saveButton.setEnabled(False)
            else:
                self.saveButton.setEnabled(True)

    def setEditMode(self, edit=None):
        if edit is not None:
            self.editing = edit
        if self.editing:
            self.saveButton.setEnabled(True)
            self.cancelButton.setVisible(True)
        else:
            self.cancelButton.setVisible(False)

    def setColour(self, widget, initialColour):
        colour = QColorDialog.getColor(QColor(initialColour), self)
        if colour.isValid():
            widget.setColour(colour)

    def _createSymbolList(self, colour=None):

        availableSymbols = [
            'x', 'o', 's', 't', 'd', '+', 't1', 't2', 't3', 'p', 'h', 'star'
        ]

        widget = QComboBox()
        for name in availableSymbols:
            widget.addItem(self.symbols[name].capitalize())

        # if colour is None:
        #     colour = self.palette().color(self.foregroundRole())
        # if isinstance(colour, str):
        #     colour = QColor(colour)
        # pen = QPen(colour)
        # brush = QBrush(colour)
        # size = 512
        # widget = QComboBox()
        # for symbol in symbols:
        #     pixmap = QPixmap(size, size)
        #     pixmap.fill(QColor("#00000000"))
        #     painter = QPainter(pixmap)
        #     painter.setRenderHint(painter.RenderHint.Antialiasing)
        #     painter.resetTransform()
        #     painter.translate(256, 256)
        #     drawSymbol(painter, symbol, 128, pen, brush)
        #     painter.end()
        #     # pixmap = renderSymbol(symbol, size, pen, brush, device=pixmap)
        #     pixmap.save(f"symbols/{symbol}.png")
        #     # pixmap = QPixmap()
        #     # pixmap.convertFromImage(image)
        #     widget.addItem(QIcon(pixmap), symbol)
        return widget
Exemple #2
0
class QtLayer(QFrame):
    def __init__(self, layer):
        super().__init__()

        self.layer = layer
        layer.events.select.connect(self._on_select)
        layer.events.deselect.connect(self._on_deselect)
        layer.events.name.connect(self._on_layer_name_change)
        layer.events.blending.connect(self._on_blending_change)
        layer.events.opacity.connect(self._on_opacity_change)
        layer.events.visible.connect(self._on_visible_change)

        self.setObjectName('layer')

        self.grid_layout = QGridLayout()

        cb = QCheckBox(self)
        cb.setObjectName('visibility')
        cb.setToolTip('Layer visibility')
        cb.setChecked(self.layer.visible)
        cb.setProperty('mode', 'visibility')
        cb.stateChanged.connect(lambda state=cb: self.changeVisible(state))
        self.visibleCheckBox = cb
        self.grid_layout.addWidget(cb, 0, 0)

        textbox = QLineEdit(self)
        textbox.setText(layer.name)
        textbox.home(False)
        textbox.setToolTip('Layer name')
        textbox.setFixedWidth(122)
        textbox.setAcceptDrops(False)
        textbox.setEnabled(True)
        textbox.editingFinished.connect(self.changeText)
        self.nameTextBox = textbox
        self.grid_layout.addWidget(textbox, 0, 1)

        self.grid_layout.addWidget(QLabel('opacity:'), 1, 0)
        sld = QSlider(Qt.Horizontal, self)
        sld.setFocusPolicy(Qt.NoFocus)
        sld.setFixedWidth(110)
        sld.setMinimum(0)
        sld.setMaximum(100)
        sld.setSingleStep(1)
        sld.setValue(self.layer.opacity * 100)
        sld.valueChanged[int].connect(
            lambda value=sld: self.changeOpacity(value))
        self.opacitySilder = sld
        self.grid_layout.addWidget(sld, 1, 1)

        blend_comboBox = QComboBox()
        for blend in self.layer._blending_modes:
            blend_comboBox.addItem(blend)
        index = blend_comboBox.findText(self.layer.blending,
                                        Qt.MatchFixedString)
        blend_comboBox.setCurrentIndex(index)
        blend_comboBox.activated[str].connect(
            lambda text=blend_comboBox: self.changeBlending(text))
        self.blendComboBox = blend_comboBox
        self.grid_layout.addWidget(QLabel('blending:'), 2, 0)
        self.grid_layout.addWidget(blend_comboBox, 2, 1)

        self.setLayout(self.grid_layout)
        msg = 'Click to select\nDrag to rearrange\nDouble click to expand'
        self.setToolTip(msg)
        self.setExpanded(False)
        self.setFixedWidth(250)
        self.grid_layout.setColumnMinimumWidth(0, 100)
        self.grid_layout.setColumnMinimumWidth(1, 100)
        self.layer.selected = True

    def _on_select(self, event):
        self.setProperty('selected', True)
        self.nameTextBox.setEnabled(True)
        self.style().unpolish(self)
        self.style().polish(self)

    def _on_deselect(self, event):
        self.setProperty('selected', False)
        self.nameTextBox.setEnabled(False)
        self.style().unpolish(self)
        self.style().polish(self)

    def changeOpacity(self, value):
        with self.layer.events.blocker(self._on_opacity_change):
            self.layer.opacity = value / 100

    def changeVisible(self, state):
        if state == Qt.Checked:
            self.layer.visible = True
        else:
            self.layer.visible = False

    def changeText(self):
        self.layer.name = self.nameTextBox.text()

    def changeBlending(self, text):
        self.layer.blending = text

    def mouseReleaseEvent(self, event):
        modifiers = event.modifiers()
        if modifiers == Qt.ShiftModifier:
            index = self.layer.viewer.layers.index(self.layer)
            lastSelected = None
            for i in range(len(self.layer.viewer.layers)):
                if self.layer.viewer.layers[i].selected:
                    lastSelected = i
            r = [index, lastSelected]
            r.sort()
            for i in range(r[0], r[1] + 1):
                self.layer.viewer.layers[i].selected = True
        elif modifiers == Qt.ControlModifier:
            self.layer.selected = not self.layer.selected
        else:
            self.layer.viewer.layers.unselect_all(ignore=self.layer)
            self.layer.selected = True

    def mousePressEvent(self, event):
        self.dragStartPosition = event.pos()

    def mouseMoveEvent(self, event):
        distance = (event.pos() - self.dragStartPosition).manhattanLength()
        if distance < QApplication.startDragDistance():
            return
        mimeData = QMimeData()
        if not self.layer.selected:
            name = self.layer.name
        else:
            name = ''
            for layer in self.layer.viewer.layers:
                if layer.selected:
                    name = layer.name + '; ' + name
            name = name[:-2]
        mimeData.setText(name)
        drag = QDrag(self)
        drag.setMimeData(mimeData)
        drag.setHotSpot(event.pos() - self.rect().topLeft())
        dropAction = drag.exec_(Qt.MoveAction | Qt.CopyAction)

        if dropAction == Qt.CopyAction:
            if not self.layer.selected:
                index = self.layer.viewer.layers.index(self.layer)
                self.layer.viewer.layers.pop(index)
            else:
                self.layer.viewer.layers.remove_selected()

    def setExpanded(self, bool):
        if bool:
            self.expanded = True
            rows = self.grid_layout.rowCount()
            self.setFixedHeight(55 * (rows - 1))
        else:
            self.expanded = False
            self.setFixedHeight(55)
        rows = self.grid_layout.rowCount()
        for i in range(1, rows):
            for j in range(2):
                if self.expanded:
                    self.grid_layout.itemAtPosition(i, j).widget().show()
                else:
                    self.grid_layout.itemAtPosition(i, j).widget().hide()

    def mouseDoubleClickEvent(self, event):
        self.setExpanded(not self.expanded)

    def _on_layer_name_change(self, event):
        with self.layer.events.name.blocker():
            self.nameTextBox.setText(self.layer.name)
            self.nameTextBox.home(False)

    def _on_opacity_change(self, event):
        with self.layer.events.opacity.blocker():
            self.opacitySilder.setValue(self.layer.opacity * 100)

    def _on_blending_change(self, event):
        with self.layer.events.blending.blocker():
            index = self.blendComboBox.findText(self.layer.blending,
                                                Qt.MatchFixedString)
            self.blendComboBox.setCurrentIndex(index)

    def _on_visible_change(self, event):
        with self.layer.events.visible.blocker():
            self.visibleCheckBox.setChecked(self.layer.visible)