hytos / HYTOS / HYTOS / QtImageViewer.py @ e8f57e0a
이력 | 보기 | 이력해설 | 다운로드 (24.7 KB)
1 |
# coding: utf-8
|
---|---|
2 |
import sys |
3 |
import os.path |
4 |
try:
|
5 |
from PyQt5.QtCore import * |
6 |
from PyQt5.QtGui import * |
7 |
from PyQt5.QtWidgets import * |
8 |
except ImportError: |
9 |
try:
|
10 |
from PyQt4.QtCore import * |
11 |
from PyQt4.QtGui import * |
12 |
except ImportError: |
13 |
raise ImportError("ImageViewerQt: Requires PyQt5 or PyQt4.") |
14 |
|
15 |
sys.path.insert(0, os.path.dirname(os.path.realpath(__file__)) + '\\Commands') |
16 |
import DefaultCommand |
17 |
|
18 |
sys.path.insert(0, os.path.dirname(os.path.realpath(__file__)) + '\\Shapes') |
19 |
from SymbolSvgItem import SymbolSvgItem |
20 |
|
21 |
__author__ = "Marcel Goldschen-Ohm <marcel.goldschen@gmail.com>"
|
22 |
__version__ = '0.9.0'
|
23 |
|
24 |
|
25 |
class QtImageViewer(QGraphicsView): |
26 |
""" PyQt image viewer widget for a QPixmap in a QGraphicsView scene with mouse zooming and panning.
|
27 |
Displays a QImage or QPixmap (QImage is internally converted to a QPixmap).
|
28 |
To display any other image format, you must first convert it to a QImage or QPixmap.
|
29 |
Some useful image format conversion utilities:
|
30 |
qimage2ndarray: NumPy ndarray <==> QImage (https://github.com/hmeine/qimage2ndarray)
|
31 |
ImageQt: PIL Image <==> QImage (https://github.com/python-pillow/Pillow/blob/master/PIL/ImageQt.py)
|
32 |
Mouse interaction:
|
33 |
Left mouse button drag: Pan image.
|
34 |
Right mouse button drag: Zoom box.
|
35 |
Right mouse button doubleclick: Zoom to show entire image.
|
36 |
"""
|
37 |
|
38 |
# Mouse button signals emit image scene (x, y) coordinates.
|
39 |
# !!! For image (row, column) matrix indexing, row = y and column = x.
|
40 |
leftMouseButtonPressed = pyqtSignal(float, float) |
41 |
rightMouseButtonPressed = pyqtSignal(float, float) |
42 |
leftMouseButtonMoved = pyqtSignal(float, float) |
43 |
rightMouseButtonMoved = pyqtSignal(float, float) |
44 |
leftMouseButtonReleased = pyqtSignal(float, float) |
45 |
rightMouseButtonReleased = pyqtSignal(float, float) |
46 |
leftMouseButtonDoubleClicked = pyqtSignal(float, float) |
47 |
rightMouseButtonDoubleClicked = pyqtSignal(float, float) |
48 |
#itemRemoved = pyqtSignal(QGraphicsItem)
|
49 |
startPointChanged = pyqtSignal(float, float) |
50 |
|
51 |
'''
|
52 |
@history 2018.06.27 Jeongwoo Change zoom rule (Qt.KeepAspectRatioByExpanding → Qt.KeepAspectRatio)
|
53 |
'''
|
54 |
def __init__(self, mainWindow = None): |
55 |
from QtImageViewerScene import QtImageViewerScene |
56 |
|
57 |
QGraphicsView.__init__(self)
|
58 |
|
59 |
self.mainWindow = mainWindow
|
60 |
# Image is displayed as a QPixmap in a QGraphicsScene attached to this QGraphicsView.
|
61 |
self.command = None |
62 |
self.scene = QtImageViewerScene(mainWindow)
|
63 |
self.setScene(self.scene) |
64 |
self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse)
|
65 |
|
66 |
self.scaleFactor = 1.0 |
67 |
self.numScheduledScalings = 0 |
68 |
self.isOriginalPointSelected = False |
69 |
|
70 |
# Store a local handle to the scene's current image pixmap.
|
71 |
self._pixmapHandle = None |
72 |
|
73 |
# Image aspect ratio mode.
|
74 |
# !!! ONLY applies to full image. Aspect ratio is always ignored when zooming.
|
75 |
# Qt.IgnoreAspectRatio: Scale image to fit viewport.
|
76 |
# Qt.KeepAspectRatio: Scale image to fit inside viewport, preserving aspect ratio.
|
77 |
# Qt.KeepAspectRatioByExpanding: Scale image to fill the viewport, preserving aspect ratio.
|
78 |
self.aspectRatioMode = Qt.KeepAspectRatio
|
79 |
|
80 |
# Scroll bar behaviour.
|
81 |
# Qt.ScrollBarAlwaysOff: Never shows a scroll bar.
|
82 |
# Qt.ScrollBarAlwaysOn: Always shows a scroll bar.
|
83 |
# Qt.ScrollBarAsNeeded: Shows a scroll bar only when zoomed.
|
84 |
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
85 |
self.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
86 |
|
87 |
# Stack of QRectF zoom boxes in scene coordinates.
|
88 |
self.zoomStack = []
|
89 |
|
90 |
self.setRenderHint(QPainter.Antialiasing)
|
91 |
|
92 |
self.setAcceptDrops(True) # enable drop |
93 |
|
94 |
# Flags for enabling/disabling mouse interaction.
|
95 |
self.canZoom = True |
96 |
self.canPan = True |
97 |
self.setMouseTracking(True) |
98 |
self.command = None |
99 |
|
100 |
self.guidesEnabled = False |
101 |
self._guidePen = QPen()
|
102 |
self._guidePen.setColor(QColor(180, 180, 180)) |
103 |
self._guidePen.setStyle(Qt.DashLine)
|
104 |
self._guidePen.setWidthF(0.3) |
105 |
|
106 |
# set currentAttribute
|
107 |
self.currentAttribute = '' |
108 |
|
109 |
'''
|
110 |
@brief Return Pixmap Handler
|
111 |
@author Jeongwoo
|
112 |
@date 2018.06.11
|
113 |
'''
|
114 |
def getPixmapHandle(self): |
115 |
return self._pixmapHandle |
116 |
|
117 |
'''
|
118 |
@brief Use Default ImageViewer Command
|
119 |
@author Jeongwoo
|
120 |
@date 18.04.10
|
121 |
@history .
|
122 |
'''
|
123 |
def useDefaultCommand(self): |
124 |
""" Use Default Command """
|
125 |
if self.command is not None: |
126 |
self.command.reset()
|
127 |
|
128 |
self.command = DefaultCommand.DefaultCommand(self) |
129 |
|
130 |
def hasImage(self): |
131 |
""" Returns whether or not the scene contains an image pixmap.
|
132 |
"""
|
133 |
return self._pixmapHandle is not None |
134 |
|
135 |
def clearImage(self): |
136 |
""" Removes the current image pixmap from the scene if it exists.
|
137 |
"""
|
138 |
if self.hasImage(): |
139 |
self.scene.removeItem(self._pixmapHandle) |
140 |
self._pixmapHandle = None |
141 |
|
142 |
def pixmap(self): |
143 |
""" Returns the scene's current image pixmap as a QPixmap, or else None if no image exists.
|
144 |
:rtype: QPixmap | None
|
145 |
"""
|
146 |
if self.hasImage(): |
147 |
return self._pixmapHandle.pixmap() |
148 |
return None |
149 |
|
150 |
def image(self): |
151 |
""" Returns the scene's current image pixmap as a QImage, or else None if no image exists.
|
152 |
:rtype: QImage | None
|
153 |
"""
|
154 |
if self.hasImage(): |
155 |
return self._pixmapHandle.pixmap().toImage() |
156 |
return None |
157 |
|
158 |
def setImage(self, image): |
159 |
""" Set the scene's current image pixmap to the input QImage or QPixmap.
|
160 |
Raises a RuntimeError if the input image has type other than QImage or QPixmap.
|
161 |
:type image: QImage | QPixmap
|
162 |
"""
|
163 |
try:
|
164 |
if type(image) is QPixmap: |
165 |
pixmap = image |
166 |
elif type(image) is QImage: |
167 |
pixmap = QPixmap.fromImage(image) |
168 |
else:
|
169 |
raise RuntimeError("ImageViewer.setImage: Argument must be a QImage or QPixmap.") |
170 |
|
171 |
self.clearImage()
|
172 |
self.scene.clear()
|
173 |
|
174 |
if self.hasImage(): |
175 |
self._pixmapHandle.setPixmap(pixmap)
|
176 |
else:
|
177 |
self._pixmapHandle = self.scene.addPixmap(pixmap) |
178 |
self._pixmapHandle.setFlags(QGraphicsItem.ItemClipsChildrenToShape)
|
179 |
|
180 |
self.setSceneRect(QRectF(pixmap.rect())) # Set scene size to image size. |
181 |
self.updateViewer()
|
182 |
except Exception as ex: |
183 |
from App import App |
184 |
from AppDocData import MessageType |
185 |
|
186 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno) |
187 |
App.mainWnd().addMessage.emit(MessageType.Error, message) |
188 |
|
189 |
'''
|
190 |
@brief open a image file selected by user
|
191 |
@author
|
192 |
@date
|
193 |
'''
|
194 |
def loadImageFromFile(self, folder='', fileName=""): |
195 |
""" Load an image from file.
|
196 |
Without any arguments, loadImageFromFile() will popup a file dialog to choose the image file.
|
197 |
With a fileName argument, loadImageFromFile(fileName) will attempt to load the specified image file directly.
|
198 |
"""
|
199 |
try:
|
200 |
if len(fileName) == 0: |
201 |
options = QFileDialog.Options() |
202 |
options |= QFileDialog.DontUseNativeDialog |
203 |
if QT_VERSION_STR[0] == '4': |
204 |
fileName = QFileDialog.getOpenFileName(self, "Open image file", os.getcwd() if folder == '' else folder, "Image files(*.png *.jpg)", options=options) |
205 |
elif QT_VERSION_STR[0] == '5': |
206 |
fileName, dummy = QFileDialog.getOpenFileName(self, "Open image file", os.getcwd() if folder == '' else folder, "Image files(*.png *.jpg)", options=options) |
207 |
if len(fileName) and os.path.isfile(fileName): |
208 |
image = QImage(fileName, format = None)
|
209 |
self.setImage(image)
|
210 |
except Exception as ex: |
211 |
from App import App |
212 |
from AppDocData import MessageType |
213 |
|
214 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno) |
215 |
App.mainWnd().addMessage.emit(MessageType.Error, message) |
216 |
|
217 |
return fileName
|
218 |
|
219 |
def zoom_rect(self, rect: QRectF) -> None: |
220 |
"""zoom window within given rectangle"""
|
221 |
self.fitInView(rect, Qt.KeepAspectRatio)
|
222 |
if not self.zoomStack: |
223 |
self.zoomStack = []
|
224 |
|
225 |
self.zoomStack.append(rect)
|
226 |
|
227 |
def updateViewer(self, zoomNewRect=None): |
228 |
"""Show current zoom (if showing entire image, apply current aspect ratio mode)."""
|
229 |
if self.zoomStack: |
230 |
if zoomNewRect is None: |
231 |
self.fitInView(self.zoomStack[-1], Qt.KeepAspectRatio) # Show zoomed rect (ignore aspect ratio). |
232 |
else:
|
233 |
self.fitInView(zoomNewRect, Qt.KeepAspectRatio)
|
234 |
else:
|
235 |
self.zoomStack = [] # Clear the zoom stack (in case we got here because of an invalid zoom). |
236 |
self.fitInView(self.sceneRect(), self.aspectRatioMode) # Show entire image (use current aspect ratio mode). |
237 |
|
238 |
def zoomImageInit(self): |
239 |
self.zoomStack = []
|
240 |
self.updateViewer()
|
241 |
self.setCursor(QCursor(Qt.ArrowCursor))
|
242 |
|
243 |
'''
|
244 |
@brief Zoom in & out image
|
245 |
@author Jeongwoo
|
246 |
@date -
|
247 |
@history 18.04.11 Jeongwoo add parameter 'adjust' (@ref ResultTreeWidget.itemClickEvent(self, item, columnNo))
|
248 |
'''
|
249 |
def zoomImage(self, isZoomIn, event, adjust = 1): |
250 |
"""Zoom in & out"""
|
251 |
|
252 |
HALF_SIZE = 300
|
253 |
clickPos = event.pos() |
254 |
left_top = self.mapToScene(clickPos.x() - HALF_SIZE//adjust, clickPos.y() - HALF_SIZE//adjust)
|
255 |
right_bottom = self.mapToScene(clickPos.x() + HALF_SIZE//adjust, clickPos.y() + HALF_SIZE//adjust)
|
256 |
if isZoomIn:
|
257 |
zoomArea = QRectF(left_top, right_bottom) |
258 |
viewBBox = self.zoomStack[-1] if self.zoomStack else self.sceneRect() |
259 |
selectionBBox = zoomArea.intersected(viewBBox) |
260 |
self.scene.setSelectionArea(QPainterPath()) # Clear current selection area. |
261 |
if selectionBBox.isValid() and (selectionBBox != viewBBox): |
262 |
self.zoomStack.append(selectionBBox)
|
263 |
self.updateViewer()
|
264 |
else:
|
265 |
zoomNewRect = None
|
266 |
self.scene.setSelectionArea(QPainterPath()) # Clear current selection area. |
267 |
if self.zoomStack: |
268 |
zoomNewRect = self.zoomStack.pop()
|
269 |
self.updateViewer(zoomNewRect)
|
270 |
|
271 |
"""
|
272 |
def resizeEvent(self, event):
|
273 |
Maintain current zoom on resize.
|
274 |
self.updateViewer()
|
275 |
"""
|
276 |
|
277 |
'''
|
278 |
@brief mouse move event
|
279 |
'''
|
280 |
def mouseMoveEvent(self, event): |
281 |
try:
|
282 |
scenePos = self.mapToScene(event.pos())
|
283 |
if self.command is not None: |
284 |
self.command.execute(['mouseMoveEvent', event, scenePos]) |
285 |
if self.command.name == "SelectAttribute": |
286 |
QGraphicsView.mouseMoveEvent(self, event)
|
287 |
if self.command.isTreated == True: return |
288 |
except Exception as ex: |
289 |
from App import App |
290 |
from AppDocData import MessageType |
291 |
|
292 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno) |
293 |
App.mainWnd().addMessage.emit(MessageType.Error, message) |
294 |
|
295 |
QGraphicsView.mouseMoveEvent(self, event)
|
296 |
|
297 |
'''
|
298 |
@brief
|
299 |
@author
|
300 |
@date
|
301 |
@history block clear selection when right mouse button is clicked
|
302 |
'''
|
303 |
def mousePressEvent(self, event): |
304 |
try:
|
305 |
if self.command is not None: |
306 |
scenePos = self.mapToScene(event.pos())
|
307 |
self.command.execute(['mousePressEvent', event, scenePos]) |
308 |
if self.command.isTreated == True: return |
309 |
except Exception as ex: |
310 |
from App import App |
311 |
from AppDocData import MessageType |
312 |
|
313 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno) |
314 |
App.mainWnd().addMessage.emit(MessageType.Error, message) |
315 |
|
316 |
if event.button() != Qt.RightButton:
|
317 |
QGraphicsView.mousePressEvent(self, event)
|
318 |
|
319 |
'''
|
320 |
@brief
|
321 |
@author
|
322 |
@date
|
323 |
'''
|
324 |
def mouseReleaseEvent(self, event): |
325 |
try:
|
326 |
if self.command is not None: |
327 |
scenePos = self.mapToScene(event.pos())
|
328 |
instance = self.command.execute(['mouseReleaseEvent', event, scenePos]) |
329 |
if instance is not None: |
330 |
self.scene.addItem(instance)
|
331 |
|
332 |
if self.command is not None and self.command.isTreated == True: |
333 |
if self.command.name == 'Default' and self.command.isCopy: |
334 |
return
|
335 |
self.command = DefaultCommand.DefaultCommand(self) |
336 |
cursor = QCursor(Qt.ArrowCursor) |
337 |
QApplication.instance().setOverrideCursor(cursor) |
338 |
return
|
339 |
except Exception as ex: |
340 |
from App import App |
341 |
from AppDocData import MessageType |
342 |
|
343 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno) |
344 |
App.mainWnd().addMessage.emit(MessageType.Error, message) |
345 |
|
346 |
QGraphicsView.mouseReleaseEvent(self, event)
|
347 |
|
348 |
"""
|
349 |
@brief Show entire image.
|
350 |
"""
|
351 |
def mouseDoubleClickEvent(self, event): |
352 |
scenePos = self.mapToScene(event.pos())
|
353 |
if self.command is not None: |
354 |
instance = self.command.execute(['mouseDoubleClickEvent', event, scenePos]) |
355 |
if self.command.isTreated == True: return |
356 |
|
357 |
if event.button() == Qt.LeftButton:
|
358 |
self.leftMouseButtonDoubleClicked.emit(scenePos.x(), scenePos.y())
|
359 |
elif event.button() == Qt.RightButton:
|
360 |
if self.canZoom: |
361 |
self.zoomStack = [] # Clear zoom stack. |
362 |
self.updateViewer()
|
363 |
self.rightMouseButtonDoubleClicked.emit(scenePos.x(), scenePos.y())
|
364 |
|
365 |
QGraphicsView.mouseDoubleClickEvent(self, event)
|
366 |
|
367 |
'''
|
368 |
@brief key release event
|
369 |
@author Jeongwoo
|
370 |
@date 2018.??.??
|
371 |
'''
|
372 |
def keyReleaseEvent(self, event): |
373 |
if event.key() == Qt.Key_Delete:
|
374 |
pass
|
375 |
|
376 |
QGraphicsView.keyReleaseEvent(self, event)
|
377 |
|
378 |
'''
|
379 |
@brief mouse wheel event
|
380 |
@autor humkyung
|
381 |
@date
|
382 |
'''
|
383 |
def wheelEvent(self, event): |
384 |
if event.modifiers() == Qt.ControlModifier:
|
385 |
if self.canZoom: |
386 |
numDegrees = event.angleDelta() / 8
|
387 |
if numDegrees is not None: |
388 |
if numDegrees.y() > 0: |
389 |
self.zoomImage(True, event) |
390 |
elif numDegrees.y() < 0: |
391 |
self.zoomImage(False, event) |
392 |
else:
|
393 |
super().wheelEvent(event)
|
394 |
|
395 |
'''
|
396 |
@brief
|
397 |
'''
|
398 |
def drawForeground(self, painter, rect): |
399 |
if hasattr(self, 'coords') and self.guidesEnabled: |
400 |
painter.setClipRect(rect) |
401 |
painter.setPen(self._guidePen)
|
402 |
painter.drawLine(round(self.coords.x()), rect.top(), round(self.coords.x()), rect.bottom()) |
403 |
painter.drawLine(rect.left(), round(self.coords.y()), rect.right(), round(self.coords.y())) |
404 |
|
405 |
'''
|
406 |
image = self.image()
|
407 |
if image is not None:
|
408 |
width = rect.width()
|
409 |
height = rect.height()
|
410 |
if self.crosshairPos is not None:
|
411 |
pen = QPen()
|
412 |
pen.setColor(QColor(180, 180, 180))
|
413 |
pen.setStyle(Qt.DashLine)
|
414 |
pen.setWidthF(0.3)
|
415 |
painter.setClipRect(rect)
|
416 |
painter.setPen(pen)
|
417 |
painter.drawLine(self.crosshairPos.x(), 0, self.crosshairPos.x(), height)#Vertical
|
418 |
painter.drawLine(0, self.crosshairPos.y(), width, self.crosshairPos.y())#Horizontal
|
419 |
#else:
|
420 |
# painter.eraseRect(QRectF(0, 0, width, height))
|
421 |
'''
|
422 |
|
423 |
'''
|
424 |
@brief draw background
|
425 |
@author humkyung
|
426 |
@date 2018.07.23
|
427 |
'''
|
428 |
def drawBackground(self, painter, rect): |
429 |
from HydroCalculationCommand import HydroCalculationCommand |
430 |
|
431 |
QGraphicsView.drawBackground(self, painter, rect)
|
432 |
|
433 |
painter.setPen(Qt.gray) |
434 |
painter.setBrush(Qt.gray) |
435 |
rect = self.sceneRect()
|
436 |
rect.setLeft(rect.left() + 5)
|
437 |
rect.setTop(rect.top() + 5)
|
438 |
painter.fillRect(rect, Qt.gray) |
439 |
|
440 |
painter.setPen(Qt.white) |
441 |
painter.setBrush(Qt.white) |
442 |
rect.setLeft(rect.left() - 5)
|
443 |
rect.setTop(rect.top() - 5)
|
444 |
rect.setRight(rect.right() - 5)
|
445 |
rect.setBottom(rect.bottom() - 5)
|
446 |
if not HydroCalculationCommand.ERRORS: |
447 |
painter.fillRect(rect, QBrush(Qt.white)) |
448 |
else:
|
449 |
painter.fillRect(rect, QBrush(Qt.lightGray)) |
450 |
|
451 |
'''
|
452 |
@history 2018.06.11 Jeongwoo Change method to manage guideline items
|
453 |
humkyung 2018.08.28 remove guide lines before drawing
|
454 |
'''
|
455 |
GUIDELINE_ITEMS = [] |
456 |
def showGuideline(self, pos, isShow): |
457 |
image = self.image()
|
458 |
width = image.width() |
459 |
height = image.height() |
460 |
pen = QPen() |
461 |
pen.setColor(QColor(180, 180, 180)) |
462 |
pen.setStyle(Qt.DashLine) |
463 |
pen.setWidthF(0.5)
|
464 |
if isShow:
|
465 |
items = self.scene.items()
|
466 |
for item in self.GUIDELINE_ITEMS: |
467 |
if item in items: |
468 |
self.scene.removeItem(item)
|
469 |
self.GUIDELINE_ITEMS.clear()
|
470 |
|
471 |
if pos is not None: |
472 |
verticalLine = self.scene.addLine(pos.x(), 0, pos.x(), height, pen) |
473 |
horizontalLine = self.scene.addLine(0, pos.y(), width, pos.y(), pen) |
474 |
else:
|
475 |
verticalLine = self.scene.addLine(round(width*0.5), 0, round(width*0.5), height, pen) |
476 |
horizontalLine = self.scene.addLine(0, round(height*0.5), width, round(height*0.5), pen) |
477 |
|
478 |
self.GUIDELINE_ITEMS.append(verticalLine)
|
479 |
self.GUIDELINE_ITEMS.append(horizontalLine)
|
480 |
else:
|
481 |
items = self.scene.items()
|
482 |
for item in self.GUIDELINE_ITEMS: |
483 |
if item in items: |
484 |
self.scene.removeItem(item)
|
485 |
self.GUIDELINE_ITEMS.clear()
|
486 |
|
487 |
'''
|
488 |
@brief drag enter event
|
489 |
@author humkyung
|
490 |
@date 2018.04.17
|
491 |
'''
|
492 |
def dragEnterEvent(self, event): |
493 |
event.acceptProposedAction() |
494 |
|
495 |
'''
|
496 |
@brief drag move event
|
497 |
@author humkyung
|
498 |
@date 2018.04.17
|
499 |
@history humkyung 2018.08.21 highlight item under mouse
|
500 |
'''
|
501 |
def dragMoveEvent(self, event): |
502 |
scenePos = self.mapToScene(event.pos())
|
503 |
items = [item for item in self.scene.items(scenePos) if type(item) is not QGraphicsPixmapItem and type(item) is not QGraphicsTextItem] |
504 |
if len(items) > 0: |
505 |
if not hasattr(self, '_underItem') or self._underItem is not items[0]: |
506 |
if hasattr(self, '_underItem') and self._underItem is not None: |
507 |
if hasattr(self._underItem, 'highlight'): |
508 |
self._underItem.highlight(False) |
509 |
|
510 |
self._underItem = items[0] |
511 |
if hasattr(self._underItem, 'highlight'): |
512 |
self._underItem.highlight(True) |
513 |
#elif hasattr(self, '_underItem') and self._underItem is not None:
|
514 |
# self._underItem.hoverLeaveEvent(event)
|
515 |
# self._underItem = None
|
516 |
|
517 |
event.acceptProposedAction() |
518 |
|
519 |
def dropEvent(self, event): |
520 |
"""create a symbol"""
|
521 |
try:
|
522 |
from AppDocData import AppDocData |
523 |
import symbol |
524 |
|
525 |
scenePos = self.mapToScene(event.pos())
|
526 |
uid = event.mimeData().text() |
527 |
svg = self.scene.createSymbolObject(uid, scale=None, angle=None) |
528 |
if svg:
|
529 |
self.scene.place_symbol(svg, scenePos)
|
530 |
|
531 |
event.acceptProposedAction() |
532 |
except Exception as ex: |
533 |
from App import App |
534 |
from AppDocData import MessageType |
535 |
|
536 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
537 |
sys.exc_info()[-1].tb_lineno)
|
538 |
App.mainWnd().addMessage.emit(MessageType.Error, message) |
539 |
|
540 |
'''
|
541 |
@brief find item by uid (SymbolSvgItem 기반, QEngineeringConnectorItem 제외, QEngineeringLineItem 포함)
|
542 |
@author kyouho
|
543 |
@date 2018.07.31
|
544 |
'''
|
545 |
def findItemByUid(self, uid): |
546 |
|
547 |
items = [item for item in self.scene.items() if hasattr(item, 'uid') and str(item.uid) == str(uid)] |
548 |
return items[0] if items else None |
549 |
|
550 |
def save_as_image(self, file_path): |
551 |
""" save scene as given file """
|
552 |
|
553 |
try:
|
554 |
rect = self.scene.itemsBoundingRect()
|
555 |
default = QSize(rect.width()*1, rect.height()*1) |
556 |
image = QImage(default, QImage.Format_ARGB32_Premultiplied) |
557 |
painter = QPainter(image) |
558 |
|
559 |
# render the scene
|
560 |
self.scene.render(painter, QRectF(image.rect()), rect)
|
561 |
painter.end() |
562 |
|
563 |
# save the image to a given file
|
564 |
image.save(file_path) |
565 |
except Exception as ex: |
566 |
from App import App |
567 |
from AppDocData import MessageType |
568 |
|
569 |
message = f"error occurred({repr(ex)}) in {sys.exc_info()[-1].tb_frame.f_code.co_filename}:" \
|
570 |
f"{sys.exc_info()[-1].tb_lineno}"
|
571 |
App.mainWnd().addMessage.emit(MessageType.Error, message) |
572 |
|
573 |
def save_as_svg(self, file_path): |
574 |
""" save scene as given file """
|
575 |
from xml.etree.ElementTree import Element, tostring, SubElement, dump, ElementTree, parse |
576 |
|
577 |
def prettify(elem): |
578 |
"""Return a pretty-printed XML string for the Element."""
|
579 |
from xml.etree import ElementTree |
580 |
from xml.dom import minidom |
581 |
|
582 |
try:
|
583 |
rough_string = ElementTree.tostring(elem, 'utf-8')
|
584 |
reparsed = minidom.parseString(rough_string) |
585 |
return reparsed.toprettyxml(indent=" ") |
586 |
except Exception as ex: |
587 |
from AppDocData import MessageType |
588 |
|
589 |
message = f'error occurred({repr(ex)}) in {sys.exc_info()[-1].tb_frame.f_code.co_filename}:' \
|
590 |
f'{sys.exc_info()[-1].tb_lineno}'
|
591 |
self.display_message.emit(MessageType.Error, message)
|
592 |
|
593 |
try:
|
594 |
rect = self.scene.itemsBoundingRect()
|
595 |
|
596 |
svg = Element('svg')
|
597 |
svg.attrib['width'] = f"{rect.width()}" |
598 |
svg.attrib['height'] = f"{rect.height()}" |
599 |
svg.attrib['viewBox'] = f"{rect.left()} {rect.top()} {rect.width()} {rect.height()}" |
600 |
svg.attrib['xmlns'] = 'http://www.w3.org/2000/svg' |
601 |
svg.attrib['xmlns:xlink'] = "http://www.w3.org/1999/xlink" |
602 |
svg.attrib['xmlns:inkscape'] = "http://www.inkscape.org/namespaces/inkscape" |
603 |
svg.attrib['version'] = "1.1" |
604 |
svg.attrib['baseProfile'] = "tiny" |
605 |
|
606 |
for item in self.scene.items(): |
607 |
if hasattr(item, 'to_svg'): |
608 |
node = item.to_svg(None)
|
609 |
if node:
|
610 |
svg.extend(node) |
611 |
|
612 |
with open(file_path, 'w', encoding='utf-8') as output_file: |
613 |
output_file.write(prettify(svg)) |
614 |
except Exception as ex: |
615 |
from App import App |
616 |
from AppDocData import MessageType |
617 |
|
618 |
message = f"error occurred({repr(ex)}) in {sys.exc_info()[-1].tb_frame.f_code.co_filename}:" \
|
619 |
f"{sys.exc_info()[-1].tb_lineno}"
|
620 |
App.mainWnd().addMessage.emit(MessageType.Error, message) |
621 |
|
622 |
|
623 |
if __name__ == '__main__': |
624 |
import sys |
625 |
try:
|
626 |
from PyQt5.QtWidgets import QApplication |
627 |
except ImportError: |
628 |
try:
|
629 |
from PyQt4.QtGui import QApplication |
630 |
except ImportError: |
631 |
raise ImportError("ImageViewerQt: Requires PyQt5 or PyQt4.") |
632 |
|
633 |
def handleLeftClick(x, y): |
634 |
row = int(y)
|
635 |
column = int(x)
|
636 |
|
637 |
# Create the application.
|
638 |
app = QApplication(sys.argv) |
639 |
|
640 |
# Create image viewer and load an image file to display.
|
641 |
viewer = QtImageViewer(None)
|
642 |
viewer.loadImageFromFile() # Pops up file dialog.
|
643 |
|
644 |
# Handle left mouse clicks with custom slot.
|
645 |
viewer.leftMouseButtonPressed.connect(handleLeftClick) |
646 |
|
647 |
# Show viewer and run application.
|
648 |
viewer.show() |
649 |
sys.exit(app.exec_()) |