예제 #1
0
class CategoryWidget(QWidget):
    def __init__(self, cat, parent=None, flags=Qt.WindowFlags()):
        super().__init__(parent=parent, flags=flags)
        self.cat = cat
        self.onCategoryChanged = Listenable()

        layout = QHBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        self.setLayout(layout)

        nameButton = EditableButton(cat["name"])
        nameButton.onLabelChanged.addListener(self.onNameChanged_callback)
        layout.addWidget(nameButton)
        self.onNameChanged = nameButton.onLabelChanged

        # TODO: move edit functionality into color button
        colorButton = ColorButton(cat["color"])
        colorButton.onColorChanged.addListener(self.onColorChanged_callback)
        layout.addWidget(colorButton)
        self.onColorChanged = colorButton.onColorChanged

        self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)

        # TODO: Delete button (make sure you check that no annotations use this labl before deleting)
        # TODO: edit name

    def onNameChanged_callback(self, name):
        self.cat["name"] = name
        self.onCategoryChanged.notify()

    def onColorChanged_callback(self, color):
        self.cat["color"] = color
        self.onCategoryChanged.notify()
예제 #2
0
class CategoryEditor(QWidget):
	def __init__(self, cats, parent=None, flags=Qt.WindowFlags()):
		super().__init__(parent=parent, flags=flags)
		self.onCategoryAdded = Listenable()
		self.cats = cats
		self.setWindowTitle("Category Editor")
		self.setMinimumSize(256, 0)
		
		layout = QVBoxLayout()
		self.setLayout(layout)
		
		# TODO: notify label updated (color/name changed)
		
		self.catList = CategoryListScroll(cats)
		self.onCategoryChanged = self.catList.onCategoryChanged
		layout.addWidget(self.catList)
		
		addCategoryButton = QPushButton("Add Category")
		addCategoryButton.clicked.connect(lambda: self.addCategory("label " + str(len(cats)), QColor("#000").rgba()))
		layout.addWidget(addCategoryButton)
		
		self.updateCategories()
		
	def addCategory(self, name, color):
		cat = {
			"id": len(self.cats),
			"name": name,
			"color": color,
		}
		self.cats.append(cat)
		self.updateCategories()
		self.onCategoryAdded.notify(cat)
		
	def updateCategories(self):
		self.catList.updateCategories()
예제 #3
0
class EditableButton(QPushButton):
    def __init__(self, label, parent=None):
        super().__init__(parent=parent)
        self.onLabelChanged = Listenable()

        layout = QGridLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        self.setLayout(layout)

        self.setText(label)
        self.clicked.connect(self.editBegin)

        self.lineEdit = QLineEdit()
        self.lineEdit.setAlignment(Qt.AlignCenter)
        self.lineEdit.setSizePolicy(QSizePolicy.Expanding,
                                    QSizePolicy.Expanding)
        self.lineEdit.textEdited.connect(self.editing)
        self.lineEdit.editingFinished.connect(self.editFinished)
        self.lineEdit.hide()
        layout.addWidget(self.lineEdit)

    def editBegin(self):
        self.lineEdit.setText(self.text())
        self.lineEdit.show()
        self.lineEdit.setFocus(Qt.OtherFocusReason)

    def editing(self, text):
        self.setText(text)
        self.update()

    def editFinished(self):
        self.lineEdit.hide()
        self.onLabelChanged.notify(self.text())
예제 #4
0
	def __init__(self, cats, parent=None, flags=Qt.WindowFlags()):
		super().__init__(parent=parent, flags=flags)
		self.cats = cats
		self.onCategoryChanged = Listenable()
		self.layout = QVBoxLayout()
		self.layout.setContentsMargins(0, 0, 0, 0) # TODO: Can we control this on a application level? intead of per widget?
		self.setLayout(self.layout)
		self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding)
		self.updateCategories()
예제 #5
0
    def __init__(self, labels, parent=None, flags=Qt.WindowFlags()):
        super().__init__(parent=parent, flags=flags)
        self.onLayersUpdated = Listenable()
        self.layers = []
        self.actions = {}
        self.activeLayer = None

        self.labels = labels
        self.setMouseTracking(True)
        self.curPos = QPoint()
        self.oldPos = QPoint()
        self.points = []
예제 #6
0
    def __init__(self, label, parent=None):
        super().__init__(parent=parent)
        self.onLabelChanged = Listenable()

        layout = QGridLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        self.setLayout(layout)

        self.setText(label)
        self.clicked.connect(self.editBegin)

        self.lineEdit = QLineEdit()
        self.lineEdit.setAlignment(Qt.AlignCenter)
        self.lineEdit.setSizePolicy(QSizePolicy.Expanding,
                                    QSizePolicy.Expanding)
        self.lineEdit.textEdited.connect(self.editing)
        self.lineEdit.editingFinished.connect(self.editFinished)
        self.lineEdit.hide()
        layout.addWidget(self.lineEdit)
예제 #7
0
class ColorButton(QPushButton):
    def __init__(self, color=0, parent=None):
        super().__init__(parent=parent)
        self.onColorChanged = Listenable()
        self.setColor(color)
        self.clicked.connect(self.clicked_callback)

    def clicked_callback(self):
        color = QColorDialog.getColor(initial=self.color,
                                      options=QColorDialog.DontUseNativeDialog)
        if color.isValid():
            self.setColor(color.rgba())

    def setColor(self, color):
        self.color = QColor(color)
        self.onColorChanged.notify(color)

    def paintEvent(self, event: QPaintEvent):
        painter = QPainter(self)
        painter.fillRect(0, 0, self.width(), self.height(), self.color)
예제 #8
0
    def __init__(self, cat, parent=None, flags=Qt.WindowFlags()):
        super().__init__(parent=parent, flags=flags)
        self.cat = cat
        self.onCategoryChanged = Listenable()

        layout = QHBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        self.setLayout(layout)

        nameButton = EditableButton(cat["name"])
        nameButton.onLabelChanged.addListener(self.onNameChanged_callback)
        layout.addWidget(nameButton)
        self.onNameChanged = nameButton.onLabelChanged

        # TODO: move edit functionality into color button
        colorButton = ColorButton(cat["color"])
        colorButton.onColorChanged.addListener(self.onColorChanged_callback)
        layout.addWidget(colorButton)
        self.onColorChanged = colorButton.onColorChanged

        self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
예제 #9
0
class CategoryList(QWidget):
	def __init__(self, cats, parent=None, flags=Qt.WindowFlags()):
		super().__init__(parent=parent, flags=flags)
		self.cats = cats
		self.onCategoryChanged = Listenable()
		self.layout = QVBoxLayout()
		self.layout.setContentsMargins(0, 0, 0, 0) # TODO: Can we control this on a application level? intead of per widget?
		self.setLayout(self.layout)
		self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding)
		self.updateCategories()
		
	def updateCategories(self):
		while self.layout.count():
			self.layout.takeAt(0).widget().deleteLater()
		for cat in self.cats:
			widget = CategoryWidget(cat)
			widget.onCategoryChanged.addListener(lambda: self.onCategoryChanged.notify())
			self.layout.addWidget(widget)
예제 #10
0
class EditArea(QWidget):
    def __init__(self, labels, parent=None, flags=Qt.WindowFlags()):
        super().__init__(parent=parent, flags=flags)
        self.onLayersUpdated = Listenable()
        self.layers = []
        self.actions = {}
        self.activeLayer = None

        self.labels = labels
        self.setMouseTracking(True)
        self.curPos = QPoint()
        self.oldPos = QPoint()
        self.points = []

    def setImage(self, img: QImage):
        self.base = img
        self.canvas = QImage(self.base.width(), self.base.height(),
                             QImage.Format_RGBA8888)
        self.canvas.fill(Qt.transparent)
        self.layers = []
        self.layersUpdate()
        self.recalcScale()
        self.update()

    def getLayers(self):
        pass  # TODO: impl

    def layersUpdate(self):
        self.onLayersUpdated.notify(self.layers)

    def setActiveLayer(self, layer: LayerBitmap):
        self.activeLayer = layer
        self.activeAction.setLayer(self.activeLayer)
        self.update()

    def addLayer(self, layer):
        self.layers.append(layer)

        def fieldChangedListener(new, old):
            self.layersUpdate()
            self.update()

        layer.label.addListener(fieldChangedListener)
        layer.visible.addListener(fieldChangedListener)
        self.setActiveLayer(layer)
        self.layersUpdate()
        return layer

    def addBitmapLayer(self, layer_id: int):
        return self.addLayer(
            LayerBitmap(layer_id, self.base.height(), self.base.width()))

    def deleteLayer(self, layer):
        self.layers.remove(layer)
        if layer == self.activeLayer:
            self.setActiveLayer(None)
        self.layersUpdate()
        self.update()

    def setAction(self, action):
        if not action in self.actions:
            self.actions[action] = action()
        self.activeAction = self.actions[action]
        self.activeAction.setLayer(self.activeLayer)

    def recalcScale(self):
        self.scaledScale = min(self.width() / self.base.width(),
                               self.height() / self.base.height())
        self.scaledSize = self.base.size() * self.scaledScale
        self.scaledOffset = (self.size() - self.scaledSize) / 2
        self.scaledOffset = QPoint(self.scaledOffset.width(),
                                   self.scaledOffset.height())

    def resizeEvent(self, event: QResizeEvent):
        self.recalcScale()

    def mousePosToCanvasPos(self, pos: QPoint):
        return (pos - self.scaledOffset) / self.scaledScale

    def updateBindSystems(self, inp: Input, val: Any):
        if self.activeLayer and self.activeLayer.visible.value:
            self.activeAction.binds.update(inp, val)
        self.update()

    def mousePressEvent(self, event: QMouseEvent):
        self.updateBindSystems(
            Input(InputType.MOUSE, event.button()),
            (True, self.mousePosToCanvasPos(
                event.pos())))  # TODO: make proper custom event for this?

    def mouseReleaseEvent(self, event: QMouseEvent):
        self.updateBindSystems(
            Input(InputType.MOUSE, event.button()),
            (False, self.mousePosToCanvasPos(
                event.pos())))  # TODO: make proper custom event for this?

    def wheelEvent(self, event: QWheelEvent):
        self.updateBindSystems(Input(InputType.MOUSE_WHEEL),
                               event.angleDelta().y())

    def keyPressEvent(self, event: QKeyEvent):
        self.updateBindSystems(Input(InputType.KEYBOARD, event.key()),
                               (True, ))

    def keyReleaseEvent(self, event: QKeyEvent):
        self.updateBindSystems(Input(InputType.KEYBOARD, event.key()),
                               (False, ))

    def mouseMoveEvent(self, event: QMouseEvent):
        newPos = self.mousePosToCanvasPos(event.pos())

        if newPos == self.curPos:
            return

        self.oldPos = self.curPos
        self.curPos = newPos
        self.updateBindSystems(
            Input(InputType.MOUSE, event.button()),
            (None, self.curPos))  # TODO: make proper event for this?

    def composeCanvas(self):
        with QPainter(self.canvas) as painter:
            painter.drawImage(0, 0, self.base)

            # TODO: Cache the composite of the layers. So we dont lag when not editing with large number of layers.
            for layer in self.layers:
                if not layer.visible.value: continue
                # TODO: always draw active layer on top, lower opacity of BG layers
                # TODO: See if composing in numpy then converting is faster than converting to QImage and composing
                mask = layer.mask
                maskToQt = QImage(mask.data, mask.shape[1], mask.shape[0],
                                  QImage.Format_Indexed8)
                color = self.labels[layer.label.value]["color"]

                # Set the alpha
                color &= 0x00FFFFFF
                if layer == self.activeLayer:
                    color |= 0xAA000000
                else:
                    color |= 0x44000000

                maskToQt.setColorTable([0] * 255 + [color])
                painter.drawImage(0, 0, maskToQt)

        # TODO: make hints inverse of background color?
        self.activeAction.drawHints(self.canvas, self.curPos)

    def paintEvent(self, event: QPaintEvent):
        # TODO: Make use of event.rect(), may get better performance
        w = self.width()
        h = self.height()

        painter = QPainter(self)
        painter.setClipping(False)

        pen = QPen()
        pen.setWidth(2)
        pen.setColor(Qt.black)

        bgBrush = QBrush()
        bgBrush.setStyle(Qt.Dense5Pattern)
        bgBrush.setColor(QColor(0, 0, 0, 50))

        # Draw background
        painter.setBrush(Qt.white)
        painter.drawRect(0, 0, w, h)

        painter.setPen(pen)
        painter.setBrush(bgBrush)
        painter.drawRect(0, 0, w, h)

        # Draw Canvas
        # TODO: switch to Pillow resize here
        self.composeCanvas()
        painter.drawImage(self.scaledOffset,
                          self.canvas.scaled(self.scaledSize))
예제 #11
0
 def __init__(self, color=0, parent=None):
     super().__init__(parent=parent)
     self.onColorChanged = Listenable()
     self.setColor(color)
     self.clicked.connect(self.clicked_callback)