프로젝트

일반

사용자정보

개정판 599b9bc1

ID599b9bc1b713f98dddc10b53fcc1f25df083ad21
상위 f7adec0d
하위 ff3d1c7a

김정우 이(가) 약 7년 전에 추가함

SG_DbHelper 및 SymbolGenerator python 파일 삭제

차이점 보기:

DTI_PID/DTI_PID/SG_DbHelper.py
1
import sqlite3
2
import os
3
import sys
4
import symbol
5
import SymbolBase
6
from sqlite3 import Error
7
from AppDocData import AppDocData
8

  
9

  
10
#############################################################
11
#           NOT USED
12
#############################################################
13

  
14

  
15
class SG_DbHelper():
16
    ROOT_DIR = ""
17
    DB_PATH = "db"
18
    DB_NAME = "ITI_PID.db"
19
    
20
    CREATE_SYMBOLS_TABLE_SQL = '''
21
        CREATE TABLE IF NOT EXISTS 'Symbol' (
22
            'uid'	INTEGER PRIMARY KEY AUTOINCREMENT,
23
	        'symId'	INTEGER NOT NULL,
24
	        'name'	TEXT NOT NULL,
25
	        'type'	TEXT,
26
	        'threshold'	NUMERIC NOT NULL DEFAULT 0.4,
27
	        'minMatchPoint'	INTEGER NOT NULL DEFAULT 0,
28
	        'isDetectOrigin'	INTEGER NOT NULL DEFAULT 0,
29
	        'rotationCount'	INTEGER NOT NULL DEFAULT 4,
30
	        'ocrOption'	INTEGER NOT NULL DEFAULT 0,
31
	        'isContainChild'	INTEGER NOT NULL DEFAULT 0,
32
	        'originalPoint'	TEXT,
33
	        'connectionPoint'	TEXT,
34
	        'baseSymbol'	TEXT,
35
	        'additionalSymbol'	TEXT,
36
            'isExceptDetect'    INTEGER DEFAULT 0
37
        );
38
    '''
39

  
40
    CREATE_LINEPROPERTIES_TABLE_SQL = '''
41
        CREATE TABLE IF NOT EXISTS LineProperties (
42
            UID TEXT NOT NULL,
43
            Name TEXT NOT NULL,
44
            CONSTRAINT LineProperties_PK PRIMARY KEY (UID)
45
        );
46
        CREATE UNIQUE INDEX LineProperties_Name_IDX ON LineProperties (Name);
47
    '''
48
    #CREATE_SYMBOLS_TABLE_SQL = '''
49
    #    CREATE TABLE 'Symbol' (
50
    #        'uid'	INTEGER PRIMARY KEY AUTOINCREMENT,
51
	   #     'symId'	INTEGER NOT NULL,
52
	   #     'name'	TEXT NOT NULL,
53
	   #     'type'	TEXT,
54
	   #     'path'	TEXT,
55
	   #     'threshold'	NUMERIC NOT NULL DEFAULT 0.4,
56
	   #     'minMatchPoint'	INTEGER NOT NULL DEFAULT 0,
57
	   #     'isDetectOrigin'	INTEGER NOT NULL DEFAULT 0,
58
	   #     'rotationCount'	INTEGER NOT NULL DEFAULT 4,
59
	   #     'ocrOption'	INTEGER NOT NULL DEFAULT 0,
60
	   #     'isContainChild'	INTEGER NOT NULL DEFAULT 0,
61
	   #     'originalPoint'	TEXT,
62
	   #     'connectionPoint'	TEXT,
63
	   #     'baseSymbol'	TEXT,
64
	   #     'additionalSymbol'	TEXT
65
    #    );
66
    #'''
67
    
68
    INSERT_SYMBOL_SQL = '''
69
        INSERT INTO Symbol(symId, name, type, threshold, minMatchPoint, isDetectOrigin, rotationCount, ocrOption, isContainChild, originalPoint, connectionPoint, baseSymbol, additionalSymbol, isExceptDetect) 
70
        VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
71
    '''
72
    #INSERT_SYMBOL_SQL = '''
73
    #    INSERT INTO Symbol(symId, name, type, path, threshold, minMatchPoint, isDetectOrigin, rotationCount, ocrOption, isContainChild, originalPoint, connectionPoint, baseSymbol, additionalSymbol) 
74
    #    VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
75
    #'''
76
    
77
    UPDATE_SYMBOL_SQL = '''
78
        UPDATE Symbol
79
        SET
80
            symId = ?, name = ?, type = ?, threshold = ?, minMatchPoint = ?, isDetectOrigin = ?,
81
            rotationCount = ?, ocrOption = ?, isContainChild = ?, originalPoint = ?, connectionPoint = ?,
82
            baseSymbol = ?, additionalSymbol = ?, isExceptDetect = ?
83
        WHERE uid = ?
84
    '''
85
    #UPDATE_SYMBOL_SQL = '''
86
    #    UPDATE Symbol
87
    #    SET
88
    #        symId = ?, name = ?, type = ?, path = ?, threshold = ?, minMatchPoint = ?, isDetectOrigin = ?,
89
    #        rotationCount = ?, ocrOption = ?, isContainChild = ?, originalPoint = ?, connectionPoint = ?,
90
    #        baseSymbol = ?, additionalSymbol = ?
91
    #    WHERE uid = ?
92
    #'''
93

  
94

  
95
    '''
96
        @history    18.04.20    Jeongwoo    Change dbFullPath (Root dir DB→ Each project DB)
97
                    18.05.03    Jeongwoo    Remove RootDir parameter
98
    '''
99
    def __init__(self):
100
        print("DB Helper __init__")
101
        
102
        self.dbFullPath = AppDocData.instance().getCurrentProject().getDbFilePath() + "/" + self.DB_NAME
103
        #if not os.path.isfile(dbFullPath):
104
        #    print("DB doesn't exist")
105

  
106
        try:
107
            conn = sqlite3.connect(self.dbFullPath)
108
            print(sqlite3.version)
109
            
110
            cursor = conn.cursor()
111
            cursor.execute(self.CREATE_SYMBOLS_TABLE_SQL)
112
        except Error as e:
113
            print(e)
114
        finally:
115
            conn.close()
116

  
117
    def isExistData(self, fieldName, data):
118
        rows = None
119
        try:
120
            conn = sqlite3.connect(self.dbFullPath)
121
            cursor = conn.cursor()
122
            sql = ""
123
            if isinstance(data, str):
124
                sql = "SELECT * FROM Symbol WHERE " + fieldName + " = '"+ data +"'"
125
            else:
126
                sql = "SELECT * FROM Symbol WHERE " + fieldName + " = "+ str(data) +""
127
            cursor.execute(sql)
128
            rows = cursor.fetchall()
129
        except Error as e:
130
            print(e)
131
        finally:
132
            conn.close()
133
            if rows is not None and len(rows) > 0:
134
                return True
135
            else:
136
                return False
137

  
138
    '''
139
        @history    2018.05.02  Jeongwoo    Parameter and WHERE changed (path → name)
140
    '''
141
    def isExistFileName(self, name):
142
        rows = None
143
        try:
144
            conn = sqlite3.connect(self.dbFullPath)
145
            cursor = conn.cursor()
146
            sql = "SELECT * FROM Symbol WHERE name = '"+ name +"'"
147
            cursor.execute(sql)
148
            rows = cursor.fetchall()
149
        except Error as e:
150
            print(e)
151
        finally:
152
            conn.close()
153
            if rows is not None and len(rows) > 0:
154
                return True
155
            else:
156
                return False
157

  
158
    '''
159
        @history    2018.05.02  Jeongwoo    Fix bug : symbol.getIsExceptDetect → symbol.getIsExceptDetect()
160
    '''
161
    def insertSymbol(self, symbol):
162
        isAdded = False
163
        try:
164
            conn = sqlite3.connect(self.dbFullPath)
165
            
166
            cursor = conn.cursor()
167
            query = (symbol.getId(), symbol.getName(), symbol.getType(), symbol.getThreshold()
168
                           , symbol.getMinMatchCount(), symbol.getIsDetectOnOrigin(), symbol.getRotationCount()
169
                           , symbol.getOcrOption(), symbol.getIsContainChild()
170
                           , symbol.getOriginalPoint(), symbol.getConnectionPoint()
171
                           , symbol.getBaseSymbol(), symbol.getAdditionalSymbol(), symbol.getIsExceptDetect())
172
            cursor.execute(self.INSERT_SYMBOL_SQL, query)
173
            conn.commit()
174
            isAdded = True
175
        except Error as e:
176
            print(e)
177
        finally:
178
            conn.close()
179
            return (isAdded, symbol.getType(), symbol.getName(), symbol.getPath())
180
        
181
    def updateSymbol(self, symbol):
182
        isUpdated = False
183
        try:
184
            conn = sqlite3.connect(self.dbFullPath)
185
            
186
            cursor = conn.cursor()
187
            query = (symbol.getId(), symbol.getName(), symbol.getType(), symbol.getThreshold()
188
                           , symbol.getMinMatchCount(), symbol.getIsDetectOnOrigin(), symbol.getRotationCount()
189
                           , symbol.getOcrOption(), symbol.getIsContainChild()
190
                           , symbol.getOriginalPoint(), symbol.getConnectionPoint()
191
                           , symbol.getBaseSymbol(), symbol.getAdditionalSymbol(), symbol.getIsExceptDetect(), symbol.getUid())
192
            cursor.execute(self.UPDATE_SYMBOL_SQL, query)
193
            conn.commit()
194
            isUpdated = True
195
        except Error as e:
196
            print(e)
197
        finally:
198
            conn.close()
199
            return (isUpdated, symbol.getType(), symbol.getName(), symbol.getPath())
DTI_PID/DTI_PID/SymbolGenerator.py
1
# -*- coding: utf-8 -*-
2

  
3
# Form implementation generated from reading ui file 'D:\Anaconda3\output\SymbolGenerator.ui'
4
#
5
# Created by: PyQt5 UI code generator 5.6
6
#
7
# WARNING! All changes made in this file will be lost!
8

  
9
from PyQt5 import QtCore, QtGui, QtWidgets
10
from PyQt5.QtWidgets import *
11
from PyQt5.QtGui import *
12
from QcImageViewer import QcImageViewer
13
import cv2
14
from QtImageViewer import QtImageViewer
15
import os
16
import sqlite3
17
from ProjectDialog import Ui_Dialog
18
#from SymbolEditor import SymbolEditor
19
import QSymbolEditorDialog
20
import sys
21

  
22
sys.path.insert(0, os.path.dirname(os.path.realpath(__file__)) + '\\Commands')
23
import CropCommand, HandCommand, ZoomCommand, PenCommand, AreaZoomCommand
24
from AppDocData import AppDocData
25

  
26
DB_NAME = os.path.dirname(os.path.realpath(__file__)) + "\\Project_db.db"
27

  
28
class Ui_MainWindow(object):
29
    def __init__(self):
30
        self.project = None
31

  
32
    def setupUi(self, MainWindow):
33
        #defaultFilePath = "D:\\Visual Studio Project\\DTIPID\\DTIPID\\DTI_PID\\DTI_PID\\res\\UY1-K-2000_P1_300dpi.png"
34
        #defaultImg = cv2.imread("D:\\Visual Studio Project\\DTIPID\\DTIPID\\DTI_PID\\DTI_PID\\res\\UY1-K-2000_P1_300dpi.png", 1)
35
        #_chan, self.imgW, self.imgH = defaultImg.shape[::-1]
36

  
37
        MainWindow.setObjectName("MainWindow")
38
        MainWindow.showFullScreen()
39
        
40
        mainWindowWidth = MainWindow.frameGeometry().width()
41
        mainWindowHeight = MainWindow.frameGeometry().height()
42
        
43
        #region central widget setting
44
        self.centralwidget = QtWidgets.QWidget(MainWindow)
45
        self.centralwidget.setObjectName("centralwidget")
46
        self.centralwidget.setGeometry(QtCore.QRect(0, 0, mainWindowWidth, mainWindowHeight - 50))
47
        #endregion
48
        
49
        centralWidth = self.centralwidget.frameGeometry().width()
50
        centralHeight = self.centralwidget.frameGeometry().height()
51

  
52
        #region top widget setting
53
        self.topWidget = QtWidgets.QWidget(self.centralwidget)
54
        self.topWidget.setObjectName("topWidget")
55
        self.topWidget.setGeometry(QtCore.QRect(0, 0, mainWindowWidth, 40))
56
        self.topWidget.setLayout(QHBoxLayout())
57
        self.initToolMenu(self.topWidget)
58
        #endregion
59

  
60
        #region contents widget setting
61
        self.contentsWidget = QtWidgets.QWidget(self.centralwidget)
62
        self.contentsWidget.setObjectName("contentsWidget")
63
        self.contentsWidget.setGeometry(QtCore.QRect(0, self.topWidget.y()+self.topWidget.height(), mainWindowWidth, centralHeight - 40))
64
        
65
        contentsWidgetWidth = self.contentsWidget.frameGeometry().width()
66
        contentsWidgetHeight = self.contentsWidget.frameGeometry().height()
67
        #endregion
68

  
69
        #region right widget setting
70
        self.rightWidget = QtWidgets.QWidget(self.contentsWidget)
71
        self.rightWidget.setObjectName("rightWidget")
72
        self.rightWidget.setGeometry(QtCore.QRect(contentsWidgetWidth*4//5, 0, contentsWidgetWidth//5, contentsWidgetHeight))
73
        self.rightWidget.setLayout(QVBoxLayout())
74
        
75
        rightWidgetWidth = self.rightWidget.frameGeometry().width()
76
        rightWidgetHeight = self.rightWidget.frameGeometry().height()
77
        margin = 6
78

  
79
        rightLayout = self.rightWidget.layout()
80
        rightLayout.setContentsMargins(margin, 0, margin, 0)
81
        self.treeAreaWidget = QtWidgets.QWidget(self.rightWidget)
82
        self.treeAreaWidget.setObjectName("treeWidget")
83
        self.treeAreaWidget.setGeometry(QtCore.QRect(0, 0, rightWidgetWidth, rightWidgetHeight//2))
84
        self.treeAreaWidget.setStyleSheet("background-color:white;border:1px solid black;")
85
        self.treeAreaWidget.setLayout(QVBoxLayout())
86

  
87
        treeAreaWidgetWidth = self.treeAreaWidget.frameGeometry().width()
88
        treeAreaWidgetHeight = self.treeAreaWidget.frameGeometry().height()
89

  
90
        self.treeWidget = QtWidgets.QTreeWidget(self.treeAreaWidget)
91
        self.treeWidget.setGeometry(0, 0, treeAreaWidgetWidth-10, treeAreaWidgetHeight)
92
        self.treeWidget.header().hide()
93
        self.treeWidget.expandToDepth(0)
94

  
95
        rightLayout.addWidget(self.treeAreaWidget)
96
        
97
        self.propertyWidget = QtWidgets.QWidget(self.rightWidget)
98
        self.propertyWidget.setObjectName("propertyWidget")
99
        self.propertyWidget.setGeometry(QtCore.QRect(0, rightWidgetHeight//2, rightWidgetWidth, rightWidgetHeight//2))
100
        self.propertyWidget.setStyleSheet("background-color:white;border:1px solid black;")
101
        rightLayout.addWidget(self.propertyWidget)
102
        #endregion
103

  
104
        #region image widget setting
105
        self.imageWidget = QtWidgets.QWidget(self.contentsWidget)
106
        self.imageWidget.setObjectName("imageWidget")
107
        self.imageWidget.setGeometry(QtCore.QRect(0, 0, contentsWidgetWidth - rightWidgetWidth, contentsWidgetHeight))
108
        #endregion
109
        
110
        imageWidgetWidth = self.imageWidget.frameGeometry().width()
111
        imageWidgetHeight = self.imageWidget.frameGeometry().height()
112

  
113
        #region imageView(ImageViewer) setting
114
        self.imageView = QtImageViewer()
115
        self.imageView.resize(imageWidgetWidth, imageWidgetHeight)
116
        self.imageView.aspectRatioMode = QtCore.Qt.KeepAspectRatio
117
        self.imageView.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
118
        self.imageView.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
119
        self.imageView.canZoom = True
120
        self.imageView.canPan = True
121
        #image = QImage(defaultFilePath)
122
        #image = image.scaled(self.imgW, self.imgH)
123
        #self.imageView.setImage(image)
124
        self.imageView.setParent(self.imageWidget)
125
        #endregion
126

  
127

  
128

  
129
        MainWindow.setCentralWidget(self.centralwidget)
130

  
131
        #region init Menu bar
132
        self.menubar = QtWidgets.QMenuBar(MainWindow)
133
        self.menubar.setGeometry(QtCore.QRect(0, 0, mainWindowWidth, 21))
134
        self.menubar.setObjectName("menubar")
135
        self.menuFile = QtWidgets.QMenu(self.menubar)
136
        self.menuFile.setObjectName("menuFile")
137
        MainWindow.setMenuBar(self.menubar)
138
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
139
        self.statusbar.setObjectName("statusbar")
140
        MainWindow.setStatusBar(self.statusbar)
141
        self.actionSelectProject = QtWidgets.QAction(MainWindow)
142
        self.actionSelectProject.setObjectName("actionSelectProject")
143
        self.actionOpenImage = QtWidgets.QAction(MainWindow)
144
        self.actionOpenImage.setObjectName("actionOpenImage")
145
        self.actionSave = QtWidgets.QAction(MainWindow)
146
        self.actionSave.setObjectName("actionSave")
147
        self.actionExit = QtWidgets.QAction(MainWindow)
148
        self.actionExit.setObjectName("actionExit")
149
        self.menuFile.addAction(self.actionSelectProject)
150
        self.menuFile.addAction(self.actionOpenImage)
151
        self.menuFile.addAction(self.actionSave)
152
        self.menuFile.addAction(self.actionExit)
153
        self.menubar.addAction(self.menuFile.menuAction())
154
        
155
        self.actionSelectProject.triggered.connect(self.actionSelectProjectClicked)
156
        self.actionOpenImage.triggered.connect(self.actionOpenImageClicked)
157
        self.actionExit.triggered.connect(qApp.quit)
158

  
159
        self.retranslateUi(MainWindow)
160
        #endregion
161

  
162
        QtCore.QMetaObject.connectSlotsByName(MainWindow)
163

  
164
        project = self.showProjectSelectionDialog()
165
        self.initTreeWidget(project)
166

  
167
    def showProjectSelectionDialog(self):
168
        self.projectDialog = Ui_Dialog()
169
        project = self.projectDialog.showDialog()
170
        return project
171

  
172
    def retranslateUi(self, MainWindow):
173
        _translate = QtCore.QCoreApplication.translate
174
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
175
        self.menuFile.setTitle(_translate("MainWindow", "File"))
176
        self.actionSelectProject.setText(_translate("MainWindow", "Select Project"))
177
        self.actionOpenImage.setText(_translate("MainWindow", "Open Image"))
178
        self.actionSave.setText(_translate("MainWindow", "Save"))
179
        self.actionExit.setText(_translate("MainWindow", "Exit"))
180

  
181
    def makeChildDir(self):
182
        project = AppDocData.instance().getCurrentProject()
183
        dbDir = project.getDbFilePath()
184
        if not os.path.exists(dbDir):
185
            os.makedirs(dbDir)
186
        imgDir = project.getImageFilePath()
187
        if not os.path.exists(imgDir):
188
            os.makedirs(imgDir)
189
        svgDir = project.getSvgFilePath()
190
        if not os.path.exists(svgDir):
191
            os.makedirs(svgDir)
192

  
193
    def loadDirectoryInfo(self, startPath, tree):
194
        for element in os.listdir(startPath):
195
            pathInfo = startPath + "/" + element
196
            parentItem = QTreeWidgetItem(tree, [os.path.basename(element)])
197
            if os.path.isdir(pathInfo):
198
                self.loadDirectoryInfo(pathInfo, parentItem)
199

  
200
    def initTreeWidget(self, project):
201
        if project is not None:
202
            self.treeWidget.clear()
203
            self.project = project
204
            print("Project : " + self.project.getName())
205
            projectPath = self.project.getPath().replace("\\", "/")
206
            self.makeChildDir()
207
            self.loadDirectoryInfo(projectPath, self.treeWidget)
208

  
209
    def actionSelectProjectClicked(self):
210
        project = self.showProjectSelectionDialog()
211
        self.initTreeWidget(project)
212

  
213
    def actionOpenImageClicked(self):
214
        options = QFileDialog.Options()
215
        options |= QFileDialog.DontUseNativeDialog
216
        fileName, _ = QFileDialog.getOpenFileName(None, "QFileDialog.getOpenFileName()", "D:\\Visual Studio Project\\DTIPID\\DTIPID\\DTI_PID\\DTI_PID\\res\\Symbols\\UY1_300dpi", "Image Files (*.png)", options=options)
217
        if fileName:
218
            print(fileName)
219
            image = QImage(fileName).copy()
220
            self.imgW = image.width()
221
            self.imgH = image.height()
222
            image = image.scaled(self.imgW, self.imgH)
223
            self.imageView.setImage(image)
224

  
225
    def initToolMenu(self, topWidget):
226
        layout = topWidget.layout()
227
        
228
        handTool = QPushButton()
229
        handTool.setText("Hand Tool")
230
        handTool.clicked.connect(self.handToolClickEvent)
231
        layout.addWidget(handTool)
232

  
233
        cropTool = QPushButton()
234
        cropTool.setText("Crop Tool")
235
        cropTool.clicked.connect(self.cropToolClickEvent)
236
        layout.addWidget(cropTool)
237

  
238
        areaZoomTool = QPushButton()
239
        areaZoomTool.setText("Area Zoom Tool")
240
        areaZoomTool.clicked.connect(self.areaZoomToolClickEvent)
241
        layout.addWidget(areaZoomTool)
242

  
243
        zoomTool = QPushButton()
244
        zoomTool.setText("Zoom Tool")
245
        zoomTool.clicked.connect(self.zoomToolClickEvent)
246
        layout.addWidget(zoomTool)
247

  
248
        zoomInitTool = QPushButton()
249
        zoomInitTool.setText("Zoom Init Tool")
250
        zoomInitTool.clicked.connect(self.zoomInitToolClickEvent)
251
        layout.addWidget(zoomInitTool)
252

  
253
        nextStepTool = QPushButton()
254
        nextStepTool.setText("Next Step")
255
        nextStepTool.clicked.connect(self.nextStepToolClickEvent)
256
        layout.addWidget(nextStepTool)
257

  
258
    def handToolClickEvent(self, event):
259
        print("hand tool clicked")
260
        self.imageView.command = HandCommand.HandCommand(self.imageView)
261

  
262
    def cropToolClickEvent(self, event):
263
        print("crop tool clicked")
264
        self.imageView.command = CropCommand.CropCommand(self.imageView)
265

  
266
    def zoomInitToolClickEvent(self, event):
267
        print("zoom init tool clicked")
268
        self.imageView.command = None
269
        self.imageView.zoomImageInit()
270

  
271
    def zoomToolClickEvent(self, event):
272
        print("zoom tool clicked")
273
        self.imageView.command = ZoomCommand.ZoomCommand(self.imageView)
274

  
275
    def areaZoomToolClickEvent(self, event):
276
        print("area zoom tool clicked")
277
        self.imageView.command = AreaZoomCommand.AreaZoomCommand(self.imageView)
278

  
279
    '''
280
        @history    2018.05.02  Jeongwoo    Return value changed (Single variable → Tuple)
281
    '''
282
    def nextStepToolClickEvent(self, event):
283
        print("next step tool clicked")
284
        if self.imageView.hasImage():
285
            self.symbolEditorDialog = QSymbolEditorDialog.QSymbolEditorDialog(self.imageView, self.imageView.image(), self.project)
286
            (isAccepted, isImmediateInsert, offsetX, offsetY, newSym) = self.symbolEditorDialog.showDialog()
287
            
288
            if isAccepted:
289
                self.initTreeWidget(self.project)
290
        else:
291
            QMessageBox.about(self.topWidget, "알림", "이미지를 선택한 후 다시 시도해주세요.")
292

  
293

  
294
if __name__ == "__main__":
295
    import sys
296
    app = QtWidgets.QApplication(sys.argv)
297
    MainWindow = QtWidgets.QMainWindow()
298
    ui = Ui_MainWindow()
299
    ui.setupUi(MainWindow)
300
    MainWindow.show()
301
    sys.exit(app.exec_())
302

  

내보내기 Unified diff