프로젝트

일반

사용자정보

통계
| 개정판:

hytos / DTI_PID / DTI_PID / Shapes / EngineeringTextItem.py @ 0e17caa8

이력 | 보기 | 이력해설 | 다운로드 (43.2 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
    def validate(self):
193
        """ validation check """
194

    
195
        from EngineeringLineItem import QEngineeringLineItem
196

    
197
        errors = []
198

    
199
        try:
200
            app_doc_data = AppDocData.instance()
201
            dataPath = app_doc_data.getErrorItemSvgPath()
202

    
203
            # check overlapping
204
            texts = [item for item in self.scene().items() if item is not self and issubclass(type(item), QEngineeringTextItem)]
205
            for text in texts:
206
                textRect = text.sceneBoundingRect()
207
                rect1 = QRectF(textRect.left() - 3, textRect.top() - 3, textRect.width() + 6, textRect.height() + 6)
208
                rect2 = QRectF(textRect.left() + 3, textRect.top() + 3, textRect.width() - 6, textRect.height() - 6)
209
                if rect1.contains(self.sceneBoundingRect()) or rect2.contains(self.sceneBoundingRect().center()):
210
                    error = SymbolSvgItem.createItem('Error', None, dataPath)
211
                    error.parent = self
212
                    error.msg = self.tr('Text overlapping warning')
213
                    error.setToolTip(error.msg)
214
                    error.area = self.area
215
                    error.name = 'Warning'
216
                    error.items = [ text ]
217
                    errors.append(error)
218

    
219
                    error.setPosition([self.sceneBoundingRect().center().x(), self.sceneBoundingRect().center().y()])
220

    
221
            lines = [item for item in self.scene().items() if item is not self and issubclass(type(item), QEngineeringLineItem)]
222
            for line in lines:
223
                lineRect = line.sceneBoundingRect()
224
                if lineRect.intersects(self.sceneBoundingRect()):
225
                    error = SymbolSvgItem.createItem('Error', None, dataPath)
226
                    error.parent = self
227
                    error.msg = self.tr('Text overlapping waring')
228
                    error.setToolTip(error.msg)
229
                    error.area = self.area
230
                    error.name = 'Warning'
231
                    error.items = [ line ]
232
                    errors.append(error)
233

    
234
                    error.setPosition([self.sceneBoundingRect().center().x(), self.sceneBoundingRect().center().y()])
235
                            
236
        except Exception as ex:
237
            from App import App
238
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
239
                                                          sys.exc_info()[-1].tb_lineno)
240
            App.mainWnd().addMessage.emit(MessageType.Error, message)
241

    
242
        return errors
243

    
244
    def contextMenuEvent(self, event):
245
        items = self.scene().selectedItems()
246
        if len(items) > 0 and self in items:
247
            menu = QMenu()
248

    
249
            if len(items) > 1:
250
                mergeAction = QAction('Merge(M)', None)
251
                mergeAction.triggered.connect(self.contextMerge)
252
                menu.addAction(mergeAction)
253

    
254
            if len(items) == 1:
255
                editAction = QAction('Edit(Return)', None)
256
                editAction.triggered.connect(self.contextEdit)
257
                menu.addAction(editAction)
258

    
259
                rotateAction = QAction('Rotate(R)', None)
260
                rotateAction.triggered.connect(self.contextRotate)
261
                menu.addAction(rotateAction)
262

    
263
            if len(items) >= 1:
264
                trimAction = QAction('Remove Space', None)
265
                trimAction.triggered.connect(self.contextTrim)
266
                menu.addAction(trimAction)
267

    
268
            deleteAction = QAction('Delete(E)', None)
269
            deleteAction.triggered.connect(self.contextDelete)
270
            menu.addAction(deleteAction)
271

    
272
            menu.exec_(event.screenPos())
273

    
274
    def contextTrim(self):
275
        textItems = [item for item in self.scene().selectedItems() if type(item) is QEngineeringTextItem]
276

    
277
        for text in textItems:
278
            text.setPlainText(text.text().replace(' ', ''))
279

    
280
    def contextMerge(self):
281
        event = QKeyEvent(QEvent.KeyPress, Qt.Key_M, Qt.NoModifier)
282
        self.keyPressEvent(event)
283

    
284
    def contextDelete(self):
285
        event = QKeyEvent(QEvent.KeyPress, Qt.Key_Delete, Qt.NoModifier)
286
        self.scene().keyPressEvent(event)
287

    
288
    def contextEdit(self):
289
        event = QKeyEvent(QEvent.KeyPress, Qt.Key_Return, Qt.NoModifier)
290
        self.keyPressEvent(event)
291

    
292
    def contextRotate(self):
293
        self.textRotate()
294

    
295
    def textRotate(self):
296
        # degree 0
297
        if 0 == self.angle:
298
            self.angle = 1.57
299
        # degree 90
300
        elif 1.57 == self.angle:
301
            self.angle = 3.14
302
        # degree 180
303
        elif 3.14 == self.angle:
304
            self.angle = 4.71
305
        # degree 270
306
        elif 4.71 == self.angle:
307
            self.angle = 0
308

    
309
        width = self.size[0]
310
        height = self.size[1]
311
        self.size = [height, width]
312

    
313
        self.rotate()
314

    
315
    def keyPressEvent(self, event):
316
        #print('item :' + str(event.key()))
317
        if Qt.Key_Return == event.key():
318
            self.edit_text()
319
        elif event.key() == Qt.Key_Up:  # translate up/down/left/right
320
            modifiers = QApplication.keyboardModifiers()
321
            delta = 10 if modifiers == Qt.ControlModifier else 1
322

    
323
            trans = QTransform()
324
            trans.rotateRadians(self.angle)
325
            dx, dy = trans.map(0, -1)
326
            self.loc[1] = self.loc[1] - delta
327

    
328
            trans = self.transform()
329
            trans.translate(dx*delta, dy*delta)
330
            self.setTransform(trans)
331
        elif event.key() == Qt.Key_Down:
332
            modifiers = QApplication.keyboardModifiers()
333
            delta = 10 if modifiers == Qt.ControlModifier else 1
334

    
335
            trans = QTransform()
336
            trans.rotateRadians(self.angle)
337
            dx, dy = trans.map(0, 1)
338
            self.loc[1] = self.loc[1] + delta
339

    
340
            trans = self.transform()
341
            trans.translate(dx * delta, dy * delta)
342
            self.setTransform(trans)
343
        elif event.key() == Qt.Key_Left:
344
            modifiers = QApplication.keyboardModifiers()
345
            delta = 10 if modifiers == Qt.ControlModifier else 1
346

    
347
            trans = QTransform()
348
            trans.rotateRadians(self.angle)
349
            dx, dy = trans.map(-1, 0)
350
            self.loc[0] = self.loc[0] - delta
351

    
352
            trans = self.transform()
353
            trans.translate(dx * delta, dy * delta)
354
            self.setTransform(trans)
355
        elif event.key() == Qt.Key_Right:
356
            modifiers = QApplication.keyboardModifiers()
357
            delta = 10 if modifiers == Qt.ControlModifier else 1
358

    
359
            trans = QTransform()
360
            trans.rotateRadians(self.angle)
361
            dx, dy = trans.map(1, 0)
362
            self.loc[0] = self.loc[0] + delta
363

    
364
            trans = self.transform()
365
            trans.translate(dx * delta, dy * delta)
366
            self.setTransform(trans)
367
        elif event.key() == Qt.Key_M:
368
            from App import App
369

    
370
            App.mainWnd().keyPressEvent(event)
371

    
372
        # QGraphicsTextItem.keyPressEvent(self, event)
373

    
374
    def mouseMoveEvent(self, event):
375
        modifiers = QApplication.keyboardModifiers()
376
        if modifiers == Qt.ShiftModifier:
377
            super().mouseMoveEvent(event)
378
    
379
    def itemChange(self, change, value):
380
        """ call signals when item's position or rotation is changed """
381
        if not self.scene(): return super().itemChange(change, value)
382

    
383
        if change == QGraphicsItem.ItemPositionHasChanged:
384

    
385
            scene_origin = self.sceneBoundingRect()
386
            configs = AppDocData.instance().getConfigs('Data', 'Grid')
387
            grid = int(configs[0].value) if 1 == len(configs) else -1
388
            if grid == 1:
389
                self.loc = [round(scene_origin.x()), round(scene_origin.y())]
390
            else:
391
                self.loc = [round(scene_origin.x(), 1), round(scene_origin.y(), 1)]
392

    
393
            if hasattr(self.scene(), 'contents_changed'):
394
                self.scene().contents_changed.emit()
395

    
396
            return value
397

    
398
        return super().itemChange(change, value)
399

    
400
    '''
401
        @brief  draw rect when item is selected
402
        @author humkyung
403
        @date   2018.07.08
404
    '''
405
    def drawFocusRect(self, painter):
406
        if not hasattr(self, '_focus_pen'):
407
            self._focus_pen = QPen(Qt.DotLine)
408
            self._focus_pen.setColor(Qt.black)
409
            self._focus_pen.setWidthF(1.5)
410

    
411
        if not hasattr(self, '_hilight_color'):
412
            self._hilight_color = QColor(255, 0, 0, 127)
413
        painter.setBrush(QBrush(self._hilight_color))
414

    
415
        painter.setPen(self._focus_pen)
416
        painter.drawRect(self.text_size)
417

    
418
    '''
419
        @brief  override paint(draw connection points)
420
        @author humkyung
421
        @date   2018.07.08
422
    '''
423
    def paint(self, painter, options=None, widget=None):
424
        import math
425

    
426
        painter.setClipRect(options.exposedRect)
427

    
428
        self.setColor(self.getColor())
429

    
430
        #if self.angle == 1.57 or self.angle == 4.71:
431
        #    rect = QRectF(0, 0, self.size[1], self.size[0])
432
        #else:
433
        #    rect = QRectF(0, 0, self.size[0], self.size[1])
434

    
435
        # draw onwer indicator
436
        if self.owner and issubclass(type(self.owner), QEngineeringAbstractItem):
437
            painter.setPen(Qt.magenta)
438
            rect = self.text_size
439
            center = self.sceneBoundingRect().center()
440
            center2 = self.owner.sceneBoundingRect().center()
441
            dx = center2.x() - center.x()
442
            dy = center2.y() - center.y()
443
            if self.angle == 1.57:
444
                rad = 90 * (math.pi / 180.0)
445
                nx = math.cos(rad) * dx - math.sin(rad) * dy
446
                ny = math.sin(rad) * dx + math.cos(rad) * dy
447
                dx, dy = nx, ny
448
            painter.drawLine(rect.center().x(), rect.center().y(), rect.center().x() + dx, rect.center().y() + dy)
449

    
450
        painter.setFont(self.font())
451
        color = self.defaultTextColor()
452

    
453
        # draw white background during hover
454
        if self.hover:
455
            configs = AppDocData.instance().getConfigs('Text', 'Background')
456
            if not configs or not int(configs[0].value) != 1:
457
                painter.setPen(Qt.NoPen)
458
                painter.setBrush(Qt.white)
459
                painter.drawRect(self.text_size)
460
        # up to here
461

    
462
        painter.setPen(QPen(color))
463

    
464
        if self.isSelected():
465
            self.drawFocusRect(painter)
466

    
467
        #painter.drawText(rect, Qt.AlignCenter, self.text())
468
        QGraphicsTextItem.paint(self, painter, options, widget)
469

    
470
    '''
471
        @brief      Return real item position
472
        @author     Jeongwoo
473
        @date       2018.05.25
474
    '''
475

    
476
    def boundingRectOnScene(self):
477
        rect = self.text_size
478
        rect.moveTo(self.loc[0], self.loc[1])
479
        return rect
480

    
481
    @staticmethod
482
    def create_text_with(scene, text_info):
483
        """create a text using text_info"""
484
        from TextItemFactory import TextItemFactory
485

    
486
        x = text_info.getX()
487
        y = text_info.getY()
488
        angle = text_info.getAngle()
489
        text = text_info.getText()
490
        if not text.replace(' ', '').replace('\n', ''):
491
            return None
492
        width = text_info.getW()
493
        height = text_info.getH()
494

    
495
        configs = AppDocData.instance().getConfigs('Text Style', 'Font Size')
496
        fontSize = int(configs[0].value) if configs else -1
497
        if fontSize != -1:
498
            textItems = [item for item in scene.items() if issubclass(type(item), QEngineeringTextItem)]
499
            if textItems:
500
                _height = textItems[0].size[1] if textItems[0].angle == 0 else textItems[0].size[0]
501
                if angle == 0:
502
                    height = _height
503
                else:
504
                    width = _height
505

    
506
        item = TextItemFactory.instance().createTextItem(text_info)
507
        if item is not None:
508
            item.loc = [x, y]
509
            item.size = (width, height)
510
            item.angle = angle
511
            item.addTextItemToScene(scene)
512

    
513
        return item
514

    
515
    def edit_text(self):
516
        """edit text by using ocr dialog"""
517
        from TextItemFactory import TextItemFactory
518
        from OcrResultDialog import QOcrResultDialog
519

    
520
        item = None
521
        try:
522
            dialog = QOcrResultDialog(None, self.scene().views()[0].image().copy(self.loc[0], self.loc[1],
523
                                                                               self.size[0], self.size[1]),
524
                                      QRect(self.loc[0], self.loc[1], self.size[0], self.size[1]), text_item=self)
525
            (res, textInfoList) = dialog.showDialog()
526

    
527
            if QDialog.Accepted == res and textInfoList:
528
                # create new texts
529
                for text_info in textInfoList:
530
                    item = QEngineeringTextItem.create_text_with(self.scene(), text_info)
531
                    if item:
532
                        item.area = self.area
533
                        item.transfer.onRemoved.connect(self.transfer.onRemoved)
534
                        #item.transfer.onRemoved.connect(App.mainWnd().itemRemoved)
535

    
536
                self.transfer.onRemoved.emit(self)
537
        except Exception as ex:
538
            from App import App
539
            from AppDocData import MessageType
540

    
541
            message = 'error occurred({}-{}) in {}:{}'.format(repr(ex), self.text(),
542
                                                              sys.exc_info()[-1].tb_frame.f_code.co_filename,
543
                                                              sys.exc_info()[-1].tb_lineno)
544
            App.mainWnd().addMessage.emit(MessageType.Error, message)
545

    
546
        return item
547

    
548
    def focusOutEvent(self, event: QFocusEvent) -> None:
549
        self.setTextInteractionFlags(Qt.NoTextInteraction)
550
        super(QEngineeringTextItem, self).focusOutEvent(event)
551

    
552
    '''
553
        @brief      Double click event, Show QOcrResultDialog
554
        @author     Jeongwoo
555
        @date       18.04.23
556
        @history    18.06.20    Jeongwoo    Resize QRect added 1
557
    '''
558

    
559
    def mouseDoubleClickEvent(self, event):
560
        if event.buttons() == Qt.LeftButton:
561
            """
562
            if self.textInteractionFlags() == Qt.NoTextInteraction:
563
                self.setTextInteractionFlags(Qt.TextEditorInteraction)
564
                self.setFocus()
565
            """
566
            self.edit_text()
567
        super(QGraphicsTextItem, self).mouseDoubleClickEvent(event)
568

    
569
    '''
570
        @brief      rotate text
571
        @author     humkyung
572
        @date       2018.08.18
573
    '''
574

    
575
    def rotate(self):
576
        sx = 1
577
        sy = 1
578
        width = self.size[0]
579
        height = self.size[1]
580
        x = self.loc[0]
581
        y = self.loc[1]
582

    
583
        transform = QTransform()
584
        if (1.57 == self.angle) or (4.71 == self.angle):
585
            rect = self.text_size
586
            sx = width / rect.height()
587
            sy = height / rect.width()
588

    
589
            transform.translate(x, y)
590
            transform.translate(width * 0.5, height * 0.5)
591
            transform.scale(1, sy)
592
            transform.rotateRadians(-self.angle)
593
            transform.translate(-rect.width() * 0.5, -rect.height() * 0.5)
594
        elif 3.14 == self.angle:
595
            rect = self.text_size
596
            sx = width / rect.width()
597
            sy = height / rect.height()
598

    
599
            transform.translate(x, y - round((rect.height() - height) * 0.5))
600
            transform.scale(sx, 1)
601
            transform.rotateRadians(-self.angle)
602
            transform.translate(-width * 0.5, -height * 0.5)
603
        else:
604
            rect = self.text_size
605
            sx = width / rect.width()
606
            sy = height / rect.height()
607

    
608
            # if '\n' not in text:
609
            transform.translate(x, y - round((rect.height() - height) * 0.5))
610
            transform.scale(sx, 1)
611

    
612
        self.setTransform(transform)
613
        self.update()
614

    
615
    def text_width(self, font):
616
        """return text width"""
617
        text_width = 0
618
        for _text in self.text().split('\n'):
619
            """
620
            rect = QFontMetricsF(font).tightBoundingRect(_text)
621
            text_width = max(rect.width(), text_width)
622
            """
623
            text_width = max(QFontMetricsF(font).width(_text), text_width)
624

    
625
        return text_width
626

    
627
    def update_shape(self):
628
        """update text shape"""
629

    
630
        try:
631
            app_doc_data = AppDocData.instance()
632
            configs = app_doc_data.getConfigs('Text Style', 'Font Name')
633
            fontName = configs[0].value if configs else 'Arial'
634
            configs = app_doc_data.getConfigs('Text Style', 'Font Size')
635
            fontSize = int(configs[0].value) if configs else -1
636

    
637
            sx = 1
638
            sy = 1
639
            width = self.size[0]
640
            height = self.size[1]
641
            x = self.loc[0]
642
            y = self.loc[1]
643
            rect = None
644
            line_count = self.text().count('\n') if self.text().count('\n') is not 0 else 1
645

    
646
            allowed_error = 0.01
647
            if abs(self.angle - 1.57) < allowed_error:
648
                self.angle = 1.57
649
            elif abs(self.angle - 4.71) < allowed_error:
650
                self.angle = 4.71
651
            elif abs(self.angle - 3.14) < allowed_error:
652
                self.angle = 3.14
653
            else:
654
                self.angle = 0
655

    
656
            transform = QTransform()
657
            if abs(self.angle - 1.57) < allowed_error or abs(self.angle - 4.71) < allowed_error:
658
                font = QFont(fontName, width if fontSize == -1 else fontSize)
659
                font.setBold(True)
660

    
661
                x_factor = width / QFontMetricsF(font).height() #self.text()).height() / line_count)
662
                y_factor = height / self.text_width(font)
663
                factor = min(x_factor, y_factor)
664
                font.setPointSizeF(font.pointSizeF() * factor)
665
                self.setFont(font)
666

    
667
                rect = self.text_size
668
                sx = rect.height() / QFontMetricsF(font).tightBoundingRect(self.text()).height()
669
                sy = rect.width() / self.text_width(font)
670

    
671
                transform.translate(x + width * 0.5, y + height * 0.5)
672
                transform.rotateRadians(-self.angle)
673
                #transform.scale(1, sy)
674
                transform.translate(-rect.width() * 0.5, -rect.height() * 0.5)
675
            elif abs(self.angle - 3.14) < allowed_error:
676
                font = QFont(fontName, height if fontSize == -1 else fontSize)
677
                font.setBold(True)
678

    
679
                x_factor = width / self.text_width(font)
680
                y_factor = height / QFontMetricsF(font).height() #self.text()).height() / line_count)
681
                factor = min(x_factor, y_factor)
682
                font.setPointSizeF(font.pointSizeF() * factor)
683
                self.setFont(font)
684

    
685
                rect = self.text_size
686
                sx = width / self.text_width(font)
687
                sy = height / QFontMetricsF(font).tightBoundingRect(self.text()).height()
688

    
689
                transform.translate(x, y - round((rect.height() - height) * 0.5))
690
                #transform.scale(sx, 1)
691
                transform.rotateRadians(-self.angle)
692
                transform.translate(-width * 0.5, -height * 0.5)
693
            else:
694
                font = QFont(fontName, height if fontSize == -1 else fontSize)
695
                font.setBold(True)
696

    
697
                x_factor = width / self.text_width(font)
698
                y_factor = height / QFontMetricsF(font).height() #.boun(self.text()).height() / line_count)
699
                factor = min(x_factor, y_factor)
700
                font.setPointSizeF(font.pointSizeF() * factor)
701
                self.setFont(font)
702

    
703
                sx = width / self.text_width(font)
704
                sy = height / QFontMetricsF(font).tightBoundingRect(self.text()).height()
705

    
706
                transform.translate(x, y)
707

    
708
            self.setTransform(transform)
709

    
710
            self.document().setDocumentMargin(0)
711
        except Exception as ex:
712
            from App import App
713
            from AppDocData import MessageType
714

    
715
            message = 'error occurred({}-{}) in {}:{}'.format(ex, self.text(),
716
                                                              sys.exc_info()[-1].tb_frame.f_code.co_filename,
717
                                                              sys.exc_info()[-1].tb_lineno)
718
            App.mainWnd().addMessage.emit(MessageType.Error, message)
719

    
720
    '''
721
        @brief      Put text on scene
722
        @author     Jeongwoo
723
        @date       18.04.23
724
        @history    humkyung 2018.06.30 apply font configuration
725
    '''
726
    def addTextItemToScene(self, scene):
727
        try:
728
            self.update_shape()
729
            """
730
            white_char_list = app_doc_data.getConfigs('Text Recognition', 'White Character List')
731
            self.highlighter = Highlighter(self.document())
732
            self.highlighter.white_char_list = '‘' #white_char_list[0].value if white_char_list else None
733
            """
734

    
735
            scene.addItem(self)
736
        except Exception as ex:
737
            from App import App
738
            from AppDocData import MessageType
739

    
740
            message = 'error occurred({}-{}) in {}:{}'.format(ex, self.text(),
741
                                                              sys.exc_info()[-1].tb_frame.f_code.co_filename,
742
                                                              sys.exc_info()[-1].tb_lineno)
743
            App.mainWnd().addMessage.emit(MessageType.Error, message)
744

    
745
    '''
746
        @brief  get connected items
747
        @author humkyung
748
        @date   2018.04.23
749
        @history    2018.11.22      euisung     fix note road
750
    '''
751

    
752
    def getConnectedItems(self):
753
        visited = []
754

    
755
        try:
756
            if 1 == len(self.conns):
757
                # iterate connected items
758
                pool = []
759
                visited = []
760
                pool.append(self.conns[0])
761
                while len(pool) > 0:
762
                    it = pool.pop()
763
                    visited.append(it)
764
                    for conn in it.conns:
765
                        if (conn is not None) and (conn not in visited): pool.append(conn)
766
                # up to here
767
        except Exception as ex:
768
            print('error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
769
                                                       sys.exc_info()[-1].tb_lineno))
770

    
771
        return visited
772

    
773
    @staticmethod
774
    def from_database(component):
775
        """ get text item from database """
776
        import uuid
777
        from AppDocData import AppDocData
778
        from TextItemFactory import TextItemFactory
779
        from SymbolAttr import SymbolAttr
780

    
781
        item = None
782

    
783
        try:
784
            x = float(component['X'])
785
            y = float(component['Y'])
786
            width = float(component['Width']) if component['Width'] is not None else 0
787
            height = float(component['Height']) if component['Height'] is not None else 0
788
            if height < 10:
789
                height = 10
790
            if width < 10:
791
                width = 10
792
            angle = float(component['Rotation']) if component['Rotation'] is not None else 0
793
            text = component['Value']
794

    
795
            configs = AppDocData.instance().getConfigs('Data', 'Grid')
796
            grid = int(configs[0].value) if 1 == len(configs) else -1
797
            if grid == 1:
798
                x = round(x)
799
                y = round(y)
800
                width = round(width)
801
                height = round(height)
802
                
803
            textInfo = TextInfo(text, x, y, width, height, angle)
804
            connline = component['Connected'] if component['Connected'] is not None else None
805

    
806
            item = TextItemFactory.instance().createTextItem(textInfo)
807
            if item is not None:
808
                item.setVisible(False)
809
                item.uid = uuid.UUID(component['UID'])
810
                item.loc = [x, y]
811
                item.size = [width, height]
812
                item.angle = angle
813
                #item.setToolTip('<b>{}</b><br>LINE NO={}'.format(str(item.uid), text))
814

    
815
                if component['Owner'] and component['Owner'] != 'None':
816
                    item._owner = uuid.UUID(component['Owner'])
817

    
818
                # assign area
819
                if not component['Area']:
820
                    app_doc_data = AppDocData.instance()
821
                    for area in app_doc_data.getAreaList():
822
                        if area.contains([x, y]):
823
                            item.area = area.name
824
                            break
825
                else:
826
                    item.area = component['Area']
827
                ## up to here
828

    
829
                """ apply freeze value """
830
                # item.freeze_item.update_freeze(item.prop('Freeze'))
831

    
832
                if connline is not None:
833
                    item.conns.append(connline)
834
        except Exception as ex:
835
            from App import App
836
            from AppDocData import MessageType
837

    
838
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
839
                                                           sys.exc_info()[-1].tb_lineno)
840
            App.mainWnd().addMessage.emit(MessageType.Error, message)
841
            return None
842

    
843
        return item
844

    
845
    '''
846
        @brief      parse xml code
847
        @author     humkyung
848
        @date       2018.09.15
849
    '''
850

    
851
    @staticmethod
852
    def fromXml(node):
853
        import uuid
854
        from TextItemFactory import TextItemFactory
855
        from AppDocData import AppDocData
856
        from EngineeringNoteItem import QEngineeringNoteItem
857
        from QEngineeringSizeTextItem import QEngineeringSizeTextItem
858
        from EngineeringValveOperCodeTextItem import QEngineeringValveOperCodeTextItem
859

    
860
        item = None
861

    
862
        try:
863
            location = node.find('LOCATION').text if node.find('LOCATION') is not None else '0,0'
864
            x = float(location.split(',')[0])
865
            y = float(location.split(',')[1])
866
            width = float(node.find('WIDTH').text) if node.find('WIDTH') is not None else 0
867
            height = float(node.find('HEIGHT').text) if node.find('HEIGHT') is not None else 0
868
            if height < 10:
869
                height = 10
870
            if width < 10:
871
                width = 10
872
            angle = float(node.find('ANGLE').text) if node.find('ANGLE') is not None else 0
873
            value = node.find('VALUE').text
874
            if not value or len(value.replace('\n', '').replace(' ', '')) == 0:
875
                return None
876
            # attributeValue = node.find('ATTRIBUTEVALUE')
877
            name = node.find('NAME').text
878

    
879
            configs = AppDocData.instance().getConfigs('Data', 'Grid')
880
            grid = int(configs[0].value) if 1 == len(configs) else -1
881
            if grid == 1:
882
                x = round(x)
883
                y = round(y)
884
                width = round(width)
885
                height = round(height)
886

    
887
            textInfo = TextInfo(value, x, y, width, height, angle)
888

    
889
            item = TextItemFactory.instance().createTextItem(textInfo)
890
            if item is not None:
891
                item.loc = [x, y]
892
                item.size = [width, height]
893
                item.angle = angle
894

    
895
            # set uid and owner of item
896
            if item is not None:
897
                item.uid = uuid.UUID(node.find('UID').text)
898
                item.setVisible(False)
899

    
900
                if node.find('OWNER') is not None and node.find('OWNER').text != 'None':
901
                    item._owner = uuid.UUID(node.find('OWNER').text)
902

    
903
            ## assign area
904
            if item is not None:
905
                if node.find('AREA') is None:
906
                    appDocData = AppDocData.instance()
907
                    for area in appDocData.getAreaList():
908
                        if area.contains([x, y]):
909
                            item.area = area.name
910
                            break
911
                else:
912
                    item.area = node.find('AREA').text
913
                    ## up to here
914
        except Exception as ex:
915
            from App import App
916
            from AppDocData import MessageType
917

    
918
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
919
                                                           sys.exc_info()[-1].tb_lineno)
920
            App.mainWnd().addMessage.emit(MessageType.Error, message)
921

    
922
        return item
923

    
924
    '''
925
        @brief      generate xml code
926
        @author     humkyung
927
        @date       2018.04.23
928
        @history    humkyung 2018.04.27 move to QEngineeringLineNoTextItem
929
                    humkyung 2018.05.02 add name as parameter
930
                    Jeongwoo 2018.05.30 Change variable [owner] is nullable and Add/Modify attributes
931
    '''
932

    
933
    def toXml(self, owner=None):
934
        from xml.etree.ElementTree import Element, SubElement, dump, ElementTree
935
        from EngineeringLineItem import QEngineeringLineItem
936
        from SymbolSvgItem import SymbolSvgItem
937

    
938
        try:
939
            node = Element('ATTRIBUTE')
940
            uidNode = Element('UID')
941
            uidNode.text = str(self.uid)
942
            node.append(uidNode)
943

    
944
            # write owner's uid to xml
945
            ownerNode = Element('OWNER')
946
            if self.owner is not None:
947
                ownerNode.text = str(self.owner)
948
            else:
949
                ownerNode.text = 'None'
950
            node.append(ownerNode)
951
            # up to here
952

    
953
            attributeValueNode = Element('ATTRIBUTEVALUE')
954
            attributeValueNode.text = self.attribute
955
            node.append(attributeValueNode)
956

    
957
            nameNode = Element('NAME')
958
            nameNode.text = self.type
959
            node.append(nameNode)
960

    
961
            locNode = Element('LOCATION')
962
            locNode.text = '{},{}'.format(self.loc[0], self.loc[1])
963
            node.append(locNode)
964

    
965
            valueNode = Element('VALUE')
966
            valueNode.text = self.text()
967
            node.append(valueNode)
968

    
969
            angleNode = Element('ANGLE')
970
            angleNode.text = str(self.angle)
971
            node.append(angleNode)
972

    
973
            widthNode = Element('WIDTH')
974
            widthNode.text = str(self.size[0])
975
            node.append(widthNode)
976

    
977
            heightNode = Element('HEIGHT')
978
            heightNode.text = str(self.size[1])
979
            node.append(heightNode)
980

    
981
            areaNode = Element('AREA')
982
            areaNode.text = self.area
983
            node.append(areaNode)
984

    
985
            sceneNode = Element('SCENE')
986
            rect = self.sceneBoundingRect()
987
            sceneNode.text = '{},{},{},{}'.format(rect.x(), rect.y(), rect.width(), rect.height()) if self.scene() else \
988
                            '{},{},{},{}'.format(self.loc[0], self.loc[1], self.size[0], self.size[1])
989
            node.append(sceneNode)
990

    
991
        except Exception as ex:
992
            from App import App
993
            from AppDocData import MessageType
994

    
995
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
996
                                                           sys.exc_info()[-1].tb_lineno)
997
            App.mainWnd().addMessage.emit(MessageType.Error, message)
998

    
999
            return None
1000

    
1001
        return node
1002

    
1003
    def to_svg(self, parent) -> list:
1004
        """convert text item to svg"""
1005
        from xml.etree.ElementTree import Element, SubElement, dump, ElementTree
1006
        from App import App
1007

    
1008
        res = []
1009
        try:
1010
            node = None
1011
            if parent is None or str(self.owner) == str(parent):
1012
                app_doc_data = AppDocData.instance()
1013
                prj = app_doc_data.getCurrentProject()
1014

    
1015
                node = Element('g')
1016
                node.attrib['id'] = str(self.uid)
1017
                node.attrib['font-family'] = self.font().family()
1018
                node.attrib['font-size'] = str(self.boundingRect().height())  # str(self.font().pointSizeF())
1019
                node.attrib['font-weight'] = str(self.font().weight())
1020

    
1021
                inverted = QTransform()
1022
                if parent:
1023
                    trans = parent.sceneTransform()
1024
                    inverted, _ = trans.inverted()
1025

    
1026
                trans = self.sceneTransform()
1027

    
1028
                trans = trans*inverted
1029
                node.attrib['transform'] = f"matrix(" \
1030
                                           f"{trans.m11()},{trans.m12()}," \
1031
                                           f"{trans.m21()},{trans.m22()}," \
1032
                                           f"{trans.m31()},{trans.m32()}" \
1033
                                           f")"
1034
                text = Element('text')
1035
                text.attrib['textLength'] = str(self.boundingRect().width())
1036
                text.attrib['lengthAdjust'] = 'spacingAndGlyphs'
1037
                text.text = self.toPlainText()
1038
                text.attrib['alignment-baseline'] = 'hanging'  # align left-top corner
1039
                node.append(text)
1040

    
1041
                res.append(node)
1042
        except Exception as ex:
1043
            from App import App
1044
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
1045
                                                          sys.exc_info()[-1].tb_lineno)
1046
            App.mainWnd().addMessage.emit(MessageType.Error, message)
1047

    
1048
        return res
1049

    
1050
    def findOwner(self, symbols, force=False):
1051
        import math
1052

    
1053
        try:
1054
            minDist = None
1055
            selected = None
1056

    
1057
            configs = AppDocData.instance().getConfigs('Range', 'Detection Ratio')
1058
            if not force:
1059
                ratio = float(configs[0].value) if 1 == len(configs) else 1.5
1060
            else:
1061
                ratio = 50
1062

    
1063
            if len(self.text()) > 3:
1064
                width = self.sceneBoundingRect().width()
1065
            elif len(self.text()) == 3:
1066
                width = self.sceneBoundingRect().width() * 4 / 3
1067
            elif len(self.text()) == 2:
1068
                width = self.sceneBoundingRect().width() * 2
1069
            else:
1070
                width = self.sceneBoundingRect().width() * 4
1071
            dist = (self.sceneBoundingRect().height() + width) * ratio / 2
1072
            center = self.sceneBoundingRect().center()
1073

    
1074
            target_symbols = []
1075
            for symbol in symbols:
1076
                attrs = symbol.getAttributes(findOwner=True)
1077

    
1078
                freeze = False
1079
                for attr in attrs:
1080
                    if attr.Freeze:
1081
                        freeze = True
1082
                        break
1083
                if freeze and not force: continue
1084

    
1085
                for attr in attrs:
1086
                    ret = attr.Codes.find_match_exactly(self.text())
1087
                    if ret:
1088
                        target_symbols.append(symbol)
1089
                        break
1090

    
1091
            for symbol in target_symbols:
1092
                if issubclass(type(symbol), SymbolSvgItem):
1093
                    dx = symbol.origin[0] - center.x()
1094
                    dy = symbol.origin[1] - center.y()
1095

    
1096
                    #offset = (symbol.sceneBoundingRect().width() + symbol.sceneBoundingRect().height()) / 4
1097
                    length = math.sqrt(dx * dx + dy * dy)# - offset
1098

    
1099
                    if (length < dist) and (minDist is None or length < minDist):
1100
                        minDist = length
1101
                        selected = symbol
1102

    
1103
            if selected is not None:
1104
                attrs = selected.getAttributes(findOwner=True)
1105
                target_attrs = []
1106
                for attr in attrs:
1107
                    ret = attr.Codes.find_match_exactly(self.text())
1108
                    if ret:
1109
                        target_attrs.append(attr)
1110

    
1111
                for target_attr in sorted(target_attrs, key=lambda param:int(param.AttrAt)):
1112
                    if target_attr.AssocItem is None and selected.add_assoc_item(self, at=int(target_attr.AttrAt), force=force):
1113
                        target_attr.AssocItem = self
1114
                        self.owner = selected
1115
                        if force:
1116
                            selected.getAttributes()
1117
                            for neyKey in selected.attrs.keys():
1118
                                if neyKey.Attribute == target_attr.Attribute:
1119
                                    neyKey.Freeze = True
1120
                                    break
1121
                        return ret
1122

    
1123
            return False
1124
        except Exception as ex:
1125
            from App import App
1126
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
1127
                                                           sys.exc_info()[-1].tb_lineno)
1128
            App.mainWnd().addMessage.emit(MessageType.Error, message)
1129

    
1130
    def setColor(self, color):
1131
        """change text color with given color"""
1132
        if QColor.isValidColor(color) and color.upper() != self.defaultTextColor().name().upper():
1133
            c = QColor()
1134
            c.setNamedColor(color)
1135
            self.setDefaultTextColor(c)
1136
            #self.update()
1137

    
1138
    def toSql(self):
1139
        """ convert text data to sql query for components and title block """
1140
        from AppDocData import AppDocData
1141

    
1142
        res = []
1143

    
1144
        appDocData = AppDocData.instance()
1145
        cols = ['UID', 'Drawings_UID', 'Symbol_UID', 'X', 'Y', 'Width', 'Height', 'Rotation', 'Area', 'Value', 'Owner',
1146
                'Connected', 'SpecialItemTypes_UID']
1147
        values = ['?', '?', "(select UID from Symbol where Name='Text' and SymbolType_UID=-1)", '?', '?', '?', '?', '?',
1148
                  '?', '?', '?', '?', '?']
1149

    
1150
        param = [
1151
            (str(self.uid), str(appDocData.activeDrawing.UID), self.loc[0], self.loc[1], self.size[0], self.size[1],
1152
             str(self.angle),
1153
             self.area, self.text(), \
1154
             str(self.owner) if self.owner else None, \
1155
             str(self.conns[0]) if self.conns else None, \
1156
             str(self.special_item_type) if self.special_item_type else None)]
1157
        sql = 'insert into Components({}) values({})'.format(','.join(cols), ','.join(values))
1158
        res.append((sql, tuple(param)))
1159

    
1160
        titleBlockProps = appDocData.getTitleBlockProperties()
1161
        if titleBlockProps:
1162
            cols = ['UID', 'Drawings_UID', 'TitleBlockProperties_UID', 'VALUE']
1163
            values = ['?', '?', '?', '?']
1164
            params = []
1165
            for titleBlockProp in titleBlockProps:
1166
                if self.area == titleBlockProp[0]:
1167
                    params.append((str(self.uid), appDocData.activeDrawing.UID, self.area, self.text()))
1168

    
1169
            sql = 'insert into TitleBlockValues({}) values({})'.format(','.join(cols), ','.join(values))
1170
            if params: res.append((sql, tuple(params)))
1171

    
1172
        return res
1173

    
1174

    
1175
'''
1176
    @brief      The class transfer pyqtSignal Event. Cause Subclass of QGraphicsRectItem can't use pyqtSignal
1177
    @author     Jeongwoo
1178
    @date       2018.06.18
1179
'''
1180

    
1181

    
1182
class Transfer(QObject):
1183
    onRemoved = pyqtSignal(QGraphicsItem)
1184

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