__main__.py 17.4 KB
Newer Older
Yan's avatar
Yan committed
1
2
3
4
#!/usr/bin/env python3

from PyQt5 import QtCore
from PyQt5 import QtWidgets
Yan's avatar
Yan committed
5
from PyQt5 import QtGui
6
from PyQt5 import QtPrintSupport
Yan's avatar
Yan committed
7
from rawprasslib import load_raw
Yan's avatar
Yan committed
8
from prasopes.predictmz import predict as getmzpattern
Yan's avatar
Yan committed
9
10
try:
    from rawautoparams import load_params
Yan's avatar
Yan committed
11
    import rawautoparams
Yan's avatar
Yan committed
12
    autoparams = True
13
except ImportError:
Yan's avatar
Yan committed
14
    autoparams = False
Yan's avatar
Yan committed
15
import rawprasslib
Yan's avatar
Yan committed
16
import numpy as np
Yan's avatar
Yan committed
17
import prasopes.config as cf
18
import prasopes.datatools as dt
19
import prasopes.drltools_gui as drlgui
Yan's avatar
Yan committed
20
import prasopes.filetools as ft
Yan's avatar
Yan committed
21
22
import prasopes.graphtools as gt
import prasopes.imagetools as imgt
Yan's avatar
Yan committed
23
import prasopes.zcetools as zce
Yan's avatar
Yan committed
24
import prasopes.docks as docks
Yan's avatar
Yan committed
25
import prasopes.tangoicons
Yan's avatar
Yan committed
26
27
import sys
import logging
3Yan's avatar
3Yan committed
28
import os.path
Yan's avatar
Yan committed
29
30


31
32
33
34
class update_signal(QtCore.QObject):
    signal = QtCore.pyqtSignal()


35
36
37
38
class QStatusBarLogger(logging.Handler):
    def __init__(self, parent=None):
        super().__init__()
        self.statusBar = QtWidgets.QStatusBar(parent)
Yan's avatar
Yan committed
39
40
        self.trigger = update_signal()
        self.msg = str("")
41
42

    def emit(self, record):
Yan's avatar
Yan committed
43
44
        self.msg = self.format(record)
        self.trigger.signal.emit()
45
46


47
48
49
50
51
52
53
def show_exception_and_exit(exc_type, exc_value, tb):
    import traceback
    traceback.print_exception(exc_type, exc_value, tb)
    raw_input("Press key to exit.")
    sys.exit(-1)


54
def load_file(parent, augCanvas, update, settings, loadthread, filename=None):
Yan's avatar
Yan committed
55
    """populates dataset and plots it"""
56
57
58
    directory = augCanvas.filename or settings.value("open_folder")
    filename = filename or QtWidgets.QFileDialog.getOpenFileName(
            caption="Open spectrum", directory=directory,
3Yan's avatar
3Yan committed
59
            filter="Finnigan RAW files (*.raw, *.RAW)")[0]
60
61
    if filename != '' and os.path.isfile(filename)\
            and not os.path.isdir(filename):
Yan's avatar
Yan committed
62
63
        error = update_signal()
        errormsg = []
64

65
        def runfnc():
Yan's avatar
Yan committed
66
            try:
Yan's avatar
Yan committed
67
                [i.clear() for i in (
68
69
                    augCanvas.ds, augCanvas.chrom['timesarg'],
                    augCanvas.ms['params'], augCanvas.ms['headers'])]
70
71
                [augCanvas.ds.append(dict(
                    chrom_dat=i[0], masses=i[1], matrix=i[2]))
72
                 for i in load_raw(filename, settings.value("tmp_location"))]
73
            except rawprasslib.ParsingException as pex:
Yan's avatar
Yan committed
74
75
                errormsg.append("Opening of the file has failed!")
                errormsg.append(
76
77
                    "File is incompatible with the rawprasslib, "
                    "canceling request!\n\n"
Yan's avatar
Yan committed
78
                    "Error message:\n{}".format(pex.args[0]))
Yan's avatar
Yan committed
79
                error.signal.emit()
80
                return
81
            if autoparams:
82
                try:
83
                    (augCanvas.ms['params'], rawheaders,
Yan's avatar
Yan committed
84
85
                        augCanvas.chrom['machtype']) = load_params(
                                filename, settings.value("tmp_location"))
86
                    segments = [len(subset['chrom_dat'][0])
87
                                for subset in augCanvas.ds]
88
                    indicies = [sum(segments[:i+1])
89
                                for i in range(len(segments))]
90
91
                    augCanvas.ms['headers'] = np.split(
                            rawheaders, indicies)[:-1]
92
                except Exception as pex:
93
94
                    errormsg.append(
                            "File is incompatible with the rawautoparams,")
Yan's avatar
Yan committed
95
96
97
98
                    errormsg.append(
                            "no parameters loaded!\n\n"
                            "Error message:\n{}".format(pex.args[0]))
                    error.signal.emit()
99
100
            gt.populate(augCanvas)
            augCanvas.filename = filename
101
102
103
            oldrecents = settings.value("recents")
            oldrecents.remove(filename) if filename in oldrecents else None
            settings.setValue("recents", [filename, *oldrecents])
104
            update.signal.emit()
105
106
        error.signal.connect(lambda: QtWidgets.QMessageBox.critical(
            parent, errormsg[0], errormsg[1]))
107
108
        loadthread.run = runfnc
        loadthread.start()
3Yan's avatar
3Yan committed
109

Yan's avatar
Yan committed
110

111
def print_graph(augCanvas):
Yan's avatar
Yan committed
112
    def printimage(printdevice, img):
Yan's avatar
Yan committed
113
        printer.setResolution(600)
Yan's avatar
Yan committed
114
        painter = QtGui.QPainter(printdevice)
Yan's avatar
Yan committed
115
116
117
118
        font = painter.font()
        linesize = printer.resolution()/15
        font.setPixelSize(linesize)
        painter.setFont(font)
119
        painter.drawImage(0, 0, img)
Yan's avatar
Yan committed
120
121
122
        offset = img.size().height()
        line = 1
        spacing = 1.5
123
124
125
126
127
        for row in range(augCanvas.paramstable.rowCount()):
            if augCanvas.paramstable.cellWidget(row, 0).checkState() == 2:
                text = augCanvas.paramstable.item(row, 1).text() +\
                       augCanvas.paramstable.item(row, 2).text()
                painter.drawText(300, int(offset+line*linesize*spacing), text)
Yan's avatar
Yan committed
128
                line += 1
Yan's avatar
Yan committed
129
        painter.end()
130
    # TODO: substitute the QPrintPreviewDialog with QPrintPreviewWidget
131
132
    printPreview = QtPrintSupport.QPrintPreviewDialog()
    printer = printPreview.printer()
Yan's avatar
Yan committed
133
134
    printer.setPageSize(printer.A5)
    printer.setDuplex(printer.DuplexNone)
135
136
    image = imgt.paint_image(augCanvas.ms, augCanvas.spectplot,
                             augCanvas.filename, printer)
Yan's avatar
Yan committed
137
    printPreview.paintRequested.connect(lambda:
Yan's avatar
Yan committed
138
                                        printimage(printer, image))
139
140
141
    printPreview.exec()


142
143
144
145
146
147
148
def update_spectrum(augCanvas, config):
    if augCanvas.filename:
        slims = [augCanvas.spectplot.get_xlim(),
                 augCanvas.spectplot.get_ylim()]
        augCanvas.ds.clear()
        [augCanvas.ds.append(dict(chrom_dat=i[0], masses=i[1], matrix=i[2]))
         for i in load_raw(augCanvas.filename, config.value("tmp_location"))]
3Yan's avatar
3Yan committed
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
        if autoparams:
            try:
                (augCanvas.ms['params'], rawheaders,
                    augCanvas.chrom['machtype']) = load_params(
                            augCanvas.filename, config.value("tmp_location"))
                segments = [len(subset['chrom_dat'][0])
                            for subset in augCanvas.ds]
                indicies = [sum(segments[:i+1])
                            for i in range(len(segments))]
                augCanvas.ms['headers'] = np.split(
                        rawheaders, indicies)[:-1]
            except Exception as pex:
                QtWidgets.QMessageBox.critical(None,
                        "File is incompatible with the rawautoparams",
                        "no parameters loaded!\n\n"
                        "Error message:\n{}".format(pex))
165
166
167
168
169
        gt.populate(augCanvas)
        augCanvas.spectplot.set_xlim(slims[0])
        augCanvas.spectplot.set_ylim(slims[1])
        gt.ann_spec(augCanvas.spectplot, augCanvas.ms)
        augCanvas.draw()
Yan's avatar
Yan committed
170
171


172
def update_recents(rcm, main_window, augCanvas, update, config, loadthread):
173
    """updates recents_menu (rcm)"""
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
    rcm.clear()
    # Actions need to be stored somewhere. Otherwise they end up in garbage.
    rcm.actionCache = []
    for i, j in enumerate(config.value("recents"), start=1):
        rcm.actionCache.append(QtWidgets.QAction("&{}. {}".format(i, j), None))
        rcm.actionCache[-1].triggered.connect(lambda _, fn=j: load_file(
            main_window, augCanvas, update, config, loadthread, filename=fn))
    rcm.actionCache.append(rcm.addSeparator())
    rcm.actionCache.append(QtWidgets.QAction("Clear Recents", None))
    [rcm.actionCache[-1].triggered.connect(i) for i in (
         lambda: config.setValue("recents", ""), lambda: update_recents(
             rcm, main_window, augCanvas, update, config, loadthread))]
    rcm.addActions(rcm.actionCache)


189
def dropped(event, parent, augCanvas, update, config, loadthread):
190
    dropurl = event.mimeData().urls()[0].toLocalFile()
191
    load_file(parent, augCanvas, update, config, loadthread, filename=dropurl)
Yan's avatar
Yan committed
192
193
194
195


def drag_entered(event):
    if event.mimeData().hasUrls() and event.mimeData().urls()[0]\
196
            .toLocalFile().lower().endswith('.raw'):
Yan's avatar
Yan committed
197
198
199
        event.accept()


200
def predictmz(form, augCanvas):
Yan's avatar
Yan committed
201
202
    text = form.text()
    if text == "":
203
        augCanvas.ms["predict"] = None
Yan's avatar
Yan committed
204
        return
205
206
207
208
209
210
211
    slims = [augCanvas.spectplot.get_xlim(),
             augCanvas.spectplot.get_ylim()]
    augCanvas.ms["predict"] = getmzpattern(text)
    gt.populate(augCanvas)
    augCanvas.spectplot.set_xlim(slims[0])
    augCanvas.spectplot.set_ylim(slims[1])
    augCanvas.draw()
Yan's avatar
Yan committed
212
213


214
def oddeven_changed(augCanvas, config, oddevenact):
3Yan's avatar
3Yan committed
215
    config.setValue("view/oddeven", oddevenact.isChecked())
216
    update_spectrum(augCanvas, config)
3Yan's avatar
3Yan committed
217
218


3Yan's avatar
3Yan committed
219
def key_pressed(event, augCanvas, config, update):
Yan's avatar
Yan committed
220
    if event.key() == QtCore.Qt.Key_F5:
221
        update_spectrum(augCanvas, config)
3Yan's avatar
3Yan committed
222
223
        if augCanvas.filename:
            update.signal.emit()
224
225
    if event.key() == QtCore.Qt.Key_C:
        if event.modifiers().__int__() == QtCore.Qt.ControlModifier:
226
227
228
229
230
            if augCanvas.paramstable.underMouse():
                dt.clip_tablestr(augCanvas)
            else:
                imgt.clip_spect_img(augCanvas.ms, augCanvas.spectplot,
                                    augCanvas.filename)
231
232
        if event.modifiers().__int__() == QtCore.Qt.ControlModifier + \
                QtCore.Qt.ShiftModifier:
233
            dt.clip_spectstr(augCanvas)
Yan's avatar
Yan committed
234
    if event.key() in (QtCore.Qt.Key_Left, QtCore.Qt.Key_Right):
235
        gt.shift_times(event, augCanvas)
Yan's avatar
Yan committed
236
237


Yan's avatar
Yan committed
238
239
def about(parent):
    """constructs window with "about" info"""
Yan's avatar
Yan committed
240
241
242
    rawparver = rawautoparams.__version__ if autoparams else\
            "library not found"

Yan's avatar
Yan committed
243
244
245
    QtWidgets.QMessageBox.information(
            parent, "About Prasopes",
            "Prasopes Finnigan raw file viewer\n\n"
Yan's avatar
Yan committed
246
247
248
249
            "Version: {} (alpha)\n\n"
            "Rawprasslib version: {}\n"
            "Rawautoparams version: {}".format(
                prasopes.__version__, rawprasslib.__version__, rawparver))
Yan's avatar
Yan committed
250
251


Yan's avatar
Yan committed
252
def main():
253
254
255
    # thx to: https://stackoverflow.com/questions/779675/stop-python-from-closing-on-error/781074#781074
    sys.excepthook = show_exception_and_exit

256
    app = QtWidgets.QApplication(sys.argv)
257
    loadthread = QtCore.QThread()
258

259
    augCanvas = gt.AugFigureCanvas()
260
    update = update_signal()
Yan's avatar
Yan committed
261

Yan's avatar
Yan committed
262
263
    config = cf.settings()

264
    barHandler = QStatusBarLogger()
265
266
    barHandler.trigger.signal.connect(
        lambda: barHandler.statusBar.showMessage(barHandler.msg))
267

Yan's avatar
Yan committed
268
    p_logger = logging.getLogger('parseLogger')
269
    params_logger = logging.getLogger('acqLogLogger')
Yan's avatar
Yan committed
270
    drl_logger = logging.getLogger('drlLogger')
271
    zce_logger = logging.getLogger('zceLogger')
Yan's avatar
Yan committed
272
    logging.basicConfig()
273
    # p_logger.setLevel("WARN")
274
    p_logger.setLevel("DEBUG")
275
    # drl_logger.setLevel("INFO")
Yan's avatar
Yan committed
276
    drl_logger.setLevel("DEBUG")
277
    zce_logger.setLevel("DEBUG")
278
    # params_logger.setLevel("DEBUG")
279
280
    p_logger.addHandler(barHandler)
    zce_logger.addHandler(barHandler)
281
282
    params_logger.addHandler(barHandler)
    barHandler.setLevel("DEBUG")
Yan's avatar
Yan committed
283

Yan's avatar
Yan committed
284
    main_window = QtWidgets.QMainWindow(windowTitle="Prasopes")
285
286
    update.signal.connect(lambda: main_window.setWindowTitle(
        "Prasopes - {}".format(os.path.basename(augCanvas.filename))))
Yan's avatar
Yan committed
287

288
    if QtGui.QIcon.themeName() == "":
Yan's avatar
Yan committed
289
290
        QtGui.QIcon.setThemeName("TangoMFK")

Yan's avatar
Yan committed
291
292
293
    consoledock = docks.consoleDockWidget(
            locals(), "&Console", "view/consolevisible")
    treedock = docks.treeDockWidget(
294
295
            "&File browser", "view/filebrowservisible", update, load_file,
            main_window, augCanvas, config, loadthread)
Yan's avatar
Yan committed
296
    paramsdock = docks.AugDock("Acquisition parameters", "&Acq parameters",
297
                               "view/acqparvisible")
298
299
    update.signal.connect(lambda: gt.update_paramstable(augCanvas))
    paramsdock.setWidget(augCanvas.paramstable)
Yan's avatar
Yan committed
300

Yan's avatar
Yan committed
301
    openact = QtWidgets.QAction(QtGui.QIcon.fromTheme(
Yan's avatar
Yan committed
302
        "document-open"), "&Open...", None)
Yan's avatar
Yan committed
303
304
    openact.setShortcut(QtCore.Qt.CTRL + QtCore.Qt.Key_O)
    openact.triggered.connect(lambda: load_file(
305
        main_window, augCanvas, update, config, loadthread))
Yan's avatar
Yan committed
306
    exportact = QtWidgets.QAction(QtGui.QIcon.fromTheme(
Yan's avatar
Yan committed
307
        "document-save-as"), "&Export...", None)
Yan's avatar
Yan committed
308
309
    exportact.setShortcut(QtCore.Qt.CTRL + QtCore.Qt.Key_E)
    exportact.triggered.connect(lambda: ft.export_dial(
310
        augCanvas, main_window))
311
312
313
    printact = QtWidgets.QAction(QtGui.QIcon.fromTheme(
        "document-print"), "&Print", None)
    printact.setShortcut(QtCore.Qt.CTRL + QtCore.Qt.Key_P)
314
    printact.triggered.connect(lambda: print_graph(augCanvas))
Yan's avatar
Yan committed
315
    settingsact = QtWidgets.QAction(QtGui.QIcon.fromTheme(
Yan's avatar
Yan committed
316
        "preferences-system"), "&Settings...", None)
Yan's avatar
Yan committed
317
318
    settingsact.triggered.connect(lambda: cf.dial(main_window))
    quitact = QtWidgets.QAction(QtGui.QIcon.fromTheme(
Yan's avatar
Yan committed
319
        "application-exit"), "&Quit", None)
Yan's avatar
Yan committed
320
321
322
    quitact.setShortcut(QtCore.Qt.CTRL + QtCore.Qt.Key_Q)
    quitact.triggered.connect(main_window.close)
    zceact = QtWidgets.QAction(QtGui.QIcon.fromTheme(
Yan's avatar
Yan committed
323
        "applications-utilities"), "&TSQ zce...", None)
Yan's avatar
Yan committed
324
325
    zceact.setShortcut(QtCore.Qt.CTRL + QtCore.Qt.Key_T)
    zceact.triggered.connect(lambda: zce.dialog(
326
        main_window, augCanvas, update))
Yan's avatar
Yan committed
327
    drlact = QtWidgets.QAction(QtGui.QIcon.fromTheme(
Yan's avatar
Yan committed
328
        "applications-utilities"), "&DRL...", None)
Yan's avatar
Yan committed
329
    drlact.setShortcut(QtCore.Qt.CTRL + QtCore.Qt.Key_D)
330
    drlact.triggered.connect(lambda: drlgui.main_window(
331
        main_window, augCanvas, update))
Yan's avatar
Yan committed
332
333
    aboutact = QtWidgets.QAction("&About Prasopes", None)
    aboutact.triggered.connect(lambda: about(main_window))
Yan's avatar
Yan committed
334
    autozoomy = QtWidgets.QAction(QtGui.QIcon.fromTheme(
Yan's avatar
Yan committed
335
336
337
338
        "zoom-original"), "Auto Zoom Y", None, checkable=True,
        checked=config.value("view/autozoomy", type=bool))
    autozoomy.triggered.connect(lambda: config.setValue(
                                "view/autozoomy", autozoomy.isChecked()))
Yan's avatar
Yan committed
339
    autozoomy.triggered.connect(lambda: gt.autozoomy(augCanvas.spectplot))
340
    intensitiesact = QtWidgets.QAction(
Yan's avatar
Yan committed
341
342
        "&Show intensities", None, checkable=True,
        checked=config.value("view/intensities", type=bool))
343
344
    intensitiesact.triggered.connect(lambda: config.setValue(
        "view/intensities", intensitiesact.isChecked()))
Yan's avatar
Yan committed
345
346
    intensitiesact.triggered.connect(lambda: gt.ann_spec(
        augCanvas.spectplot, augCanvas.ms))
347
    intensitiesact.triggered.connect(lambda: augCanvas.draw())
348
    oddevenact = QtWidgets.QAction(
Yan's avatar
Yan committed
349
350
            "&Odd / even", None, checkable=True,
            checked=config.value("view/oddeven", type=bool))
351
352
    oddevenact.triggered.connect(
        lambda: oddeven_changed(augCanvas, config, oddevenact))
Yan's avatar
Yan committed
353

Yan's avatar
Yan committed
354
355
    predictform = QtWidgets.QLineEdit(maximumWidth=150)
    predictform.editingFinished.connect(lambda: predictmz(
356
        predictform, augCanvas))
Yan's avatar
Yan committed
357

358
359
360
361
362
363
    recents_menu = QtWidgets.QMenu('Open &Recent', main_window)
    update_recents(recents_menu, main_window, augCanvas,
                   update, config, loadthread)
    update.signal.connect(lambda: update_recents(
        recents_menu, main_window, augCanvas, update, config, loadthread))

Yan's avatar
Yan committed
364
365
    file_menu = QtWidgets.QMenu('&File', main_window)
    main_window.menuBar().addMenu(file_menu)
Yan's avatar
Yan committed
366
    file_menu.addAction(openact)
367
    file_menu.addMenu(recents_menu)
Yan's avatar
Yan committed
368
369
    file_menu.addAction(exportact)
    file_menu.addSeparator()
370
371
    file_menu.addAction(printact)
    file_menu.addSeparator()
Yan's avatar
Yan committed
372
373
374
    file_menu.addAction(settingsact)
    file_menu.addSeparator()
    file_menu.addAction(quitact)
Yan's avatar
Yan committed
375
376
    tools_menu = QtWidgets.QMenu('&Tools', main_window)
    main_window.menuBar().addMenu(tools_menu)
Yan's avatar
Yan committed
377
378
    tools_menu.addAction(zceact)
    tools_menu.addAction(drlact)
379
    tools_menu.addSeparator()
Yan's avatar
Yan committed
380
    view_menu = QtWidgets.QMenu('&View', main_window)
381
382
    [view_menu.addAction(i.action) for i in
     (treedock, paramsdock, consoledock)]
Yan's avatar
Yan committed
383
    [view_menu.addAction(i) for i in (autozoomy, intensitiesact)]
384
385
    view_menu.addSeparator()
    view_menu.addAction(oddevenact)
Yan's avatar
Yan committed
386
    main_window.menuBar().addMenu(view_menu)
Yan's avatar
Yan committed
387
388
389
    help_menu = QtWidgets.QMenu('&Help', main_window)
    main_window.menuBar().addMenu(help_menu)
    help_menu.addAction(aboutact)
Yan's avatar
Yan committed
390

391
    main_window.setCentralWidget(augCanvas)
Yan's avatar
Yan committed
392

Yan's avatar
Yan committed
393
394
395
396
397
    toolBar = QtWidgets.QToolBar(main_window)
    toolBar.setAllowedAreas(QtCore.Qt.TopToolBarArea)
    toolBar.setFloatable(False)
    toolBar.setMovable(False)
    toolBar.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon)
Yan's avatar
Yan committed
398

Yan's avatar
Yan committed
399
400
401
    toolBar.addAction(openact)
    toolBar.addAction(exportact)
    toolBar.addSeparator()
Yan's avatar
Yan committed
402
403
404
    toolBar.addWidget(QtWidgets.QLabel("Predict Formula:"))
    toolBar.addWidget(predictform)
    toolBar.addSeparator()
Yan's avatar
Yan committed
405
406
    toolBar.addAction(zceact)
    toolBar.addAction(drlact)
Yan's avatar
Yan committed
407
408
    toolBar.addSeparator()
    toolBar.addAction(autozoomy)
Yan's avatar
Yan committed
409

Yan's avatar
Yan committed
410
411
    main_window.dragEnterEvent = lambda event: drag_entered(event)
    main_window.dropEvent = lambda event: dropped(
412
        event, main_window, augCanvas, update, config, loadthread)
Yan's avatar
Yan committed
413
    main_window.setAcceptDrops(True)
414
    main_window.keyPressEvent = lambda event: key_pressed(
3Yan's avatar
3Yan committed
415
            event, augCanvas, config, update)
Yan's avatar
Yan committed
416
417
    main_window.resizeEvent = lambda event: augCanvas.constrained_draw()
    update.signal.connect(lambda: augCanvas.constrained_draw())
Yan's avatar
Yan committed
418

419
420
    main_window.addToolBar(QtCore.Qt.TopToolBarArea, toolBar)
    main_window.addDockWidget(QtCore.Qt.LeftDockWidgetArea, treedock)
Yan's avatar
Yan committed
421
    main_window.addDockWidget(QtCore.Qt.RightDockWidgetArea, paramsdock)
Yan's avatar
Yan committed
422
    main_window.addDockWidget(QtCore.Qt.RightDockWidgetArea, consoledock)
423
    main_window.setStatusBar(barHandler.statusBar)
424
425
426

    main_window.setFocus()

Yan's avatar
Yan committed
427
    if len(sys.argv) == 2:
428
        load_file(main_window, augCanvas, update, config, loadthread,
429
                  filename=sys.argv[1])
Yan's avatar
Yan committed
430
    else:
431
432
        gt.pop_plot(0, 0, augCanvas.spectplot, augCanvas.ms)
        gt.pop_plot(0, 0, augCanvas.chromplot, augCanvas.chrom)
Yan's avatar
Yan committed
433
434
435

    main_window.show()
    sys.exit(app.exec_())
Yan's avatar
Yan committed
436

437

Yan's avatar
Yan committed
438
439
if __name__ == "__main__":
    main()