#!/usr/bin/env python3 import PyQt5.QtCore as QtCore import PyQt5.QtGui as QtGui import xasy2asy as xasy2asy import PrimitiveShape import math import Widg_addPolyOpt import Widg_addLabel class InplaceObjProcess(QtCore.QObject): objectCreated = QtCore.pyqtSignal(QtCore.QObject) objectUpdated = QtCore.pyqtSignal() def __init__(self, parent=None): super().__init__(parent) self._active = False pass @property def active(self): return self._active def mouseDown(self, pos, info, mouseEvent: QtGui.QMouseEvent=None): raise NotImplementedError def mouseMove(self, pos, event: QtGui.QMouseEvent): raise NotImplementedError def mouseRelease(self): raise NotImplementedError def forceFinalize(self): raise NotImplementedError def getPreview(self): return None def getObject(self): raise NotImplementedError def getXasyObject(self): raise NotImplementedError def postDrawPreview(self, canvas: QtGui.QPainter): pass def createOptWidget(self, info): return None class AddCircle(InplaceObjProcess): def __init__(self, parent=None): super().__init__(parent) self.center = QtCore.QPointF(0, 0) self.radius = 0 def mouseDown(self, pos, info, mouseEvent: QtGui.QMouseEvent=None): x, y = PrimitiveShape.PrimitiveShape.pos_to_tuple(pos) self.radius = 0 self.center.setX(x) self.center.setY(y) self.fill = info['fill'] self._active = True def mouseMove(self, pos, event): self.radius = PrimitiveShape.PrimitiveShape.euclideanNorm(pos, self.center) def mouseRelease(self): self.objectCreated.emit(self.getXasyObject()) self._active = False def getPreview(self): x, y = PrimitiveShape.PrimitiveShape.pos_to_tuple(self.center) boundRect = QtCore.QRectF(x - self.radius, y - self.radius, 2 * self.radius, 2 * self.radius) # because the internal image is flipped... newPath = QtGui.QPainterPath() newPath.addEllipse(boundRect) # newPath.addRect(boundRect) return newPath def getObject(self): return PrimitiveShape.PrimitiveShape.circle(self.center, self.radius) def getXasyObject(self): if self.fill: newObj = xasy2asy.xasyFilledShape(self.getObject(), None) else: newObj = xasy2asy.xasyShape(self.getObject(), None) return newObj def forceFinalize(self): self.mouseRelease() class AddLabel(InplaceObjProcess): def __init__(self, parent=None): super().__init__(parent) self.alignMode = None self.opt = None self.text = None self.anchor = QtCore.QPointF(0, 0) self._active = False self.fontSize = 12 def createOptWidget(self, info): self.opt = Widg_addLabel.Widg_addLabel(info) return self.opt def getPreview(self): return None def mouseRelease(self): self.objectCreated.emit(self.getXasyObject()) self._active = False def mouseMove(self, pos, event): x, y = PrimitiveShape.PrimitiveShape.pos_to_tuple(pos) self.anchor.setX(x) self.anchor.setY(y) def mouseDown(self, pos, info, mouseEvent: QtGui.QMouseEvent=None): if self.opt is not None: self.text = self.opt.labelText x, y = PrimitiveShape.PrimitiveShape.pos_to_tuple(pos) self.anchor.setX(x) self.anchor.setY(y) self.alignMode = info['align'] self.fontSize = info['fontSize'] self._active = True def getObject(self): finalTuple = PrimitiveShape.PrimitiveShape.pos_to_tuple(self.anchor) return {'txt': self.text, 'align': str(self.alignMode), 'anchor': finalTuple} def getXasyObject(self): text = self.text align = str(self.alignMode) anchor = PrimitiveShape.PrimitiveShape.pos_to_tuple(self.anchor) newLabel = xasy2asy.xasyText(text=text, location=anchor, pen=None, align=align, asyengine=None, fontsize=self.fontSize) newLabel.asyfied = False return newLabel def forceFinalize(self): self.mouseRelease() class AddBezierShape(InplaceObjProcess): def __init__(self, parent=None): super().__init__(parent) self.asyengine = None self.basePath = None self.basePathPreview = None self.closedPath = None self.info = None self.fill = False self.opt = None # list of "committed" points with Linkage information. # Linkmode should be to the last point. # (x, y, linkmode), (u, v, lm2) <==> (x, y) <=lm2=> (u, v) self.pointsList = [] self.currentPoint = QtCore.QPointF(0, 0) self.pendingPoint = None self.useLegacy = False def mouseDown(self, pos, info, mouseEvent: QtGui.QMouseEvent=None): x, y = PrimitiveShape.PrimitiveShape.pos_to_tuple(pos) self.currentPoint.setX(x) self.currentPoint.setY(y) self.info = info if not self._active: self._active = True self.fill = info['fill'] self.asyengine = info['asyengine'] self.closedPath = info['closedPath'] self.useBezierBase = info['useBezier'] self.useLegacy = self.info['options']['useLegacyDrawMode'] self.pointsList.clear() self.pointsList.append((x, y, None)) else: # see http://doc.qt.io/archives/qt-4.8/qt.html#MouseButton-enum if (int(mouseEvent.buttons()) if mouseEvent is not None else 0) & 0x2 and self.useLegacy: self.forceFinalize() def _getLinkType(self): if self.info['useBezier']: return '..' else: return '--' def mouseMove(self, pos, event): # in postscript coords. if self._active: x, y = PrimitiveShape.PrimitiveShape.pos_to_tuple(pos) if self.useLegacy or int(event.buttons()) != 0: self.currentPoint.setX(x) self.currentPoint.setY(y) else: self.forceFinalize() def createOptWidget(self, info): return None # self.opt = Widg_addBezierInPlace.Widg_addBezierInplace(info) # return self.opt def finalizeClosure(self): if self.active: self.closedPath = True self.forceFinalize() def mouseRelease(self): x, y = self.currentPoint.x(), self.currentPoint.y() self.pointsList.append((x, y, self._getLinkType())) # self.updateBasePath() def updateBasePath(self): self.basePath = xasy2asy.asyPath(asyengine=self.asyengine, forceCurve=self.useBezierBase) newNode = [(x, y) for x, y, _ in self.pointsList] newLink = [lnk for *args, lnk in self.pointsList[1:]] if self.useLegacy: newNode += [(self.currentPoint.x(), self.currentPoint.y())] newLink += [self._getLinkType()] if self.closedPath: newNode.append('cycle') newLink.append(self._getLinkType()) self.basePath.initFromNodeList(newNode, newLink) if self.useBezierBase: self.basePath.computeControls() def updateBasePathPreview(self): self.basePathPreview = xasy2asy.asyPath( asyengine=self.asyengine, forceCurve=self.useBezierBase) newNode = [(x, y) for x, y, _ in self.pointsList] + [(self.currentPoint.x(), self.currentPoint.y())] newLink = [lnk for *args, lnk in self.pointsList[1:]] + [self._getLinkType()] if self.closedPath: newNode.append('cycle') newLink.append(self._getLinkType()) self.basePathPreview.initFromNodeList(newNode, newLink) if self.useBezierBase: self.basePathPreview.computeControls() def forceFinalize(self): self.updateBasePath() self._active = False self.pointsList.clear() self.objectCreated.emit(self.getXasyObject()) self.basePath = None def getObject(self): if self.basePath is None: raise RuntimeError('BasePath is None') self.basePath.asyengine = self.asyengine return self.basePath def getPreview(self): if self._active: if self.pointsList: self.updateBasePathPreview() newPath = self.basePathPreview.toQPainterPath() return newPath def getXasyObject(self): if self.fill: return xasy2asy.xasyFilledShape(self.getObject(), None) else: return xasy2asy.xasyShape(self.getObject(), None) class AddPoly(InplaceObjProcess): def __init__(self, parent=None): super().__init__(parent) self.center = QtCore.QPointF(0, 0) self.currPos = QtCore.QPointF(0, 0) self.sides = None self.inscribed = None self.centermode = None self.asyengine = None self.fill = None self.opt = None def mouseDown(self, pos, info, mouseEvent: QtGui.QMouseEvent=None): self._active = True self.sides = info['sides'] self.inscribed = info['inscribed'] self.centermode = info['centermode'] self.fill = info['fill'] x, y = PrimitiveShape.PrimitiveShape.pos_to_tuple(pos) self.center.setX(x) self.center.setY(y) self.currPos = QtCore.QPointF(self.center) def mouseMove(self, pos, event): x, y = PrimitiveShape.PrimitiveShape.pos_to_tuple(pos) self.currPos.setX(x) self.currPos.setY(y) def mouseRelease(self): if self.active: self.objectCreated.emit(self.getXasyObject()) self._active = False def forceFinalize(self): self.mouseRelease() def getObject(self): if self.inscribed: return PrimitiveShape.PrimitiveShape.inscribedRegPolygon(self.sides, self.center, self._rad(), self._angle()) else: return PrimitiveShape.PrimitiveShape.exscribedRegPolygon(self.sides, self.center, self._rad(), self._angle()) def getPreview(self): if self.inscribed: poly = PrimitiveShape.PrimitiveShape.inscribedRegPolygon(self.sides, self.center, self._rad(), self._angle(), qpoly=True) else: poly = PrimitiveShape.PrimitiveShape.exscribedRegPolygon(self.sides, self.center, self._rad(), self._angle(), qpoly=True) newPath = QtGui.QPainterPath() newPath.addPolygon(poly) return newPath def createOptWidget(self, info): self.opt = Widg_addPolyOpt.Widg_addPolyOpt(info) return self.opt def _rad(self): return PrimitiveShape.PrimitiveShape.euclideanNorm(self.currPos, self.center) def _angle(self): dist_x = self.currPos.x() - self.center.x() dist_y = self.currPos.y() - self.center.y() if dist_x == 0 and dist_y == 0: return 0 else: return math.atan2(dist_y, dist_x) def getXasyObject(self): if self.fill: newObj = xasy2asy.xasyFilledShape(self.getObject(), None) else: newObj = xasy2asy.xasyShape(self.getObject(), None) return newObj class AddFreehand(InplaceObjProcess): # TODO: At the moment this is just a copy-paste of the AddBezierObj. # Must find a better algorithm for constructing the obj rather than # a node for every pixel the mouse moves. def __init__(self, parent=None): super().__init__(parent) self.asyengine = None self.basePath = None self.basePathPreview = None self.closedPath = None self.info = None self.fill = False self.opt = None # list of "committed" points with Linkage information. # Linkmode should be to the last point. # (x, y, linkmode), (u, v, lm2) <==> (x, y) <=lm2=> (u, v) self.pointsList = [] self.currentPoint = QtCore.QPointF(0, 0) self.pendingPoint = None self.useLegacy = False def mouseDown(self, pos, info, mouseEvent: QtGui.QMouseEvent=None): x, y = PrimitiveShape.PrimitiveShape.pos_to_tuple(pos) self.currentPoint.setX(x) self.currentPoint.setY(y) self.info = info if not self._active: self._active = True self.fill = info['fill'] self.asyengine = info['asyengine'] self.closedPath = info['closedPath'] self.useBezierBase = info['useBezier'] self.useLegacy = self.info['options']['useLegacyDrawMode'] self.pointsList.clear() self.pointsList.append((x, y, None)) else: # see http://doc.qt.io/archives/qt-4.8/qt.html#MouseButton-enum if (int(mouseEvent.buttons()) if mouseEvent is not None else 0) & 0x2 and self.useLegacy: self.forceFinalize() def _getLinkType(self): if self.info['useBezier']: return '..' else: return '--' def mouseMove(self, pos, event): # in postscript coords. if self._active: x, y = PrimitiveShape.PrimitiveShape.pos_to_tuple(pos) if self.useLegacy or int(event.buttons()) != 0: self.currentPoint.setX(x) self.currentPoint.setY(y) self.pointsList.append((x, y, self._getLinkType())) def createOptWidget(self, info): return None def mouseRelease(self): self.updateBasePath() self._active = False self.pointsList.clear() self.objectCreated.emit(self.getXasyObject()) self.basePath = None def updateBasePath(self): self.basePath = xasy2asy.asyPath(asyengine=self.asyengine, forceCurve=self.useBezierBase) newNode = [(x, y) for x, y, _ in self.pointsList] newLink = [lnk for *args, lnk in self.pointsList[1:]] if self.useLegacy: newNode += [(self.currentPoint.x(), self.currentPoint.y())] newLink += [self._getLinkType()] if self.closedPath: newNode.append('cycle') newLink.append(self._getLinkType()) self.basePath.initFromNodeList(newNode, newLink) if self.useBezierBase: self.basePath.computeControls() def updateBasePathPreview(self): self.basePathPreview = xasy2asy.asyPath( asyengine=self.asyengine, forceCurve=self.useBezierBase) newNode = [(x, y) for x, y, _ in self.pointsList] + [(self.currentPoint.x(), self.currentPoint.y())] newLink = [lnk for *args, lnk in self.pointsList[1:]] + [self._getLinkType()] if self.closedPath: newNode.append('cycle') newLink.append(self._getLinkType()) self.basePathPreview.initFromNodeList(newNode, newLink) if self.useBezierBase: self.basePathPreview.computeControls() def getObject(self): if self.basePath is None: raise RuntimeError('BasePath is None') self.basePath.asyengine = self.asyengine return self.basePath def getPreview(self): if self._active: if self.pointsList: self.updateBasePathPreview() newPath = self.basePathPreview.toQPainterPath() return newPath def getXasyObject(self): self.fill = False return xasy2asy.xasyShape(self.getObject(), None)