canvas.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662
  1. from qtpy import QtCore
  2. from qtpy import QtGui
  3. from qtpy import QtWidgets
  4. from labelme.lib import distance
  5. from labelme import QT5
  6. from labelme.shape import Shape
  7. # TODO(unknown):
  8. # - [maybe] Find optimal epsilon value.
  9. CURSOR_DEFAULT = QtCore.Qt.ArrowCursor
  10. CURSOR_POINT = QtCore.Qt.PointingHandCursor
  11. CURSOR_DRAW = QtCore.Qt.CrossCursor
  12. CURSOR_MOVE = QtCore.Qt.ClosedHandCursor
  13. CURSOR_GRAB = QtCore.Qt.OpenHandCursor
  14. class Canvas(QtWidgets.QWidget):
  15. zoomRequest = QtCore.Signal(int, QtCore.QPoint)
  16. scrollRequest = QtCore.Signal(int, int)
  17. newShape = QtCore.Signal()
  18. selectionChanged = QtCore.Signal(bool)
  19. shapeMoved = QtCore.Signal()
  20. drawingPolygon = QtCore.Signal(bool)
  21. edgeSelected = QtCore.Signal(bool)
  22. CREATE, EDIT = 0, 1
  23. # polygon, rectangle
  24. createMode = 'polygon'
  25. def __init__(self, *args, **kwargs):
  26. self.epsilon = kwargs.pop('epsilon', 11.0)
  27. super(Canvas, self).__init__(*args, **kwargs)
  28. # Initialise local state.
  29. self.mode = self.EDIT
  30. self.shapes = []
  31. self.shapesBackups = []
  32. self.current = None
  33. self.selectedShape = None # save the selected shape here
  34. self.selectedShapeCopy = None
  35. self.lineColor = QtGui.QColor(0, 0, 255)
  36. # self.line represents
  37. # if createMode == 'polygon': edge from last point to current
  38. # if createMode == 'rectangle': the rectangle
  39. self.line = Shape(line_color=self.lineColor)
  40. self.prevPoint = QtCore.QPoint()
  41. self.prevMovePoint = QtCore.QPoint()
  42. self.offsets = QtCore.QPoint(), QtCore.QPoint()
  43. self.scale = 1.0
  44. self.pixmap = QtGui.QPixmap()
  45. self.visible = {}
  46. self._hideBackround = False
  47. self.hideBackround = False
  48. self.hShape = None
  49. self.hVertex = None
  50. self.hEdge = None
  51. self.movingShape = False
  52. self._painter = QtGui.QPainter()
  53. self._cursor = CURSOR_DEFAULT
  54. # Menus:
  55. self.menus = (QtWidgets.QMenu(), QtWidgets.QMenu())
  56. # Set widget options.
  57. self.setMouseTracking(True)
  58. self.setFocusPolicy(QtCore.Qt.WheelFocus)
  59. def storeShapes(self):
  60. shapesBackup = []
  61. for shape in self.shapes:
  62. shapesBackup.append(shape.copy())
  63. if len(self.shapesBackups) >= 10:
  64. self.shapesBackups = self.shapesBackups[-9:]
  65. self.shapesBackups.append(shapesBackup)
  66. @property
  67. def isShapeRestorable(self):
  68. if len(self.shapesBackups) < 2:
  69. return False
  70. return True
  71. def restoreShape(self):
  72. if not self.isShapeRestorable:
  73. return
  74. self.shapesBackups.pop() # latest
  75. shapesBackup = self.shapesBackups.pop()
  76. self.shapes = shapesBackup
  77. self.storeShapes()
  78. self.repaint()
  79. def enterEvent(self, ev):
  80. self.overrideCursor(self._cursor)
  81. def leaveEvent(self, ev):
  82. self.restoreCursor()
  83. def focusOutEvent(self, ev):
  84. self.restoreCursor()
  85. def isVisible(self, shape):
  86. return self.visible.get(shape, True)
  87. def drawing(self):
  88. return self.mode == self.CREATE
  89. def editing(self):
  90. return self.mode == self.EDIT
  91. def setEditing(self, value=True):
  92. self.mode = self.EDIT if value else self.CREATE
  93. if not value: # Create
  94. self.unHighlight()
  95. self.deSelectShape()
  96. def unHighlight(self):
  97. if self.hShape:
  98. self.hShape.highlightClear()
  99. self.hVertex = self.hShape = None
  100. def selectedVertex(self):
  101. return self.hVertex is not None
  102. def mouseMoveEvent(self, ev):
  103. """Update line with last point and current coordinates."""
  104. if QT5:
  105. pos = self.transformPos(ev.pos())
  106. else:
  107. pos = self.transformPos(ev.posF())
  108. self.prevMovePoint = pos
  109. self.restoreCursor()
  110. # Polygon drawing.
  111. if self.drawing():
  112. self.overrideCursor(CURSOR_DRAW)
  113. if not self.current:
  114. return
  115. color = self.lineColor
  116. if self.outOfPixmap(pos):
  117. # Don't allow the user to draw outside the pixmap.
  118. # Project the point to the pixmap's edges.
  119. pos = self.intersectionPoint(self.current[-1], pos)
  120. elif len(self.current) > 1 and \
  121. self.closeEnough(pos, self.current[0]):
  122. # Attract line to starting point and
  123. # colorise to alert the user.
  124. pos = self.current[0]
  125. color = self.current.line_color
  126. self.overrideCursor(CURSOR_POINT)
  127. self.current.highlightVertex(0, Shape.NEAR_VERTEX)
  128. if self.createMode == 'polygon':
  129. self.line[0] = self.current[-1]
  130. self.line[1] = pos
  131. elif self.createMode == 'rectangle':
  132. self.line.points = list(self.getRectangleFromLine(
  133. (self.current[0], pos)
  134. ))
  135. self.line.close()
  136. else:
  137. raise ValueError
  138. self.line.line_color = color
  139. self.repaint()
  140. self.current.highlightClear()
  141. return
  142. # Polygon copy moving.
  143. if QtCore.Qt.RightButton & ev.buttons():
  144. if self.selectedShapeCopy and self.prevPoint:
  145. self.overrideCursor(CURSOR_MOVE)
  146. self.boundedMoveShape(self.selectedShapeCopy, pos)
  147. self.repaint()
  148. elif self.selectedShape:
  149. self.selectedShapeCopy = self.selectedShape.copy()
  150. self.repaint()
  151. return
  152. # Polygon/Vertex moving.
  153. self.movingShape = False
  154. if QtCore.Qt.LeftButton & ev.buttons():
  155. if self.selectedVertex():
  156. self.boundedMoveVertex(pos)
  157. self.repaint()
  158. self.movingShape = True
  159. elif self.selectedShape and self.prevPoint:
  160. self.overrideCursor(CURSOR_MOVE)
  161. self.boundedMoveShape(self.selectedShape, pos)
  162. self.repaint()
  163. self.movingShape = True
  164. return
  165. # Just hovering over the canvas, 2 posibilities:
  166. # - Highlight shapes
  167. # - Highlight vertex
  168. # Update shape/vertex fill and tooltip value accordingly.
  169. self.setToolTip("Image")
  170. for shape in reversed([s for s in self.shapes if self.isVisible(s)]):
  171. # Look for a nearby vertex to highlight. If that fails,
  172. # check if we happen to be inside a shape.
  173. index = shape.nearestVertex(pos, self.epsilon)
  174. index_edge = shape.nearestEdge(pos, self.epsilon)
  175. if index is not None:
  176. if self.selectedVertex():
  177. self.hShape.highlightClear()
  178. self.hVertex = index
  179. self.hShape = shape
  180. self.hEdge = index_edge
  181. shape.highlightVertex(index, shape.MOVE_VERTEX)
  182. self.overrideCursor(CURSOR_POINT)
  183. self.setToolTip("Click & drag to move point")
  184. self.setStatusTip(self.toolTip())
  185. self.update()
  186. break
  187. elif shape.containsPoint(pos):
  188. if self.selectedVertex():
  189. self.hShape.highlightClear()
  190. self.hVertex = None
  191. self.hShape = shape
  192. self.hEdge = index_edge
  193. self.setToolTip(
  194. "Click & drag to move shape '%s'" % shape.label)
  195. self.setStatusTip(self.toolTip())
  196. self.overrideCursor(CURSOR_GRAB)
  197. self.update()
  198. break
  199. else: # Nothing found, clear highlights, reset state.
  200. if self.hShape:
  201. self.hShape.highlightClear()
  202. self.update()
  203. self.hVertex, self.hShape, self.hEdge = None, None, None
  204. self.edgeSelected.emit(self.hEdge is not None)
  205. def addPointToEdge(self):
  206. if (self.hShape is None and
  207. self.hEdge is None and
  208. self.prevMovePoint is None):
  209. return
  210. shape = self.hShape
  211. index = self.hEdge
  212. point = self.prevMovePoint
  213. shape.insertPoint(index, point)
  214. shape.highlightVertex(index, shape.MOVE_VERTEX)
  215. self.hShape = shape
  216. self.hVertex = index
  217. self.hEdge = None
  218. def getRectangleFromLine(self, line):
  219. pt1 = line[0]
  220. pt3 = line[1]
  221. pt2 = QtCore.QPoint(pt3.x(), pt1.y())
  222. pt4 = QtCore.QPoint(pt1.x(), pt3.y())
  223. return pt1, pt2, pt3, pt4
  224. def mousePressEvent(self, ev):
  225. if QT5:
  226. pos = self.transformPos(ev.pos())
  227. else:
  228. pos = self.transformPos(ev.posF())
  229. if ev.button() == QtCore.Qt.LeftButton:
  230. if self.drawing():
  231. if self.current:
  232. # Add point to existing shape.
  233. if self.createMode == 'polygon':
  234. self.current.addPoint(self.line[1])
  235. self.line[0] = self.current[-1]
  236. if self.current.isClosed():
  237. self.finalise()
  238. elif self.createMode == 'rectangle':
  239. assert len(self.current.points) == 1
  240. self.current.points = self.line.points
  241. self.finalise()
  242. else:
  243. raise ValueError
  244. elif not self.outOfPixmap(pos):
  245. # Create new shape.
  246. self.current = Shape()
  247. self.current.addPoint(pos)
  248. self.line.points = [pos, pos]
  249. self.setHiding()
  250. self.drawingPolygon.emit(True)
  251. self.update()
  252. else:
  253. self.selectShapePoint(pos)
  254. self.prevPoint = pos
  255. self.repaint()
  256. elif ev.button() == QtCore.Qt.RightButton and self.editing():
  257. self.selectShapePoint(pos)
  258. self.prevPoint = pos
  259. self.repaint()
  260. def mouseReleaseEvent(self, ev):
  261. if ev.button() == QtCore.Qt.RightButton:
  262. menu = self.menus[bool(self.selectedShapeCopy)]
  263. self.restoreCursor()
  264. if not menu.exec_(self.mapToGlobal(ev.pos()))\
  265. and self.selectedShapeCopy:
  266. # Cancel the move by deleting the shadow copy.
  267. self.selectedShapeCopy = None
  268. self.repaint()
  269. elif ev.button() == QtCore.Qt.LeftButton and self.selectedShape:
  270. self.overrideCursor(CURSOR_GRAB)
  271. if self.movingShape:
  272. self.storeShapes()
  273. self.shapeMoved.emit()
  274. def endMove(self, copy=False):
  275. assert self.selectedShape and self.selectedShapeCopy
  276. shape = self.selectedShapeCopy
  277. # del shape.fill_color
  278. # del shape.line_color
  279. if copy:
  280. self.shapes.append(shape)
  281. self.selectedShape.selected = False
  282. self.selectedShape = shape
  283. self.repaint()
  284. else:
  285. shape.label = self.selectedShape.label
  286. self.deleteSelected()
  287. self.shapes.append(shape)
  288. self.storeShapes()
  289. self.selectedShapeCopy = None
  290. def hideBackroundShapes(self, value):
  291. self.hideBackround = value
  292. if self.selectedShape:
  293. # Only hide other shapes if there is a current selection.
  294. # Otherwise the user will not be able to select a shape.
  295. self.setHiding(True)
  296. self.repaint()
  297. def setHiding(self, enable=True):
  298. self._hideBackround = self.hideBackround if enable else False
  299. def canCloseShape(self):
  300. return self.drawing() and self.current and len(self.current) > 2
  301. def mouseDoubleClickEvent(self, ev):
  302. # We need at least 4 points here, since the mousePress handler
  303. # adds an extra one before this handler is called.
  304. if self.canCloseShape() and len(self.current) > 3:
  305. self.current.popPoint()
  306. self.finalise()
  307. def selectShape(self, shape):
  308. self.deSelectShape()
  309. shape.selected = True
  310. self.selectedShape = shape
  311. self.setHiding()
  312. self.selectionChanged.emit(True)
  313. self.update()
  314. def selectShapePoint(self, point):
  315. """Select the first shape created which contains this point."""
  316. self.deSelectShape()
  317. if self.selectedVertex(): # A vertex is marked for selection.
  318. index, shape = self.hVertex, self.hShape
  319. shape.highlightVertex(index, shape.MOVE_VERTEX)
  320. return
  321. for shape in reversed(self.shapes):
  322. if self.isVisible(shape) and shape.containsPoint(point):
  323. shape.selected = True
  324. self.selectedShape = shape
  325. self.calculateOffsets(shape, point)
  326. self.setHiding()
  327. self.selectionChanged.emit(True)
  328. return
  329. def calculateOffsets(self, shape, point):
  330. rect = shape.boundingRect()
  331. x1 = rect.x() - point.x()
  332. y1 = rect.y() - point.y()
  333. x2 = (rect.x() + rect.width()) - point.x()
  334. y2 = (rect.y() + rect.height()) - point.y()
  335. self.offsets = QtCore.QPoint(x1, y1), QtCore.QPoint(x2, y2)
  336. def boundedMoveVertex(self, pos):
  337. index, shape = self.hVertex, self.hShape
  338. point = shape[index]
  339. if self.outOfPixmap(pos):
  340. pos = self.intersectionPoint(point, pos)
  341. shape.moveVertexBy(index, pos - point)
  342. def boundedMoveShape(self, shape, pos):
  343. if self.outOfPixmap(pos):
  344. return False # No need to move
  345. o1 = pos + self.offsets[0]
  346. if self.outOfPixmap(o1):
  347. pos -= QtCore.QPoint(min(0, o1.x()), min(0, o1.y()))
  348. o2 = pos + self.offsets[1]
  349. if self.outOfPixmap(o2):
  350. pos += QtCore.QPoint(min(0, self.pixmap.width() - o2.x()),
  351. min(0, self.pixmap.height() - o2.y()))
  352. # XXX: The next line tracks the new position of the cursor
  353. # relative to the shape, but also results in making it
  354. # a bit "shaky" when nearing the border and allows it to
  355. # go outside of the shape's area for some reason.
  356. # self.calculateOffsets(self.selectedShape, pos)
  357. dp = pos - self.prevPoint
  358. if dp:
  359. shape.moveBy(dp)
  360. self.prevPoint = pos
  361. return True
  362. return False
  363. def deSelectShape(self):
  364. if self.selectedShape:
  365. self.selectedShape.selected = False
  366. self.selectedShape = None
  367. self.setHiding(False)
  368. self.selectionChanged.emit(False)
  369. self.update()
  370. def deleteSelected(self):
  371. if self.selectedShape:
  372. shape = self.selectedShape
  373. self.shapes.remove(self.selectedShape)
  374. self.storeShapes()
  375. self.selectedShape = None
  376. self.update()
  377. return shape
  378. def copySelectedShape(self):
  379. if self.selectedShape:
  380. shape = self.selectedShape.copy()
  381. self.deSelectShape()
  382. self.shapes.append(shape)
  383. self.storeShapes()
  384. shape.selected = True
  385. self.selectedShape = shape
  386. self.boundedShiftShape(shape)
  387. return shape
  388. def boundedShiftShape(self, shape):
  389. # Try to move in one direction, and if it fails in another.
  390. # Give up if both fail.
  391. point = shape[0]
  392. offset = QtCore.QPoint(2.0, 2.0)
  393. self.calculateOffsets(shape, point)
  394. self.prevPoint = point
  395. if not self.boundedMoveShape(shape, point - offset):
  396. self.boundedMoveShape(shape, point + offset)
  397. def paintEvent(self, event):
  398. if not self.pixmap:
  399. return super(Canvas, self).paintEvent(event)
  400. p = self._painter
  401. p.begin(self)
  402. p.setRenderHint(QtGui.QPainter.Antialiasing)
  403. p.setRenderHint(QtGui.QPainter.HighQualityAntialiasing)
  404. p.setRenderHint(QtGui.QPainter.SmoothPixmapTransform)
  405. p.scale(self.scale, self.scale)
  406. p.translate(self.offsetToCenter())
  407. p.drawPixmap(0, 0, self.pixmap)
  408. Shape.scale = self.scale
  409. for shape in self.shapes:
  410. if (shape.selected or not self._hideBackround) and \
  411. self.isVisible(shape):
  412. shape.fill = shape.selected or shape == self.hShape
  413. shape.paint(p)
  414. if self.current:
  415. self.current.paint(p)
  416. self.line.paint(p)
  417. if self.selectedShapeCopy:
  418. self.selectedShapeCopy.paint(p)
  419. p.end()
  420. def transformPos(self, point):
  421. """Convert from widget-logical coordinates to painter-logical ones."""
  422. return point / self.scale - self.offsetToCenter()
  423. def offsetToCenter(self):
  424. s = self.scale
  425. area = super(Canvas, self).size()
  426. w, h = self.pixmap.width() * s, self.pixmap.height() * s
  427. aw, ah = area.width(), area.height()
  428. x = (aw - w) / (2 * s) if aw > w else 0
  429. y = (ah - h) / (2 * s) if ah > h else 0
  430. return QtCore.QPoint(x, y)
  431. def outOfPixmap(self, p):
  432. w, h = self.pixmap.width(), self.pixmap.height()
  433. return not (0 <= p.x() <= w and 0 <= p.y() <= h)
  434. def finalise(self):
  435. assert self.current
  436. self.current.close()
  437. self.shapes.append(self.current)
  438. self.storeShapes()
  439. self.current = None
  440. self.setHiding(False)
  441. self.newShape.emit()
  442. self.update()
  443. def closeEnough(self, p1, p2):
  444. # d = distance(p1 - p2)
  445. # m = (p1-p2).manhattanLength()
  446. # print "d %.2f, m %d, %.2f" % (d, m, d - m)
  447. return distance(p1 - p2) < self.epsilon
  448. def intersectionPoint(self, p1, p2):
  449. # Cycle through each image edge in clockwise fashion,
  450. # and find the one intersecting the current line segment.
  451. # http://paulbourke.net/geometry/lineline2d/
  452. size = self.pixmap.size()
  453. points = [(0, 0),
  454. (size.width(), 0),
  455. (size.width(), size.height()),
  456. (0, size.height())]
  457. x1, y1 = p1.x(), p1.y()
  458. x2, y2 = p2.x(), p2.y()
  459. d, i, (x, y) = min(self.intersectingEdges((x1, y1), (x2, y2), points))
  460. x3, y3 = points[i]
  461. x4, y4 = points[(i + 1) % 4]
  462. if (x, y) == (x1, y1):
  463. # Handle cases where previous point is on one of the edges.
  464. if x3 == x4:
  465. return QtCore.QPoint(x3, min(max(0, y2), max(y3, y4)))
  466. else: # y3 == y4
  467. return QtCore.QPoint(min(max(0, x2), max(x3, x4)), y3)
  468. return QtCore.QPoint(x, y)
  469. def intersectingEdges(self, point1, point2, points):
  470. """Find intersecting edges.
  471. For each edge formed by `points', yield the intersection
  472. with the line segment `(x1,y1) - (x2,y2)`, if it exists.
  473. Also return the distance of `(x2,y2)' to the middle of the
  474. edge along with its index, so that the one closest can be chosen.
  475. """
  476. (x1, y1) = point1
  477. (x2, y2) = point2
  478. for i in range(4):
  479. x3, y3 = points[i]
  480. x4, y4 = points[(i + 1) % 4]
  481. denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1)
  482. nua = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)
  483. nub = (x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)
  484. if denom == 0:
  485. # This covers two cases:
  486. # nua == nub == 0: Coincident
  487. # otherwise: Parallel
  488. continue
  489. ua, ub = nua / denom, nub / denom
  490. if 0 <= ua <= 1 and 0 <= ub <= 1:
  491. x = x1 + ua * (x2 - x1)
  492. y = y1 + ua * (y2 - y1)
  493. m = QtCore.QPoint((x3 + x4) / 2, (y3 + y4) / 2)
  494. d = distance(m - QtCore.QPoint(x2, y2))
  495. yield d, i, (x, y)
  496. # These two, along with a call to adjustSize are required for the
  497. # scroll area.
  498. def sizeHint(self):
  499. return self.minimumSizeHint()
  500. def minimumSizeHint(self):
  501. if self.pixmap:
  502. return self.scale * self.pixmap.size()
  503. return super(Canvas, self).minimumSizeHint()
  504. def wheelEvent(self, ev):
  505. if QT5:
  506. mods = ev.modifiers()
  507. delta = ev.angleDelta()
  508. if QtCore.Qt.ControlModifier == int(mods):
  509. # with Ctrl/Command key
  510. # zoom
  511. self.zoomRequest.emit(delta.y(), ev.pos())
  512. else:
  513. # scroll
  514. self.scrollRequest.emit(delta.x(), QtCore.Qt.Horizontal)
  515. self.scrollRequest.emit(delta.y(), QtCore.Qt.Vertical)
  516. else:
  517. if ev.orientation() == QtCore.Qt.Vertical:
  518. mods = ev.modifiers()
  519. if QtCore.Qt.ControlModifier == int(mods):
  520. # with Ctrl/Command key
  521. self.zoomRequest.emit(ev.delta(), ev.pos())
  522. else:
  523. self.scrollRequest.emit(
  524. ev.delta(),
  525. QtCore.Qt.Horizontal
  526. if (QtCore.Qt.ShiftModifier == int(mods))
  527. else QtCore.Qt.Vertical)
  528. else:
  529. self.scrollRequest.emit(ev.delta(), QtCore.Qt.Horizontal)
  530. ev.accept()
  531. def keyPressEvent(self, ev):
  532. key = ev.key()
  533. if key == QtCore.Qt.Key_Escape and self.current:
  534. self.current = None
  535. self.drawingPolygon.emit(False)
  536. self.update()
  537. elif key == QtCore.Qt.Key_Return and self.canCloseShape():
  538. self.finalise()
  539. def setLastLabel(self, text):
  540. assert text
  541. self.shapes[-1].label = text
  542. self.shapesBackups.pop()
  543. self.storeShapes()
  544. return self.shapes[-1]
  545. def undoLastLine(self):
  546. assert self.shapes
  547. self.current = self.shapes.pop()
  548. self.current.setOpen()
  549. if self.createMode == 'polygon':
  550. self.line.points = [self.current[-1], self.current[0]]
  551. elif self.createMode == 'rectangle':
  552. self.current.points = self.current.points[0:1]
  553. else:
  554. raise ValueError
  555. self.drawingPolygon.emit(True)
  556. def undoLastPoint(self):
  557. if not self.current or self.current.isClosed():
  558. return
  559. self.current.popPoint()
  560. if len(self.current) > 0:
  561. self.line[0] = self.current[-1]
  562. else:
  563. self.current = None
  564. self.drawingPolygon.emit(False)
  565. self.repaint()
  566. def loadPixmap(self, pixmap):
  567. self.pixmap = pixmap
  568. self.shapes = []
  569. self.repaint()
  570. def loadShapes(self, shapes):
  571. self.shapes = list(shapes)
  572. self.storeShapes()
  573. self.current = None
  574. self.repaint()
  575. def setShapeVisible(self, shape, value):
  576. self.visible[shape] = value
  577. self.repaint()
  578. def overrideCursor(self, cursor):
  579. self.restoreCursor()
  580. self._cursor = cursor
  581. QtWidgets.QApplication.setOverrideCursor(cursor)
  582. def restoreCursor(self):
  583. QtWidgets.QApplication.restoreOverrideCursor()
  584. def resetState(self):
  585. self.restoreCursor()
  586. self.pixmap = None
  587. self.shapesBackups = []
  588. self.update()