1#!/usr/local/bin/python3.8 2 3import PyQt5.QtCore as Qc 4import PyQt5.QtGui as Qg 5import xasy2asy as x2a 6 7import PrimitiveShape 8import math 9 10import Widg_addPolyOpt 11import Widg_addLabel 12 13 14class InplaceObjProcess(Qc.QObject): 15 objectCreated = Qc.pyqtSignal(Qc.QObject) 16 objectUpdated = Qc.pyqtSignal() 17 18 def __init__(self, parent=None): 19 super().__init__(parent) 20 self._active = False 21 pass 22 23 @property 24 def active(self): 25 return self._active 26 27 def mouseDown(self, pos, info, mouseEvent: Qg.QMouseEvent=None): 28 raise NotImplementedError 29 30 def mouseMove(self, pos, event: Qg.QMouseEvent): 31 raise NotImplementedError 32 33 def mouseRelease(self): 34 raise NotImplementedError 35 36 def forceFinalize(self): 37 raise NotImplementedError 38 39 def getPreview(self): 40 return None 41 42 def getObject(self): 43 raise NotImplementedError 44 45 def getXasyObject(self): 46 raise NotImplementedError 47 48 def postDrawPreview(self, canvas: Qg.QPainter): 49 pass 50 51 def createOptWidget(self, info): 52 return None 53 54 55class AddCircle(InplaceObjProcess): 56 def __init__(self, parent=None): 57 super().__init__(parent) 58 self.center = Qc.QPointF(0, 0) 59 self.radius = 0 60 61 def mouseDown(self, pos, info, mouseEvent: Qg.QMouseEvent=None): 62 x, y = PrimitiveShape.PrimitiveShape.pos_to_tuple(pos) 63 self.radius = 0 64 self.center.setX(x) 65 self.center.setY(y) 66 self.fill = info['fill'] 67 self._active = True 68 69 def mouseMove(self, pos, event): 70 self.radius = PrimitiveShape.PrimitiveShape.euclideanNorm(pos, self.center) 71 72 def mouseRelease(self): 73 self.objectCreated.emit(self.getXasyObject()) 74 self._active = False 75 76 def getPreview(self): 77 x, y = PrimitiveShape.PrimitiveShape.pos_to_tuple(self.center) 78 boundRect = Qc.QRectF(x - self.radius, y - self.radius, 2 * self.radius, 2 * self.radius) 79 # because the internal image is flipped... 80 newPath = Qg.QPainterPath() 81 newPath.addEllipse(boundRect) 82 # newPath.addRect(boundRect) 83 return newPath 84 85 def getObject(self): 86 return PrimitiveShape.PrimitiveShape.circle(self.center, self.radius) 87 88 def getXasyObject(self): 89 if self.fill: 90 newObj = x2a.xasyFilledShape(self.getObject(), None) 91 else: 92 newObj = x2a.xasyShape(self.getObject(), None) 93 return newObj 94 95 def forceFinalize(self): 96 self.mouseRelease() 97 98 99class AddLabel(InplaceObjProcess): 100 def __init__(self, parent=None): 101 super().__init__(parent) 102 self.alignMode = None 103 self.opt = None 104 self.text = None 105 self.anchor = Qc.QPointF(0, 0) 106 self._active = False 107 108 def createOptWidget(self, info): 109 self.opt = Widg_addLabel.Widg_addLabel(info) 110 return self.opt 111 112 def getPreview(self): 113 return None 114 115 def mouseRelease(self): 116 self.objectCreated.emit(self.getXasyObject()) 117 self._active = False 118 119 def mouseMove(self, pos, event): 120 x, y = PrimitiveShape.PrimitiveShape.pos_to_tuple(pos) 121 self.anchor.setX(x) 122 self.anchor.setY(y) 123 124 def mouseDown(self, pos, info, mouseEvent: Qg.QMouseEvent=None): 125 if self.opt is not None: 126 self.text = self.opt.labelText 127 x, y = PrimitiveShape.PrimitiveShape.pos_to_tuple(pos) 128 self.anchor.setX(x) 129 self.anchor.setY(y) 130 131 self.alignMode = info['align'] 132 self.fontSize = info['fontSize'] 133 self._active = True 134 135 def getObject(self): 136 finalTuple = PrimitiveShape.PrimitiveShape.pos_to_tuple(self.anchor) 137 return {'txt': self.text, 'align': str(self.alignMode), 'anchor': finalTuple} 138 139 def getXasyObject(self): 140 text = self.text 141 align = str(self.alignMode) 142 anchor = PrimitiveShape.PrimitiveShape.pos_to_tuple(self.anchor) 143 newLabel = x2a.xasyText(text=text, location=anchor, pen=None, 144 align=align, asyengine=None, fontsize=self.fontSize) 145 newLabel.asyfied = False 146 return newLabel 147 148 def forceFinalize(self): 149 self.mouseRelease() 150 151 152class AddBezierShape(InplaceObjProcess): 153 def __init__(self, parent=None): 154 super().__init__(parent) 155 self.asyengine = None 156 self.basePath = None 157 self.basePathPreview = None 158 self.closedPath = None 159 self.info = None 160 self.fill = False 161 self.opt = None 162 163 # list of "committed" points with Linkage information. 164 # Linkmode should be to the last point. 165 # (x, y, linkmode), (u, v, lm2) <==> (x, y) <=lm2=> (u, v) 166 self.pointsList = [] 167 self.currentPoint = Qc.QPointF(0, 0) 168 self.pendingPoint = None 169 self.useLegacy = False 170 171 def mouseDown(self, pos, info, mouseEvent: Qg.QMouseEvent=None): 172 x, y = PrimitiveShape.PrimitiveShape.pos_to_tuple(pos) 173 self.currentPoint.setX(x) 174 self.currentPoint.setY(y) 175 self.info = info 176 177 if not self._active: 178 self._active = True 179 self.fill = info['fill'] 180 self.asyengine = info['asyengine'] 181 self.closedPath = info['closedPath'] 182 self.useBezierBase = info['useBezier'] 183 self.useLegacy = self.info['options']['useLegacyDrawMode'] 184 self.pointsList.clear() 185 self.pointsList.append((x, y, None)) 186 else: 187 # see http://doc.qt.io/archives/qt-4.8/qt.html#MouseButton-enum 188 if (int(mouseEvent.buttons()) if mouseEvent is not None else 0) & 0x2 and self.useLegacy: 189 self.forceFinalize() 190 191 def _getLinkType(self): 192 if self.info['useBezier']: 193 return '..' 194 else: 195 return '--' 196 197 def mouseMove(self, pos, event): 198 # in postscript coords. 199 if self._active: 200 x, y = PrimitiveShape.PrimitiveShape.pos_to_tuple(pos) 201 202 if self.useLegacy or int(event.buttons()) != 0: 203 self.currentPoint.setX(x) 204 self.currentPoint.setY(y) 205 else: 206 self.forceFinalize() 207 208 209 def createOptWidget(self, info): 210 return None 211 # self.opt = Widg_addBezierInPlace.Widg_addBezierInplace(info) 212 # return self.opt 213 214 def finalizeClosure(self): 215 if self.active: 216 self.closedPath = True 217 self.forceFinalize() 218 219 def mouseRelease(self): 220 x, y = self.currentPoint.x(), self.currentPoint.y() 221 self.pointsList.append((x, y, self._getLinkType())) 222 # self.updateBasePath() 223 224 def updateBasePath(self): 225 self.basePath = x2a.asyPath(asyengine=self.asyengine, forceCurve=self.useBezierBase) 226 newNode = [(x, y) for x, y, _ in self.pointsList] 227 newLink = [lnk for *args, lnk in self.pointsList[1:]] 228 if self.useLegacy: 229 newNode += [(self.currentPoint.x(), self.currentPoint.y())] 230 newLink += [self._getLinkType()] 231 if self.closedPath: 232 newNode.append('cycle') 233 newLink.append(self._getLinkType()) 234 self.basePath.initFromNodeList(newNode, newLink) 235 236 if self.useBezierBase: 237 self.basePath.computeControls() 238 239 def updateBasePathPreview(self): 240 self.basePathPreview = x2a.asyPath( 241 asyengine=self.asyengine, forceCurve=self.useBezierBase) 242 newNode = [(x, y) for x, y, _ in self.pointsList] + [(self.currentPoint.x(), self.currentPoint.y())] 243 newLink = [lnk for *args, lnk in self.pointsList[1:]] + [self._getLinkType()] 244 if self.closedPath: 245 newNode.append('cycle') 246 newLink.append(self._getLinkType()) 247 self.basePathPreview.initFromNodeList(newNode, newLink) 248 249 if self.useBezierBase: 250 self.basePathPreview.computeControls() 251 252 def forceFinalize(self): 253 self.updateBasePath() 254 self._active = False 255 self.pointsList.clear() 256 self.objectCreated.emit(self.getXasyObject()) 257 self.basePath = None 258 259 def getObject(self): 260 if self.basePath is None: 261 raise RuntimeError('BasePath is None') 262 self.basePath.asyengine = self.asyengine 263 return self.basePath 264 265 def getPreview(self): 266 if self._active: 267 if self.pointsList: 268 self.updateBasePathPreview() 269 newPath = self.basePathPreview.toQPainterPath() 270 return newPath 271 272 def getXasyObject(self): 273 if self.fill: 274 return x2a.xasyFilledShape(self.getObject(), None) 275 else: 276 return x2a.xasyShape(self.getObject(), None) 277 278 279class AddPoly(InplaceObjProcess): 280 def __init__(self, parent=None): 281 super().__init__(parent) 282 self.center = Qc.QPointF(0, 0) 283 self.currPos = Qc.QPointF(0, 0) 284 self.sides = None 285 self.inscribed = None 286 self.centermode = None 287 self.asyengine = None 288 self.fill = None 289 self.opt = None 290 291 def mouseDown(self, pos, info, mouseEvent: Qg.QMouseEvent=None): 292 self._active = True 293 self.sides = info['sides'] 294 self.inscribed = info['inscribed'] 295 self.centermode = info['centermode'] 296 self.fill = info['fill'] 297 298 299 x, y = PrimitiveShape.PrimitiveShape.pos_to_tuple(pos) 300 self.center.setX(x) 301 self.center.setY(y) 302 self.currPos = Qc.QPointF(self.center) 303 304 def mouseMove(self, pos, event): 305 x, y = PrimitiveShape.PrimitiveShape.pos_to_tuple(pos) 306 self.currPos.setX(x) 307 self.currPos.setY(y) 308 309 def mouseRelease(self): 310 if self.active: 311 self.objectCreated.emit(self.getXasyObject()) 312 self._active = False 313 314 def forceFinalize(self): 315 self.mouseRelease() 316 317 def getObject(self): 318 if self.inscribed: 319 return PrimitiveShape.PrimitiveShape.inscribedRegPolygon(self.sides, self.center, self._rad(), 320 self._angle()) 321 else: 322 return PrimitiveShape.PrimitiveShape.exscribedRegPolygon(self.sides, self.center, self._rad(), 323 self._angle()) 324 325 def getPreview(self): 326 if self.inscribed: 327 poly = PrimitiveShape.PrimitiveShape.inscribedRegPolygon(self.sides, self.center, self._rad(), 328 self._angle(), qpoly=True) 329 else: 330 poly = PrimitiveShape.PrimitiveShape.exscribedRegPolygon(self.sides, self.center, self._rad(), 331 self._angle(), qpoly=True) 332 newPath = Qg.QPainterPath() 333 newPath.addPolygon(poly) 334 return newPath 335 336 def createOptWidget(self, info): 337 self.opt = Widg_addPolyOpt.Widg_addPolyOpt(info) 338 return self.opt 339 340 def _rad(self): 341 return PrimitiveShape.PrimitiveShape.euclideanNorm(self.currPos, self.center) 342 343 def _angle(self): 344 dist_x = self.currPos.x() - self.center.x() 345 dist_y = self.currPos.y() - self.center.y() 346 if dist_x == 0 and dist_y == 0: 347 return 0 348 else: 349 return math.atan2(dist_y, dist_x) 350 351 def getXasyObject(self): 352 if self.fill: 353 newObj = x2a.xasyFilledShape(self.getObject(), None) 354 else: 355 newObj = x2a.xasyShape(self.getObject(), None) 356 return newObj 357