프로젝트

일반

사용자정보

통계
| 개정판:

hytos / DTI_PID / DTI_PID / Shapes / EngineeringTextItem.py @ c41ba341

이력 | 보기 | 이력해설 | 다운로드 (38.7 KB)

1
# coding: utf-8
2
import os.path
3
import copy
4
import sys
5

    
6
try:
7
    from PyQt5.QtCore import *
8
    from PyQt5.QtGui import *
9
    from PyQt5.QtWidgets import *
10
except ImportError:
11
    try:
12
        from PyQt4.QtCore import Qt, QRectF, pyqtSignal, QRect, QObject, QT_VERSION_STR
13
        from PyQt4.QtGui import QGraphicsView, QGraphicsScene, QImage, QPixmap, QPainterPath, QFileDialog, QFont, \
14
            QColor, QFontMetricsF
15
    except ImportError:
16
        raise ImportError("ImageViewerQt: Requires PyQt5 or PyQt4.")
17

    
18
from EngineeringPolylineItem import QEngineeringPolylineItem
19
from GraphicsBoundingBoxItem import QGraphicsBoundingBoxItem
20
from AppDocData import *
21
from EngineeringAbstractItem import QEngineeringAbstractItem
22
from TextInfo import TextInfo
23
from SymbolSvgItem import SymbolSvgItem
24

    
25

    
26
class Highlighter(QSyntaxHighlighter):
27
    err_format = QTextCharFormat()
28
    err_format.setUnderlineColor(Qt.red)
29
    err_format.setUnderlineStyle(QTextCharFormat.SpellCheckUnderline)
30

    
31
    def __init__(self, *args):
32
        QSyntaxHighlighter.__init__(self, *args)
33

    
34
        self.white_char_list = None
35

    
36
    def highlightBlock(self, text):
37
        pos = 0
38
        for word in text.split():
39
            if self.white_char_list and any((c not in self.white_char_list) for c in word):
40
                self.setFormat(pos, len(word), self.err_format)
41
            pos += len(word) + 1
42

    
43

    
44
class QEngineeringTextItem(QGraphicsTextItem, QEngineeringAbstractItem):
45
    HIGHLIGHT = '#BC4438'
46
    ZVALUE = 210
47

    
48
    '''
49
        @history    2018.05.17  Jeongwoo    Add self._owner variable
50
    '''
51

    
52
    def __init__(self, uid=None, parent=None):
53
        import uuid
54

    
55
        QGraphicsTextItem.__init__(self, parent)
56
        QEngineeringAbstractItem.__init__(self)
57

    
58
        self.uid = uuid.uuid4() if uid is None else uuid.UUID(uid)
59
        self.type = 'TEXT'
60
        self.loc = None
61
        self.size = None
62
        self.angle = 0  # angle in radian
63
        self._owner = None
64
        self.setFlags(QGraphicsItem.ItemIsSelectable | QGraphicsItem.ItemIsFocusable | QGraphicsItem.ItemIsMovable | QGraphicsItem.ItemSendsGeometryChanges)
65
        #self.setFlags(QGraphicsItem.ItemIsSelectable | QGraphicsItem.ItemIsFocusable | QGraphicsItem.ItemIsMovable)
66
        self.setAcceptHoverEvents(True)
67
        self.setAcceptTouchEvents(True)
68

    
69
        self.setColor(self._color)
70
        self._savedColor = None
71

    
72
        self.delimiter = '"'
73
        self.lineNoDelimiter = '!-!'
74

    
75
        self.attribute = ''
76
        self._properties = {}
77

    
78
        self.highlighter = None
79

    
80
        self.transfer = Transfer()
81
        self.setZValue(QEngineeringTextItem.ZVALUE)
82

    
83
    def __str__(self):
84
        """ return string represent uuid """
85
        return str(self.uid)
86

    
87
    '''
88
        @brief      Get owner
89
        @author     Jeongwoo
90
        @date       2018.05.17
91
    '''
92

    
93
    @property
94
    def owner(self):
95
        import uuid
96

    
97
        # find owner with uid
98
        if self.scene() and self._owner and type(self._owner) is uuid.UUID:
99
            #matches = [x for x in self.scene().items() if hasattr(x, 'uid') and str(x.uid) == str(self._owner)]
100
            matches = []
101
            for x in self.scene().items():
102
                if hasattr(x, 'uid') and str(x.uid) == str(self._owner):
103
                    matches = [x]
104
                    break
105
            if matches:
106
                self._owner = matches[0]
107
                
108
            return matches[0] if matches else None
109
        # up to here
110

    
111
        if type(self._owner) is not uuid.UUID and type(self._owner) is not str:
112
            return self._owner
113
        else:
114
            self._owner = None
115
            return None
116

    
117
    '''
118
        @brief      Set owner
119
        @author     Jeongwoo
120
        @date       2018.05.17
121
        @history    2018.05.17  Jeongwoo    Add Calling setColor if self._owner is None or not
122
    '''
123

    
124
    @owner.setter
125
    def owner(self, value):
126
        self._owner = value
127

    
128
        if self._owner is None:
129
            self._color = self.DEFAULT_COLOR
130
        self.setColor(self._color)
131

    
132
    '''
133
        @brief  return text string
134
        @author humkyung
135
        @date   2018.04.16
136
    '''
137

    
138
    def text(self):
139
        return self.toPlainText()
140

    
141
    '''
142
        @brief  return center position of text
143
        @author humkyung
144
        @date   2018.04.16
145
    '''
146

    
147
    def center(self):
148
        return self.sceneBoundingRect().center()
149

    
150
    @property
151
    def text_size(self):
152
        if self.angle == 1.57 or self.angle == 4.71:
153
            return QRectF(0, 0, self.size[1], self.size[0])
154
        else:
155
            return QRectF(0, 0, self.size[0], self.size[1])
156

    
157
    def boundingRect(self) -> QRectF:
158
        return self.text_size
159

    
160
    '''
161
        @brief      hover event
162
        @authro     humkyung
163
        @date       
164
    '''
165

    
166
    def hoverEnterEvent(self, event):
167
        self.highlight(True)
168

    
169
    def hoverLeaveEvent(self, event):
170
        self.highlight(False)
171

    
172
    '''
173
        @brief      set highlight
174
        @author     kyouho
175
        @date       2018.08.27
176
    '''
177

    
178
    def highlight(self, flag):
179
        self.hover = flag
180
        if flag:
181
            if self._savedColor is None:
182
                self._savedColor = self.getColor()
183
            self.setColor(QEngineeringTextItem.HIGHLIGHT)
184
        elif hasattr(self, '_savedColor'):
185
            self.setColor(self._savedColor)
186

    
187
        self.update()
188

    
189
    def hoverMoveEvent(self, event):
190
        pass
191

    
192
    '''
193
        @brief      remove item when user press delete key
194
        @author     humkyung
195
        @date       2018.04.23
196
        @history    2018.05.25  Jeongwoo    Seperate delete item method
197
                    humkyung 2018.08.18 rotate text when user press 'R'
198
    '''
199

    
200
    def contextMenuEvent(self, event):
201
        items = self.scene().selectedItems()
202
        if len(items) > 0 and self in items:
203
            menu = QMenu()
204

    
205
            editAction = QAction('Edit(Return)', None)
206
            editAction.triggered.connect(self.contextEdit)
207
            menu.addAction(editAction)
208

    
209
            mergeAction = QAction('Merge(M)', None)
210
            mergeAction.triggered.connect(self.contextMerge)
211
            menu.addAction(mergeAction)
212

    
213
            rotateAction = QAction('Rotate(R)', None)
214
            rotateAction.triggered.connect(self.contextRotate)
215
            menu.addAction(rotateAction)
216

    
217
            deleteAction = QAction('Delete(E)', None)
218
            deleteAction.triggered.connect(self.contextDelete)
219
            menu.addAction(deleteAction)
220

    
221
            menu.exec_(event.screenPos())
222

    
223
    def contextMerge(self):
224
        event = QKeyEvent(QEvent.KeyPress, Qt.Key_M, Qt.NoModifier)
225
        self.keyPressEvent(event)
226

    
227
    def contextDelete(self):
228
        event = QKeyEvent(QEvent.KeyPress, Qt.Key_Delete, Qt.NoModifier)
229
        self.scene().keyPressEvent(event)
230

    
231
    def contextEdit(self):
232
        event = QKeyEvent(QEvent.KeyPress, Qt.Key_Return, Qt.NoModifier)
233
        self.keyPressEvent(event)
234

    
235
    def contextRotate(self):
236
        self.textRotate()
237

    
238
    def textRotate(self):
239
        # degree 0
240
        if 0 == self.angle:
241
            self.angle = 1.57
242
        # degree 90
243
        elif 1.57 == self.angle:
244
            self.angle = 3.14
245
        # degree 180
246
        elif 3.14 == self.angle:
247
            self.angle = 4.71
248
        # degree 270
249
        elif 4.71 == self.angle:
250
            self.angle = 0
251

    
252
        width = self.size[0]
253
        height = self.size[1]
254
        self.size = [height, width]
255

    
256
        self.rotate()
257

    
258
    def keyPressEvent(self, event):
259
        #print('item :' + str(event.key()))
260
        if Qt.Key_Return == event.key():
261
            self.edit_text()
262
        elif event.key() == Qt.Key_Up:  # translate up/down/left/right symbol
263
            modifiers = QApplication.keyboardModifiers()
264
            delta = 10 if modifiers == Qt.ControlModifier else 1
265

    
266
            trans = QTransform()
267
            trans.rotateRadians(self.angle)
268
            dx, dy = trans.map(0, -1)
269
            self.loc[1] = self.loc[1] - delta
270

    
271
            trans = self.transform()
272
            trans.translate(dx*delta, dy*delta)
273
            self.setTransform(trans)
274
        elif event.key() == Qt.Key_Down:
275
            modifiers = QApplication.keyboardModifiers()
276
            delta = 10 if modifiers == Qt.ControlModifier else 1
277

    
278
            trans = QTransform()
279
            trans.rotateRadians(self.angle)
280
            dx, dy = trans.map(0, 1)
281
            self.loc[1] = self.loc[1] + delta
282

    
283
            trans = self.transform()
284
            trans.translate(dx * delta, dy * delta)
285
            self.setTransform(trans)
286
        elif event.key() == Qt.Key_Left:
287
            modifiers = QApplication.keyboardModifiers()
288
            delta = 10 if modifiers == Qt.ControlModifier else 1
289

    
290
            trans = QTransform()
291
            trans.rotateRadians(self.angle)
292
            dx, dy = trans.map(-1, 0)
293
            self.loc[0] = self.loc[0] - delta
294

    
295
            trans = self.transform()
296
            trans.translate(dx * delta, dy * delta)
297
            self.setTransform(trans)
298
        elif event.key() == Qt.Key_Right:
299
            modifiers = QApplication.keyboardModifiers()
300
            delta = 10 if modifiers == Qt.ControlModifier else 1
301

    
302
            trans = QTransform()
303
            trans.rotateRadians(self.angle)
304
            dx, dy = trans.map(1, 0)
305
            self.loc[0] = self.loc[0] + delta
306

    
307
            trans = self.transform()
308
            trans.translate(dx * delta, dy * delta)
309
            self.setTransform(trans)
310
        elif event.key() == Qt.Key_M:
311
            from App import App
312
            App.mainWnd().keyPressEvent(event)
313

    
314
        # QGraphicsTextItem.keyPressEvent(self, event)
315

    
316
    def mouseMoveEvent(self, event):
317
        modifiers = QApplication.keyboardModifiers()
318
        if modifiers == Qt.ShiftModifier:
319
            super().mouseMoveEvent(event)
320
    
321
    def itemChange(self, change, value):
322
        """ call signals when item's position or rotation is changed """
323
        if not self.scene(): return super().itemChange(change, value)
324

    
325
        if change == QGraphicsItem.ItemPositionHasChanged:
326

    
327
            scene_origin = self.sceneBoundingRect()
328
            self.loc = [round(scene_origin.x(), 1), round(scene_origin.y(), 1)]
329

    
330
            if hasattr(self.scene(), 'contents_changed'):
331
                self.scene().contents_changed.emit()
332

    
333
            return value
334

    
335
        return super().itemChange(change, value)
336

    
337
    '''
338
        @brief  draw rect when item is selected
339
        @author humkyung
340
        @date   2018.07.08
341
    '''
342
    def drawFocusRect(self, painter):
343
        if not hasattr(self, '_focus_pen'):
344
            self._focus_pen = QPen(Qt.DotLine)
345
            self._focus_pen.setColor(Qt.black)
346
            self._focus_pen.setWidthF(1.5)
347

    
348
        if not hasattr(self, '_hilight_color'):
349
            self._hilight_color = QColor(255, 0, 0, 127)
350
        painter.setBrush(QBrush(self._hilight_color))
351

    
352
        painter.setPen(self._focus_pen)
353
        painter.drawRect(self.text_size)
354

    
355
    '''
356
        @brief  override paint(draw connection points)
357
        @author humkyung
358
        @date   2018.07.08
359
    '''
360
    def paint(self, painter, options=None, widget=None):
361
        import math
362

    
363
        painter.setClipRect(options.exposedRect)
364

    
365
        self.setColor(self.getColor())
366

    
367
        #if self.angle == 1.57 or self.angle == 4.71:
368
        #    rect = QRectF(0, 0, self.size[1], self.size[0])
369
        #else:
370
        #    rect = QRectF(0, 0, self.size[0], self.size[1])
371

    
372
        # draw onwer indicator
373
        if self.owner and issubclass(type(self.owner), QEngineeringAbstractItem):
374
            painter.setPen(Qt.magenta)
375
            rect = self.text_size
376
            center = self.sceneBoundingRect().center()
377
            center2 = self.owner.sceneBoundingRect().center()
378
            dx = center2.x() - center.x()
379
            dy = center2.y() - center.y()
380
            if self.angle == 1.57:
381
                rad = 90 * (math.pi / 180.0)
382
                nx = math.cos(rad) * dx - math.sin(rad) * dy
383
                ny = math.sin(rad) * dx + math.cos(rad) * dy
384
                dx, dy = nx, ny
385
            painter.drawLine(rect.center().x(), rect.center().y(), rect.center().x() + dx, rect.center().y() + dy)
386

    
387
        painter.setFont(self.font())
388
        color = self.defaultTextColor()
389

    
390
        # draw white background during hover
391
        if self.hover:
392
            configs = AppDocData.instance().getConfigs('Text', 'Background')
393
            if not configs or not int(configs[0].value) != 1:
394
                painter.setPen(Qt.NoPen)
395
                painter.setBrush(Qt.white)
396
                painter.drawRect(self.text_size)
397
        # up to here
398

    
399
        painter.setPen(QPen(color))
400

    
401
        if self.isSelected():
402
            self.drawFocusRect(painter)
403

    
404
        #painter.drawText(rect, Qt.AlignCenter, self.text())
405
        QGraphicsTextItem.paint(self, painter, options, widget)
406

    
407
    '''
408
        @brief      Return real item position
409
        @author     Jeongwoo
410
        @date       2018.05.25
411
    '''
412

    
413
    def boundingRectOnScene(self):
414
        rect = self.text_size
415
        rect.moveTo(self.loc[0], self.loc[1])
416
        return rect
417

    
418
    @staticmethod
419
    def create_text_with(scene, text_info):
420
        """create a text using text_info"""
421
        from TextItemFactory import TextItemFactory
422

    
423
        x = text_info.getX()
424
        y = text_info.getY()
425
        angle = text_info.getAngle()
426
        text = text_info.getText()
427
        if not text.replace(' ', '').replace('\n', ''):
428
            return None
429
        width = text_info.getW()
430
        height = text_info.getH()
431
        item = TextItemFactory.instance().createTextItem(text_info)
432
        if item is not None:
433
            item.loc = [x, y]
434
            item.size = (width, height)
435
            item.angle = angle
436
            item.addTextItemToScene(scene)
437

    
438
        return item
439

    
440
    def edit_text(self):
441
        """edit text by using ocr dialog"""
442
        from TextItemFactory import TextItemFactory
443
        from OcrResultDialog import QOcrResultDialog
444

    
445
        item = None
446
        try:
447
            dialog = QOcrResultDialog(None, self.scene().views()[0].image().copy(self.loc[0], self.loc[1],
448
                                                                               self.size[0], self.size[1]),
449
                                      QRect(self.loc[0], self.loc[1], self.size[0], self.size[1]), text_item=self)
450
            (res, textInfoList) = dialog.showDialog()
451

    
452
            if QDialog.Accepted == res and textInfoList:
453
                # create new texts
454
                for text_info in textInfoList:
455
                    item = QEngineeringTextItem.create_text_with(self.scene(), text_info)
456
                    if item:
457
                        item.area = self.area
458
                        item.transfer.onRemoved.connect(self.transfer.onRemoved)
459
                        #item.transfer.onRemoved.connect(App.mainWnd().itemRemoved)
460

    
461
                self.transfer.onRemoved.emit(self)
462
        except Exception as ex:
463
            from App import App
464
            from AppDocData import MessageType
465

    
466
            message = 'error occurred({}-{}) in {}:{}'.format(repr(ex), self.text(),
467
                                                              sys.exc_info()[-1].tb_frame.f_code.co_filename,
468
                                                              sys.exc_info()[-1].tb_lineno)
469
            App.mainWnd().addMessage.emit(MessageType.Error, message)
470

    
471
        return item
472

    
473
    def focusOutEvent(self, event: QFocusEvent) -> None:
474
        self.setTextInteractionFlags(Qt.NoTextInteraction)
475
        super(QEngineeringTextItem, self).focusOutEvent(event)
476

    
477
    '''
478
        @brief      Double click event, Show QOcrResultDialog
479
        @author     Jeongwoo
480
        @date       18.04.23
481
        @history    18.06.20    Jeongwoo    Resize QRect added 1
482
    '''
483

    
484
    def mouseDoubleClickEvent(self, event):
485
        if event.buttons() == Qt.LeftButton:
486
            """
487
            if self.textInteractionFlags() == Qt.NoTextInteraction:
488
                self.setTextInteractionFlags(Qt.TextEditorInteraction)
489
                self.setFocus()
490
            """
491
            self.edit_text()
492
        super(QGraphicsTextItem, self).mouseDoubleClickEvent(event)
493

    
494
    '''
495
        @brief      rotate text
496
        @author     humkyung
497
        @date       2018.08.18
498
    '''
499

    
500
    def rotate(self):
501
        sx = 1
502
        sy = 1
503
        width = self.size[0]
504
        height = self.size[1]
505
        x = self.loc[0]
506
        y = self.loc[1]
507

    
508
        transform = QTransform()
509
        if (1.57 == self.angle) or (4.71 == self.angle):
510
            rect = self.text_size
511
            sx = width / rect.height()
512
            sy = height / rect.width()
513

    
514
            transform.translate(x, y)
515
            transform.translate(width * 0.5, height * 0.5)
516
            transform.scale(1, sy)
517
            transform.rotateRadians(-self.angle)
518
            transform.translate(-rect.width() * 0.5, -rect.height() * 0.5)
519
        elif 3.14 == self.angle:
520
            rect = self.text_size
521
            sx = width / rect.width()
522
            sy = height / rect.height()
523

    
524
            transform.translate(x, y - round((rect.height() - height) * 0.5))
525
            transform.scale(sx, 1)
526
            transform.rotateRadians(-self.angle)
527
            transform.translate(-width * 0.5, -height * 0.5)
528
        else:
529
            rect = self.text_size
530
            sx = width / rect.width()
531
            sy = height / rect.height()
532

    
533
            # if '\n' not in text:
534
            transform.translate(x, y - round((rect.height() - height) * 0.5))
535
            transform.scale(sx, 1)
536

    
537
        self.setTransform(transform)
538
        self.update()
539

    
540
    def text_width(self, font):
541
        """return text width"""
542
        text_width = 0
543
        for _text in self.text().split('\n'):
544
            """
545
            rect = QFontMetricsF(font).tightBoundingRect(_text)
546
            text_width = max(rect.width(), text_width)
547
            """
548
            text_width = max(QFontMetricsF(font).width(_text), text_width)
549

    
550
        return text_width
551

    
552
    def update_shape(self):
553
        """update text shape"""
554

    
555
        try:
556
            app_doc_data = AppDocData.instance()
557
            configs = app_doc_data.getConfigs('Text Style', 'Font Name')
558
            fontName = configs[0].value if configs else 'Arial'
559
            configs = app_doc_data.getConfigs('Text Style', 'Font Size')
560
            fontSize = int(configs[0].value) if configs else -1
561

    
562
            sx = 1
563
            sy = 1
564
            width = self.size[0]
565
            height = self.size[1]
566
            x = self.loc[0]
567
            y = self.loc[1]
568
            rect = None
569
            line_count = self.text().count('\n') if self.text().count('\n') is not 0 else 1
570

    
571
            allowed_error = 0.01
572
            if abs(self.angle - 1.57) < allowed_error:
573
                self.angle = 1.57
574
            elif abs(self.angle - 4.71) < allowed_error:
575
                self.angle = 4.71
576
            elif abs(self.angle - 3.14) < allowed_error:
577
                self.angle = 3.14
578
            else:
579
                self.angle = 0
580

    
581
            transform = QTransform()
582
            if abs(self.angle - 1.57) < allowed_error or abs(self.angle - 4.71) < allowed_error:
583
                font = QFont(fontName, width if fontSize == -1 else fontSize)
584
                font.setBold(True)
585

    
586
                x_factor = width / QFontMetricsF(font).height() #self.text()).height() / line_count)
587
                y_factor = height / self.text_width(font)
588
                factor = min(x_factor, y_factor)
589
                font.setPointSizeF(font.pointSizeF() * factor)
590
                self.setFont(font)
591

    
592
                rect = self.text_size
593
                sx = rect.height() / QFontMetricsF(font).tightBoundingRect(self.text()).height()
594
                sy = rect.width() / self.text_width(font)
595

    
596
                transform.translate(x + width * 0.5, y + height * 0.5)
597
                transform.rotateRadians(-self.angle)
598
                #transform.scale(1, sy)
599
                transform.translate(-rect.width() * 0.5, -rect.height() * 0.5)
600
            elif abs(self.angle - 3.14) < allowed_error:
601
                font = QFont(fontName, height if fontSize == -1 else fontSize)
602
                font.setBold(True)
603

    
604
                x_factor = width / self.text_width(font)
605
                y_factor = height / QFontMetricsF(font).height() #self.text()).height() / line_count)
606
                factor = min(x_factor, y_factor)
607
                font.setPointSizeF(font.pointSizeF() * factor)
608
                self.setFont(font)
609

    
610
                rect = self.text_size
611
                sx = width / self.text_width(font)
612
                sy = height / QFontMetricsF(font).tightBoundingRect(self.text()).height()
613

    
614
                transform.translate(x, y - round((rect.height() - height) * 0.5))
615
                #transform.scale(sx, 1)
616
                transform.rotateRadians(-self.angle)
617
                transform.translate(-width * 0.5, -height * 0.5)
618
            else:
619
                font = QFont(fontName, height if fontSize == -1 else fontSize)
620
                font.setBold(True)
621

    
622
                x_factor = width / self.text_width(font)
623
                y_factor = height / QFontMetricsF(font).height() #.boun(self.text()).height() / line_count)
624
                factor = min(x_factor, y_factor)
625
                font.setPointSizeF(font.pointSizeF() * factor)
626
                self.setFont(font)
627

    
628
                sx = width / self.text_width(font)
629
                sy = height / QFontMetricsF(font).tightBoundingRect(self.text()).height()
630

    
631
                transform.translate(x, y)
632

    
633
            self.setTransform(transform)
634

    
635
            self.document().setDocumentMargin(0)
636
        except Exception as ex:
637
            from App import App
638
            from AppDocData import MessageType
639

    
640
            message = 'error occurred({}-{}) in {}:{}'.format(ex, self.text(),
641
                                                              sys.exc_info()[-1].tb_frame.f_code.co_filename,
642
                                                              sys.exc_info()[-1].tb_lineno)
643
            App.mainWnd().addMessage.emit(MessageType.Error, message)
644

    
645
    '''
646
        @brief      Put text on scene
647
        @author     Jeongwoo
648
        @date       18.04.23
649
        @history    humkyung 2018.06.30 apply font configuration
650
    '''
651
    def addTextItemToScene(self, scene):
652
        try:
653
            self.update_shape()
654
            """
655
            white_char_list = app_doc_data.getConfigs('Text Recognition', 'White Character List')
656
            self.highlighter = Highlighter(self.document())
657
            self.highlighter.white_char_list = '‘' #white_char_list[0].value if white_char_list else None
658
            """
659

    
660
            scene.addItem(self)
661
        except Exception as ex:
662
            from App import App
663
            from AppDocData import MessageType
664

    
665
            message = 'error occurred({}-{}) in {}:{}'.format(ex, self.text(),
666
                                                              sys.exc_info()[-1].tb_frame.f_code.co_filename,
667
                                                              sys.exc_info()[-1].tb_lineno)
668
            App.mainWnd().addMessage.emit(MessageType.Error, message)
669

    
670
    '''
671
        @brief  get connected items
672
        @author humkyung
673
        @date   2018.04.23
674
        @history    2018.11.22      euisung     fix note road
675
    '''
676

    
677
    def getConnectedItems(self):
678
        visited = []
679

    
680
        try:
681
            if 1 == len(self.conns):
682
                # iterate connected items
683
                pool = []
684
                visited = []
685
                pool.append(self.conns[0])
686
                while len(pool) > 0:
687
                    it = pool.pop()
688
                    visited.append(it)
689
                    for conn in it.conns:
690
                        if (conn is not None) and (conn not in visited): pool.append(conn)
691
                # up to here
692
        except Exception as ex:
693
            print('error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
694
                                                       sys.exc_info()[-1].tb_lineno))
695

    
696
        return visited
697

    
698
    @staticmethod
699
    def from_database(component):
700
        """ get text item from database """
701
        import uuid
702
        from AppDocData import AppDocData
703
        from TextItemFactory import TextItemFactory
704
        from SymbolAttr import SymbolAttr
705

    
706
        item = None
707

    
708
        try:
709
            x = float(component['X'])
710
            y = float(component['Y'])
711
            width = float(component['Width']) if component['Width'] is not None else 0
712
            height = float(component['Height']) if component['Height'] is not None else 0
713
            if height < 10:
714
                height = 10
715
            if width < 10:
716
                width = 10
717
            angle = float(component['Rotation']) if component['Rotation'] is not None else 0
718
            text = component['Value']
719
            textInfo = TextInfo(text, x, y, width, height, angle)
720
            connline = component['Connected'] if component['Connected'] is not None else None
721

    
722
            item = TextItemFactory.instance().createTextItem(textInfo)
723
            if item is not None:
724
                item.setVisible(False)
725
                item.uid = uuid.UUID(component['UID'])
726
                item.loc = [x, y]
727
                item.size = [width, height]
728
                item.angle = angle
729
                #item.setToolTip('<b>{}</b><br>LINE NO={}'.format(str(item.uid), text))
730

    
731
                if component['Owner'] and component['Owner'] != 'None':
732
                    item._owner = uuid.UUID(component['Owner'])
733

    
734
                # assign area
735
                if not component['Area']:
736
                    app_doc_data = AppDocData.instance()
737
                    for area in app_doc_data.getAreaList():
738
                        if area.contains([x, y]):
739
                            item.area = area.name
740
                            break
741
                else:
742
                    item.area = component['Area']
743
                ## up to here
744

    
745
                """ apply freeze value """
746
                # item.freeze_item.update_freeze(item.prop('Freeze'))
747

    
748
                if connline is not None:
749
                    item.conns.append(connline)
750
        except Exception as ex:
751
            from App import App
752
            from AppDocData import MessageType
753

    
754
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
755
                                                           sys.exc_info()[-1].tb_lineno)
756
            App.mainWnd().addMessage.emit(MessageType.Error, message)
757
            return None
758

    
759
        return item
760

    
761
    '''
762
        @brief      parse xml code
763
        @author     humkyung
764
        @date       2018.09.15
765
    '''
766

    
767
    @staticmethod
768
    def fromXml(node):
769
        import uuid
770
        from TextItemFactory import TextItemFactory
771
        from AppDocData import AppDocData
772
        from EngineeringNoteItem import QEngineeringNoteItem
773
        from QEngineeringSizeTextItem import QEngineeringSizeTextItem
774
        from EngineeringValveOperCodeTextItem import QEngineeringValveOperCodeTextItem
775

    
776
        item = None
777

    
778
        try:
779
            location = node.find('LOCATION').text if node.find('LOCATION') is not None else '0,0'
780
            x = float(location.split(',')[0])
781
            y = float(location.split(',')[1])
782
            width = float(node.find('WIDTH').text) if node.find('WIDTH') is not None else 0
783
            height = float(node.find('HEIGHT').text) if node.find('HEIGHT') is not None else 0
784
            if height < 10:
785
                height = 10
786
            if width < 10:
787
                width = 10
788
            angle = float(node.find('ANGLE').text) if node.find('ANGLE') is not None else 0
789
            value = node.find('VALUE').text
790
            if len(value.replace('\n', '').replace(' ', '')) == 0:
791
                return None
792
            # attributeValue = node.find('ATTRIBUTEVALUE')
793
            name = node.find('NAME').text
794
            textInfo = TextInfo(value, x, y, width, height, angle)
795

    
796
            item = TextItemFactory.instance().createTextItem(textInfo)
797
            if item is not None:
798
                item.loc = [x, y]
799
                item.size = [width, height]
800
                item.angle = angle
801

    
802
            # set uid and owner of item
803
            if item is not None:
804
                item.uid = uuid.UUID(node.find('UID').text)
805
                item.setVisible(False)
806

    
807
                if node.find('OWNER') is not None and node.find('OWNER').text != 'None':
808
                    item._owner = uuid.UUID(node.find('OWNER').text)
809

    
810
            ## assign area
811
            if item is not None:
812
                if node.find('AREA') is None:
813
                    appDocData = AppDocData.instance()
814
                    for area in appDocData.getAreaList():
815
                        if area.contains([x, y]):
816
                            item.area = area.name
817
                            break
818
                else:
819
                    item.area = node.find('AREA').text
820
                    ## up to here
821
        except Exception as ex:
822
            from App import App
823
            from AppDocData import MessageType
824

    
825
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
826
                                                           sys.exc_info()[-1].tb_lineno)
827
            App.mainWnd().addMessage.emit(MessageType.Error, message)
828

    
829
        return item
830

    
831
    '''
832
        @brief      generate xml code
833
        @author     humkyung
834
        @date       2018.04.23
835
        @history    humkyung 2018.04.27 move to QEngineeringLineNoTextItem
836
                    humkyung 2018.05.02 add name as parameter
837
                    Jeongwoo 2018.05.30 Change variable [owner] is nullable and Add/Modify attributes
838
    '''
839

    
840
    def toXml(self, owner=None):
841
        from xml.etree.ElementTree import Element, SubElement, dump, ElementTree
842
        from EngineeringLineItem import QEngineeringLineItem
843
        from SymbolSvgItem import SymbolSvgItem
844

    
845
        try:
846
            node = Element('ATTRIBUTE')
847
            uidNode = Element('UID')
848
            uidNode.text = str(self.uid)
849
            node.append(uidNode)
850

    
851
            # write owner's uid to xml
852
            ownerNode = Element('OWNER')
853
            if self.owner is not None:
854
                ownerNode.text = str(self.owner)
855
            else:
856
                ownerNode.text = 'None'
857
            node.append(ownerNode)
858
            # up to here
859

    
860
            attributeValueNode = Element('ATTRIBUTEVALUE')
861
            attributeValueNode.text = self.attribute
862
            node.append(attributeValueNode)
863

    
864
            nameNode = Element('NAME')
865
            nameNode.text = self.type
866
            node.append(nameNode)
867

    
868
            locNode = Element('LOCATION')
869
            locNode.text = '{},{}'.format(self.loc[0], self.loc[1])
870
            node.append(locNode)
871

    
872
            valueNode = Element('VALUE')
873
            valueNode.text = self.text()
874
            node.append(valueNode)
875

    
876
            angleNode = Element('ANGLE')
877
            angleNode.text = str(self.angle)
878
            node.append(angleNode)
879

    
880
            widthNode = Element('WIDTH')
881
            widthNode.text = str(self.size[0])
882
            node.append(widthNode)
883

    
884
            heightNode = Element('HEIGHT')
885
            heightNode.text = str(self.size[1])
886
            node.append(heightNode)
887

    
888
            areaNode = Element('AREA')
889
            areaNode.text = self.area
890
            node.append(areaNode)
891

    
892
            sceneNode = Element('SCENE')
893
            rect = self.sceneBoundingRect()
894
            sceneNode.text = '{},{},{},{}'.format(rect.x(), rect.y(), rect.width(), rect.height()) if self.scene() else \
895
                            '{},{},{},{}'.format(self.loc[0], self.loc[1], self.size[0], self.size[1])
896
            node.append(sceneNode)
897

    
898
        except Exception as ex:
899
            from App import App
900
            from AppDocData import MessageType
901

    
902
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
903
                                                           sys.exc_info()[-1].tb_lineno)
904
            App.mainWnd().addMessage.emit(MessageType.Error, message)
905

    
906
            return None
907

    
908
        return node
909

    
910
    def to_svg(self, parent) -> list:
911
        """convert text item to svg"""
912
        from xml.etree.ElementTree import Element, SubElement, dump, ElementTree
913
        from App import App
914

    
915
        res = []
916
        try:
917
            node = None
918
            if parent is None or str(self.owner) == str(parent):
919
                app_doc_data = AppDocData.instance()
920
                prj = app_doc_data.getCurrentProject()
921

    
922
                node = Element('g')
923
                node.attrib['id'] = str(self.uid)
924
                node.attrib['font-family'] = self.font().family()
925
                node.attrib['font-size'] = str(self.boundingRect().height())  # str(self.font().pointSizeF())
926
                node.attrib['font-weight'] = str(self.font().weight())
927

    
928
                inverted = QTransform()
929
                if parent:
930
                    trans = parent.sceneTransform()
931
                    inverted, _ = trans.inverted()
932

    
933
                trans = self.sceneTransform()
934

    
935
                trans = trans*inverted
936
                node.attrib['transform'] = f"matrix(" \
937
                                           f"{trans.m11()},{trans.m12()}," \
938
                                           f"{trans.m21()},{trans.m22()}," \
939
                                           f"{trans.m31()},{trans.m32()}" \
940
                                           f")"
941
                text = Element('text')
942
                text.attrib['textLength'] = str(self.boundingRect().width())
943
                text.attrib['lengthAdjust'] = 'spacingAndGlyphs'
944
                text.text = self.toPlainText()
945
                text.attrib['alignment-baseline'] = 'hanging'  # align left-top corner
946
                node.append(text)
947

    
948
                res.append(node)
949
        except Exception as ex:
950
            from App import App
951
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
952
                                                          sys.exc_info()[-1].tb_lineno)
953
            App.mainWnd().addMessage.emit(MessageType.Error, message)
954

    
955
        return res
956

    
957
    def findOwner(self, symbols):
958
        import math
959

    
960
        try:
961
            minDist = None
962
            selected = None
963

    
964
            configs = AppDocData.instance().getConfigs('Range', 'Detection Ratio')
965
            ratio = float(configs[0].value) if 1 == len(configs) else 1.5
966

    
967
            if len(self.text()) > 3:
968
                width = self.sceneBoundingRect().width()
969
            elif len(self.text()) == 3:
970
                width = self.sceneBoundingRect().width() * 4 / 3
971
            elif len(self.text()) == 2:
972
                width = self.sceneBoundingRect().width() * 2
973
            else:
974
                width = self.sceneBoundingRect().width() * 4
975
            dist = (self.sceneBoundingRect().height() + width) * ratio / 2
976
            center = self.sceneBoundingRect().center()
977

    
978
            target_symbols = []
979
            for symbol in symbols:
980
                attrs = symbol.getAttributes(findOwner=True)
981

    
982
                freeze = False
983
                for attr in attrs:
984
                    if attr.Freeze:
985
                        freeze = True
986
                        break
987
                if freeze: continue
988

    
989
                for attr in attrs:
990
                    ret = attr.Codes.find_match_exactly(self.text())
991
                    if ret:
992
                        target_symbols.append(symbol)
993
                        break
994

    
995
            for symbol in target_symbols:
996
                if issubclass(type(symbol), SymbolSvgItem):
997
                    dx = symbol.origin[0] - center.x()
998
                    dy = symbol.origin[1] - center.y()
999

    
1000
                    #offset = (symbol.sceneBoundingRect().width() + symbol.sceneBoundingRect().height()) / 4
1001
                    length = math.sqrt(dx * dx + dy * dy)# - offset
1002

    
1003
                    if (length < dist) and (minDist is None or length < minDist):
1004
                        minDist = length
1005
                        selected = symbol
1006

    
1007
            if selected is not None:
1008
                attrs = selected.getAttributes(findOwner=True)
1009
                target_attrs = []
1010
                for attr in attrs:
1011
                    ret = attr.Codes.find_match_exactly(self.text())
1012
                    if ret:
1013
                        target_attrs.append(attr)
1014

    
1015
                for target_attr in sorted(target_attrs, key=lambda param:int(param.AttrAt)):
1016
                    if target_attr.AssocItem is None and selected.add_assoc_item(self, at=int(target_attr.AttrAt)):
1017
                        target_attr.AssocItem = self
1018
                        self.owner = selected
1019
                        return ret
1020

    
1021
            return False
1022
        except Exception as ex:
1023
            from App import App
1024
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
1025
                                                           sys.exc_info()[-1].tb_lineno)
1026
            App.mainWnd().addMessage.emit(MessageType.Error, message)
1027

    
1028
    def setColor(self, color):
1029
        """change text color with given color"""
1030
        if QColor.isValidColor(color) and color.upper() != self.defaultTextColor().name().upper():
1031
            c = QColor()
1032
            c.setNamedColor(color)
1033
            self.setDefaultTextColor(c)
1034
            #self.update()
1035

    
1036
    def toSql(self):
1037
        """ convert text data to sql query for components and title block """
1038
        from AppDocData import AppDocData
1039

    
1040
        res = []
1041

    
1042
        appDocData = AppDocData.instance()
1043
        cols = ['UID', 'Drawings_UID', 'Symbol_UID', 'X', 'Y', 'Width', 'Height', 'Rotation', 'Area', 'Value', 'Owner',
1044
                'Connected', 'SpecialItemTypes_UID']
1045
        values = ['?', '?', "(select UID from Symbol where Name='Text' and SymbolType_UID=-1)", '?', '?', '?', '?', '?',
1046
                  '?', '?', '?', '?', '?']
1047

    
1048
        param = [
1049
            (str(self.uid), str(appDocData.activeDrawing.UID), self.loc[0], self.loc[1], self.size[0], self.size[1],
1050
             str(self.angle),
1051
             self.area, self.text(), \
1052
             str(self.owner) if self.owner else None, \
1053
             str(self.conns[0]) if self.conns else None, \
1054
             str(self.special_item_type) if self.special_item_type else None)]
1055
        sql = 'insert into Components({}) values({})'.format(','.join(cols), ','.join(values))
1056
        res.append((sql, tuple(param)))
1057

    
1058
        titleBlockProps = appDocData.getTitleBlockProperties()
1059
        if titleBlockProps:
1060
            cols = ['UID', 'Drawings_UID', 'TitleBlockProperties_UID', 'VALUE']
1061
            values = ['?', '?', '?', '?']
1062
            params = []
1063
            for titleBlockProp in titleBlockProps:
1064
                if self.area == titleBlockProp[0]:
1065
                    params.append((str(self.uid), appDocData.activeDrawing.UID, self.area, self.text()))
1066

    
1067
            sql = 'insert into TitleBlockValues({}) values({})'.format(','.join(cols), ','.join(values))
1068
            if params: res.append((sql, tuple(params)))
1069

    
1070
        return res
1071

    
1072

    
1073
'''
1074
    @brief      The class transfer pyqtSignal Event. Cause Subclass of QGraphicsRectItem can't use pyqtSignal
1075
    @author     Jeongwoo
1076
    @date       2018.06.18
1077
'''
1078

    
1079

    
1080
class Transfer(QObject):
1081
    onRemoved = pyqtSignal(QGraphicsItem)
1082

    
1083
    def __init__(self, parent=None):
1084
        QObject.__init__(self, parent)
클립보드 이미지 추가 (최대 크기: 500 MB)