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