Exemplo n.º 1
0
    def update_connc(self):
        delta = QPointF(self.prev_pos.x() - self.x(),
                        self.prev_pos.y() - self.y())

        for c in self.connectors:
            c.setPos(QPointF(c.x() - delta.x(), c.y() - delta.y()))
            pconnc: Connection
            for pconnc in c.prim_connc:
                pconnc.a = QPointF(c.x() + c.cpos[0] + Connector.SIZE / 2,
                                   c.y() + c.cpos[1] + Connector.SIZE / 2)
            sconnc: Connection
            for sconnc in c.scnd_connc:
                sconnc.b = QPointF(c.x() + c.cpos[0] + Connector.SIZE / 2,
                                   c.y() + c.cpos[1] + Connector.SIZE / 2)
        c: InteractiveComponent
        for c in self.fd_wx_consts:
            c.setPos(QPointF(c.x() - delta.x(), c.y() - delta.y()))

        self.prev_pos = self.pos()
Exemplo n.º 2
0
 def draw_simple_shape_to_scene(view: QGraphicsView,
                                p_prev: QPointF,
                                p_act: QPointF,
                                to_gim: bool = True) -> None:
     if view.actual_selection.value in [2, 4]:
         width = math.fabs(p_prev.x() - p_act.x())
         height = math.fabs(p_prev.y() - p_act.y())
         xmin = min(p_prev.x(), p_act.x())
         ymin = min(p_prev.y(), p_act.y())
         item: QAbstractGraphicsShapeItem
         if view.actual_selection.value == 2:
             item = QGraphicsRectItem(0, 0, width, height)
         else:
             item = QGraphicsEllipseItem(0, 0, width, height)
         item.setPos(QPointF(xmin, ymin))
         item.setPen(view.actual_pen)
         item.setBrush(view.actual_brush)
         DrawTools.set_item_flags(item)
         view.scene().addItem(item)
         view.gim.append_shape(item, item.type(), view.actual_pen,
                               view.actual_brush)
     elif view.actual_selection.value in [3, 5, 6]:
         x = p_act.x() - p_prev.x()
         y = p_act.y() - p_prev.y()
         item: QGraphicsItem
         if view.actual_selection.value == 3:
             item = QGraphicsPolygonItem(
                 QPolygonF(
                     [QPointF(0, 0),
                      QPointF(0 + x, 0),
                      QPointF(x / 2, y)]))
             item.setBrush(view.actual_brush)
         else:
             item = QGraphicsLineItem(QLineF(0, 0, x, y))
         item.setPos(p_prev)
         item.setPen(view.actual_pen)
         DrawTools.set_item_flags(item)
         view.scene().addItem(item)
         if to_gim:
             view.gim.append_shape(item, item.type(), view.actual_pen,
                                   view.actual_brush)
Exemplo n.º 3
0
class ChipSceneViewer(QGraphicsView):
    selectionChanged = Signal(list)

    def __init__(self):
        super().__init__()

        scene = QGraphicsScene()
        self.setScene(scene)

        self.setRenderHint(QPainter.Antialiasing)
        self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.setMouseTracking(True)

        self._offset = QPointF(0, 0)
        self._zoom = 0

        self.backgroundColor = QColor(40, 40, 40)

        self.gridSpacing = QSizeF(60, 60)
        self.gridThickness = 1
        self.gridColor = QColor(100, 100, 100)
        self.gridZoomThreshold = -1.5
        self.showGrid = True

        self.selectionBoxStrokeColor = QColor(52, 222, 235)
        self.selectionBoxFillColor = QColor(52, 222, 235, 50)
        self.selectionBoxThickness = 2
        self._editing = True

        self._hoveredItems: List[ChipItem] = []
        self._selectedItems: List[ChipItem] = []
        self._sceneItems: Set[ChipItem] = set()

        self._boxSelectionRectAnchor = QPointF()
        self._currentCursorPosition = QPointF()

        self.selectionBox = self.scene().addRect(QRectF(),
                                                 QPen(self.selectionBoxStrokeColor, self.selectionBoxThickness),
                                                 QBrush(self.selectionBoxFillColor))
        self.selectionBox.setVisible(False)

        self._state = State.IDLE

        self.UpdateView()

    def SetEditing(self, editing: bool):
        self._editing = editing

        self.showGrid = editing

        for item in self._sceneItems:
            item.SetEditDisplay(editing)

        if not editing:
            self.DeselectAll()

    def GetSelectedItems(self):
        return self._selectedItems

    def AddItem(self, item: ChipItem):
        if item not in self._sceneItems:
            self._sceneItems.add(item)
            item.Move(QPointF())
            item.onRemoved.connect(self.RemoveItem)
            self.scene().addItem(item.GraphicsObject())
        item.SetEditDisplay(self._editing)
        return item

    def RemoveItem(self, item: ChipItem):
        self.DeselectItem(item)
        if item in self._hoveredItems:
            self._hoveredItems.remove(item)

        if item in self._sceneItems:
            self._sceneItems.remove(item)
            self.scene().removeItem(item.GraphicsObject())

    def GetItems(self):
        return self._sceneItems

    def RemoveAll(self):
        self._hoveredItems.clear()
        self._selectedItems.clear()
        for item in self._sceneItems.copy():
            self.RemoveItem(item)

    def SelectItem(self, item: ChipItem):
        if item not in self._selectedItems:
            item.SetSelected(True)
            self._selectedItems.append(item)

    def ToggleSelectItem(self, item: ChipItem):
        if item in self._selectedItems:
            self.DeselectItem(item)
        else:
            self.SelectItem(item)

    def DeselectItem(self, item: ChipItem):
        if item in self._selectedItems:
            item.SetSelected(False)
            self._selectedItems.remove(item)

    def DeselectAll(self):
        for item in self._selectedItems.copy():
            self.DeselectItem(item)

    def Recenter(self):
        itemsRect = QRectF()
        for item in self._sceneItems:
            itemsRect = itemsRect.united(item.GraphicsObject().boundingRect().translated(item.GraphicsObject().pos()))
        self._offset = itemsRect.center()

        self.UpdateView()

    def CenterItem(self, item: ChipItem):
        QApplication.processEvents()
        sceneCenter = self.mapToScene(self.rect().center())
        currentCenter = item.GraphicsObject().sceneBoundingRect().center()
        delta = sceneCenter - currentCenter
        item.Move(delta)

    def UpdateView(self):
        matrix = QTransform()
        matrix.scale(2 ** self._zoom, 2 ** self._zoom)
        self.setTransform(matrix)
        self.setSceneRect(QRectF(self._offset.x(), self._offset.y(), 10, 10))

        self.UpdateSelectionBox()
        self.UpdateHoveredItems()

    @staticmethod
    def GetSelectionMode():
        if QApplication.keyboardModifiers() == Qt.ShiftModifier:
            return SelectionMode.MODIFY
        return SelectionMode.NORMAL

    def CreateSelectionRect(self) -> QRectF:
        cursorScene = self.mapToScene(self._currentCursorPosition.toPoint())
        if self._state is State.SELECTING:
            selectionRect = QRectF(0, 0, abs(self._boxSelectionRectAnchor.x() - cursorScene.x()),
                                   abs(self._boxSelectionRectAnchor.y() - cursorScene.y()))
            selectionRect.moveCenter((cursorScene + self._boxSelectionRectAnchor) / 2.0)
        else:
            selectionRect = QRectF(cursorScene, QSizeF())
        return selectionRect

    def UpdateHoveredItems(self):
        if self._state is State.PANNING:
            return

        if not self._editing or self._state is State.MOVING:
            hoveredChipItems = []
        else:
            # What are we hovering over in the scene
            hoveredGraphicsItems = [item for item in self.scene().items(self.selectionBox.sceneBoundingRect())]

            hoveredChipItems = [item for item in self._sceneItems if item.GraphicsObject() in hoveredGraphicsItems]
            # Make sure we maintain the found order
            hoveredChipItems.sort(key=lambda x: hoveredGraphicsItems.index(x.GraphicsObject()))

            if self._state is not State.SELECTING:
                hoveredChipItems = hoveredChipItems[:1]

        for item in hoveredChipItems:
            if item not in self._hoveredItems and item.CanSelect():
                item.SetHovered(True)
        for item in self._hoveredItems:
            if item not in hoveredChipItems:
                item.SetHovered(False)
        self._hoveredItems = hoveredChipItems

    def UpdateSelectionBox(self):
        if self._state == State.SELECTING:
            self.selectionBox.setVisible(True)
            self.selectionBox.prepareGeometryChange()
        else:
            self.selectionBox.setVisible(False)
        self.selectionBox.setRect(self.CreateSelectionRect())

    def drawBackground(self, painter: QPainter, rect: QRectF):
        currentColor = self.backgroundBrush().color()
        if currentColor != self.backgroundColor:
            self.setBackgroundBrush(QBrush(self.backgroundColor))

        super().drawBackground(painter, rect)

        if self._zoom <= self.gridZoomThreshold or not self.showGrid:
            return

        painter.setPen(QPen(self.gridColor, self.gridThickness))

        lines = []
        if self.gridSpacing.width() > 0:
            xStart = rect.left() - rect.left() % self.gridSpacing.width()
            while xStart <= rect.right():
                line = QLineF(xStart, rect.bottom(), xStart, rect.top())
                lines.append(line)
                xStart = xStart + self.gridSpacing.width()

        if self.gridSpacing.height() > 0:
            yStart = rect.top() - rect.top() % self.gridSpacing.height()
            while yStart <= rect.bottom():
                line = QLineF(rect.left(), yStart, rect.right(), yStart)
                lines.append(line)
                yStart = yStart + self.gridSpacing.height()

        painter.drawLines(lines)

    def wheelEvent(self, event: QWheelEvent):
        numSteps = float(event.angleDelta().y()) / 1000

        oldWorldPos = self.mapToScene(self._currentCursorPosition.toPoint())
        self._zoom = min(self._zoom + numSteps, 0)
        self.UpdateView()
        newWorldPos = self.mapToScene(self._currentCursorPosition.toPoint())

        delta = newWorldPos - oldWorldPos
        self._offset -= delta

        self.UpdateView()

    def mousePressEvent(self, event):
        if self._state != State.IDLE:
            return

        if event.button() == Qt.RightButton:
            self._state = State.PANNING
        elif event.button() == Qt.LeftButton and self._editing:
            if len(self._hoveredItems) == 0:
                self._state = State.SELECTING
                self._boxSelectionRectAnchor = self.mapToScene(self._currentCursorPosition.toPoint())
            else:
                hoveredItem = self._hoveredItems[0]
                if self.GetSelectionMode() is SelectionMode.MODIFY:
                    self.ToggleSelectItem(hoveredItem)
                    self.selectionChanged.emit(self._selectedItems)
                else:
                    if hoveredItem not in self._selectedItems:
                        self.DeselectAll()
                        self.SelectItem(hoveredItem)
                        self.selectionChanged.emit(self._selectedItems)
                    if hoveredItem.CanMove(self.mapToScene(self._currentCursorPosition.toPoint())):
                        self._state = State.MOVING

        self.UpdateView()

        super().mousePressEvent(event)

    def mouseMoveEvent(self, event: QMouseEvent):
        # Update position movement
        newCursorPosition = event.localPos()
        if self._currentCursorPosition is None:
            deltaScene = QPointF()
        else:
            deltaScene = (self.mapToScene(newCursorPosition.toPoint()) -
                          self.mapToScene(self._currentCursorPosition.toPoint()))
        self._currentCursorPosition = newCursorPosition

        if self._state is State.PANNING:
            self._offset -= deltaScene
            self.UpdateView()
        elif self._state is State.MOVING:
            for selectedItem in self._selectedItems:
                selectedItem.Move(deltaScene)

        self.UpdateView()

        super().mouseMoveEvent(event)

    def mouseReleaseEvent(self, event: QMouseEvent):
        super().mouseReleaseEvent(event)
        if self._state == State.PANNING:
            if event.button() == Qt.RightButton:
                self._state = State.IDLE
        elif self._state == State.SELECTING:
            if event.button() == Qt.LeftButton:
                self._state = State.IDLE
                if self.GetSelectionMode() is SelectionMode.MODIFY:
                    for item in self._hoveredItems:
                        self.ToggleSelectItem(item)
                else:
                    self.DeselectAll()
                    for item in self._hoveredItems:
                        self.SelectItem(item)
                self.selectionChanged.emit(self._selectedItems)
        elif self._state == State.MOVING:
            if event.button() == Qt.LeftButton:
                self._state = State.IDLE

        self.UpdateView()

        super().mouseReleaseEvent(event)

    def keyReleaseEvent(self, event: QKeyEvent):
        if event.key() == Qt.Key.Key_Delete:
            self.DeleteSelected()

        if event.key() == Qt.Key.Key_D and event.modifiers() == Qt.Modifier.CTRL:
            self.DuplicateSelected()

        super().keyReleaseEvent(event)

    def DeleteSelected(self):
        for item in self.GetSelectedItems().copy():
            if item.CanDelete():
                item.RequestDelete()

    def DuplicateSelected(self):
        newItems = []
        for item in self.GetSelectedItems():
            if item.CanDuplicate():
                newItem = item.Duplicate()
                newItem.Move(QPointF(50, 50))
                self.AddItem(newItem)
                newItems.append(newItem)
        if newItems:
            self.DeselectAll()
            [self.SelectItem(item) for item in newItems]
            self.selectionChanged.emit(self.GetSelectedItems())
Exemplo n.º 4
0
def centered_text(painter: QtGui.QPainter, pos: QPointF, text: str):
    rect = QRectF(pos.x() - 32767 * 0.5, pos.y() - 32767 * 0.5, 32767, 32767)
    painter.drawText(rect, QtGui.Qt.AlignCenter, text)
Exemplo n.º 5
0
 def qt_local_to_game_loc(self, pos: QPointF) -> NodeLocation:
     return NodeLocation((pos.x() / self.scale) + self.area_bounds.min_x,
                         self.area_bounds.max_y - (pos.y() / self.scale), 0)
class GLWidget(QOpenGLWidget, QOpenGLFunctions):
    xRotationChanged = Signal(int)
    yRotationChanged = Signal(int)
    zRotationChanged = Signal(int)

    def __init__(self, parent=None):
        QOpenGLWidget.__init__(self, parent)
        QOpenGLFunctions.__init__(self)

        self.core = "--coreprofile" in QCoreApplication.arguments()
        self.xRot = 0
        self.yRot = 0
        self.zRot = 0
        self.lastPos = QPointF()
        self.logo = Logo()
        self.vao = QOpenGLVertexArrayObject()
        self.logoVbo = QOpenGLBuffer()
        self.program = QOpenGLShaderProgram()
        self.projMatrixLoc = 0
        self.mvMatrixLoc = 0
        self.normalMatrixLoc = 0
        self.lightPosLoc = 0
        self.proj = QMatrix4x4()
        self.camera = QMatrix4x4()
        self.world = QMatrix4x4()
        self.transparent = "--transparent" in QCoreApplication.arguments()
        if self.transparent:
            fmt = self.format()
            fmt.setAlphaBufferSize(8)
            self.setFormat(fmt)

    def xRotation(self):
        return self.xRot

    def yRotation(self):
        return self.yRot

    def zRotation(self):
        return self.zRot

    def minimumSizeHint(self):
        return QSize(50, 50)

    def sizeHint(self):
        return QSize(400, 400)

    def normalizeAngle(self, angle):
        while angle < 0:
            angle += 360 * 16
        while angle > 360 * 16:
            angle -= 360 * 16
        return angle

    def setXRotation(self, angle):
        angle = self.normalizeAngle(angle)
        if angle != self.xRot:
            self.xRot = angle
            self.emit(SIGNAL("xRotationChanged(int)"), angle)
            self.update()

    def setYRotation(self, angle):
        angle = self.normalizeAngle(angle)
        if angle != self.yRot:
            self.yRot = angle
            self.emit(SIGNAL("yRotationChanged(int)"), angle)
            self.update()

    def setZRotation(self, angle):
        angle = self.normalizeAngle(angle)
        if angle != self.zRot:
            self.zRot = angle
            self.emit(SIGNAL("zRotationChanged(int)"), angle)
            self.update()

    def cleanup(self):
        self.makeCurrent()
        self.logoVbo.destroy()
        del self.program
        self.program = None
        self.doneCurrent()

    def vertexShaderSourceCore(self):
        return """#version 150
                in vec4 vertex;
                in vec3 normal;
                out vec3 vert;
                out vec3 vertNormal;
                uniform mat4 projMatrix;
                uniform mat4 mvMatrix;
                uniform mat3 normalMatrix;
                void main() {
                   vert = vertex.xyz;
                   vertNormal = normalMatrix * normal;
                   gl_Position = projMatrix * mvMatrix * vertex;
                }"""

    def fragmentShaderSourceCore(self):
        return """#version 150
                in highp vec3 vert;
                in highp vec3 vertNormal;
                out highp vec4 fragColor;
                uniform highp vec3 lightPos;
                void main() {
                   highp vec3 L = normalize(lightPos - vert);
                   highp float NL = max(dot(normalize(vertNormal), L), 0.0);
                   highp vec3 color = vec3(0.39, 1.0, 0.0);
                   highp vec3 col = clamp(color * 0.2 + color * 0.8 * NL, 0.0, 1.0);
                   fragColor = vec4(col, 1.0);
                }"""


    def vertexShaderSource(self):
        return """attribute vec4 vertex;
                attribute vec3 normal;
                varying vec3 vert;
                varying vec3 vertNormal;
                uniform mat4 projMatrix;
                uniform mat4 mvMatrix;
                uniform mat3 normalMatrix;
                void main() {
                   vert = vertex.xyz;
                   vertNormal = normalMatrix * normal;
                   gl_Position = projMatrix * mvMatrix * vertex;
                }"""

    def fragmentShaderSource(self):
        return """varying highp vec3 vert;
                varying highp vec3 vertNormal;
                uniform highp vec3 lightPos;
                void main() {
                   highp vec3 L = normalize(lightPos - vert);
                   highp float NL = max(dot(normalize(vertNormal), L), 0.0);
                   highp vec3 color = vec3(0.39, 1.0, 0.0);
                   highp vec3 col = clamp(color * 0.2 + color * 0.8 * NL, 0.0, 1.0);
                   gl_FragColor = vec4(col, 1.0);
                }"""

    def initializeGL(self):
        self.context().aboutToBeDestroyed.connect(self.cleanup)
        self.initializeOpenGLFunctions()
        self.glClearColor(0, 0, 0, 1)

        self.program = QOpenGLShaderProgram()

        if self.core:
            self.vertexShader = self.vertexShaderSourceCore()
            self.fragmentShader = self.fragmentShaderSourceCore()
        else:
            self.vertexShader = self.vertexShaderSource()
            self.fragmentShader = self.fragmentShaderSource()

        self.program.addShaderFromSourceCode(QOpenGLShader.Vertex, self.vertexShader)
        self.program.addShaderFromSourceCode(QOpenGLShader.Fragment, self.fragmentShader)
        self.program.bindAttributeLocation("vertex", 0)
        self.program.bindAttributeLocation("normal", 1)
        self.program.link()

        self.program.bind()
        self.projMatrixLoc = self.program.uniformLocation("projMatrix")
        self.mvMatrixLoc = self.program.uniformLocation("mvMatrix")
        self.normalMatrixLoc = self.program.uniformLocation("normalMatrix")
        self.lightPosLoc = self.program.uniformLocation("lightPos")

        self.vao.create()
        vaoBinder = QOpenGLVertexArrayObject.Binder(self.vao)

        self.logoVbo.create()
        self.logoVbo.bind()
        float_size = ctypes.sizeof(ctypes.c_float)
        self.logoVbo.allocate(self.logo.constData(), self.logo.count() * float_size)

        self.setupVertexAttribs()

        self.camera.setToIdentity()
        self.camera.translate(0, 0, -1)

        self.program.setUniformValue(self.lightPosLoc, QVector3D(0, 0, 70))
        self.program.release()
        vaoBinder = None

    def setupVertexAttribs(self):
        self.logoVbo.bind()
        f = QOpenGLContext.currentContext().functions()
        f.glEnableVertexAttribArray(0)
        f.glEnableVertexAttribArray(1)
        float_size = ctypes.sizeof(ctypes.c_float)

        null = VoidPtr(0)
        pointer = VoidPtr(3 * float_size)
        f.glVertexAttribPointer(0, 3, int(GL.GL_FLOAT), int(GL.GL_FALSE), 6 * float_size, null)
        f.glVertexAttribPointer(1, 3, int(GL.GL_FLOAT), int(GL.GL_FALSE), 6 * float_size, pointer)
        self.logoVbo.release()

    def paintGL(self):
        self.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT)
        self.glEnable(GL.GL_DEPTH_TEST)
        self.glEnable(GL.GL_CULL_FACE)

        self.world.setToIdentity()
        self.world.rotate(180 - (self.xRot / 16), 1, 0, 0)
        self.world.rotate(self.yRot / 16, 0, 1, 0)
        self.world.rotate(self.zRot / 16, 0, 0, 1)

        vaoBinder = QOpenGLVertexArrayObject.Binder(self.vao)
        self.program.bind()
        self.program.setUniformValue(self.projMatrixLoc, self.proj)
        self.program.setUniformValue(self.mvMatrixLoc, self.camera * self.world)
        normalMatrix = self.world.normalMatrix()
        self.program.setUniformValue(self.normalMatrixLoc, normalMatrix)

        self.glDrawArrays(GL.GL_TRIANGLES, 0, self.logo.vertexCount())
        self.program.release()
        vaoBinder = None

    def resizeGL(self, width, height):
        self.proj.setToIdentity()
        self.proj.perspective(45, width / height, 0.01, 100)

    def mousePressEvent(self, event):
        self.lastPos = event.position()

    def mouseMoveEvent(self, event):
        pos = event.position()
        dx = pos.x() - self.lastPos.x()
        dy = pos.y() - self.lastPos.y()

        if event.buttons() & Qt.LeftButton:
            self.setXRotation(self.xRot + 8 * dy)
            self.setYRotation(self.yRot + 8 * dx)
        elif event.buttons() & Qt.RightButton:
            self.setXRotation(self.xRot + 8 * dy)
            self.setZRotation(self.zRot + 8 * dx)

        self.lastPos = pos
Exemplo n.º 7
0
class MandelbrotWidget(QWidget):
    def __init__(self, parent=None):
        super(MandelbrotWidget, self).__init__(parent)

        self.thread = RenderThread()
        self.pixmap = QPixmap()
        self.pixmapOffset = QPointF()
        self.lastDragPos = QPointF()

        self.centerX = DefaultCenterX
        self.centerY = DefaultCenterY
        self.pixmapScale = DefaultScale
        self.curScale = DefaultScale

        self.thread.renderedImage.connect(self.updatePixmap)

        self.setWindowTitle("Mandelbrot")
        self.setCursor(Qt.CrossCursor)
        self.resize(550, 400)

    def paintEvent(self, event):
        painter = QPainter(self)
        painter.fillRect(self.rect(), Qt.black)

        if self.pixmap.isNull():
            painter.setPen(Qt.white)
            painter.drawText(self.rect(), Qt.AlignCenter,
                    "Rendering initial image, please wait...")
            return

        if self.curScale == self.pixmapScale:
            painter.drawPixmap(self.pixmapOffset, self.pixmap)
        else:
            scaleFactor = self.pixmapScale / self.curScale
            newWidth = int(self.pixmap.width() * scaleFactor)
            newHeight = int(self.pixmap.height() * scaleFactor)
            newX = self.pixmapOffset.x() + (self.pixmap.width() - newWidth) / 2
            newY = self.pixmapOffset.y() + (self.pixmap.height() - newHeight) / 2

            painter.save()
            painter.translate(newX, newY)
            painter.scale(scaleFactor, scaleFactor)
            exposed, _ = painter.transform().inverted()
            exposed = exposed.mapRect(self.rect()).adjusted(-1, -1, 1, 1)
            painter.drawPixmap(exposed, self.pixmap, exposed)
            painter.restore()

        text = "Use mouse wheel or the '+' and '-' keys to zoom. Press and " \
                "hold left mouse button to scroll."
        metrics = painter.fontMetrics()
        textWidth = metrics.horizontalAdvance(text)

        painter.setPen(Qt.NoPen)
        painter.setBrush(QColor(0, 0, 0, 127))
        painter.drawRect((self.width() - textWidth) / 2 - 5, 0, textWidth + 10,
                metrics.lineSpacing() + 5)
        painter.setPen(Qt.white)
        painter.drawText((self.width() - textWidth) / 2,
                metrics.leading() + metrics.ascent(), text)

    def resizeEvent(self, event):
        self.thread.render(self.centerX, self.centerY, self.curScale, self.size())

    def keyPressEvent(self, event):
        if event.key() == Qt.Key_Plus:
            self.zoom(ZoomInFactor)
        elif event.key() == Qt.Key_Minus:
            self.zoom(ZoomOutFactor)
        elif event.key() == Qt.Key_Left:
            self.scroll(-ScrollStep, 0)
        elif event.key() == Qt.Key_Right:
            self.scroll(+ScrollStep, 0)
        elif event.key() == Qt.Key_Down:
            self.scroll(0, -ScrollStep)
        elif event.key() == Qt.Key_Up:
            self.scroll(0, +ScrollStep)
        else:
            super(MandelbrotWidget, self).keyPressEvent(event)

    def wheelEvent(self, event):
        numDegrees = event.angleDelta().y() / 8
        numSteps = numDegrees / 15.0
        self.zoom(pow(ZoomInFactor, numSteps))

    def mousePressEvent(self, event):
        if event.buttons() == Qt.LeftButton:
            self.lastDragPos = event.position()

    def mouseMoveEvent(self, event):
        if event.buttons() & Qt.LeftButton:
            pos = event.position()
            self.pixmapOffset += pos - self.lastDragPos
            self.lastDragPos = pos
            self.update()

    def mouseReleaseEvent(self, event):
        if event.button() == Qt.LeftButton:
            self.pixmapOffset += event.position() - self.lastDragPos
            self.lastDragPos = QPointF()

            deltaX = (self.width() - self.pixmap.width()) / 2 - self.pixmapOffset.x()
            deltaY = (self.height() - self.pixmap.height()) / 2 - self.pixmapOffset.y()
            self.scroll(deltaX, deltaY)

    def updatePixmap(self, image, scaleFactor):
        if not self.lastDragPos.isNull():
            return

        self.pixmap = QPixmap.fromImage(image)
        self.pixmapOffset = QPointF()
        self.lastDragPosition = QPointF()
        self.pixmapScale = scaleFactor
        self.update()

    def zoom(self, zoomFactor):
        self.curScale *= zoomFactor
        self.update()
        self.thread.render(self.centerX, self.centerY, self.curScale,
                self.size())

    def scroll(self, deltaX, deltaY):
        self.centerX += deltaX * self.curScale
        self.centerY += deltaY * self.curScale
        self.update()
        self.thread.render(self.centerX, self.centerY, self.curScale,
                self.size())