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
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)