class RobocompDslGui(QMainWindow): def __init__(self, parent=None): super(RobocompDslGui, self).__init__(parent) self.setWindowTitle("Create new component") # self._idsl_paths = [] self._communications = { "implements": [], "requires": [], "subscribesTo": [], "publishes": [] } self._interfaces = {} self._cdsl_doc = CDSLDocument() self._command_process = QProcess() self._main_widget = QWidget() self._main_layout = QVBoxLayout() self.setCentralWidget(self._main_widget) self._name_layout = QHBoxLayout() self._name_line_edit = QLineEdit() self._name_line_edit.textEdited.connect(self.update_component_name) self._name_line_edit.setPlaceholderText("New component name") self._name_layout.addWidget(self._name_line_edit) self._name_layout.addStretch() # DIRECTORY SELECTION self._dir_line_edit = QLineEdit() # self._dir_line_edit.textEdited.connect(self.update_completer) self._dir_completer = QCompleter() self._dir_completer_model = QFileSystemModel() if os.path.isdir(ROBOCOMP_COMP_DIR): self._dir_line_edit.setText(ROBOCOMP_COMP_DIR) self._dir_completer_model.setRootPath(ROBOCOMP_COMP_DIR) self._dir_completer.setModel(self._dir_completer_model) self._dir_line_edit.setCompleter(self._dir_completer) self._dir_button = QPushButton("Select directory") self._dir_button.clicked.connect(self.set_output_directory) self._dir_layout = QHBoxLayout() self._dir_layout.addWidget(self._dir_line_edit) self._dir_layout.addWidget(self._dir_button) # LIST OF ROBOCOMP INTERFACES self._interface_list = QListWidget() self._interface_list.setSelectionMode( QAbstractItemView.ExtendedSelection) self._interface_list.itemSelectionChanged.connect( self.set_comunication) # LIST OF CONNECTION TyPES self._type_combo_box = QComboBox() self._type_combo_box.addItems( ["publishes", "implements", "subscribesTo", "requires"]) self._type_combo_box.currentIndexChanged.connect( self.reselect_existing) # BUTTON TO ADD A NEW CONNECTION # self._add_connection_button = QPushButton("Add") # self._add_connection_button.clicked.connect(self.add_new_comunication) self._add_connection_layout = QHBoxLayout() # self._add_connection_layout.addWidget(self._add_connection_button) self._language_combo_box = QComboBox() self._language_combo_box.addItems(["Python", "Cpp", "Cpp11"]) self._language_combo_box.currentIndexChanged.connect( self.update_language) self._add_connection_layout.addWidget(self._language_combo_box) self._add_connection_layout.addStretch() self._gui_check_box = QCheckBox() self._gui_check_box.stateChanged.connect(self.update_gui_selection) self._gui_label = QLabel("Use Qt GUI") self._add_connection_layout.addWidget(self._gui_label) self._add_connection_layout.addWidget(self._gui_check_box) # WIDGET CONTAINING INTERFACES AND TYPES self._selection_layout = QVBoxLayout() self._selection_layout.addWidget(self._type_combo_box) self._selection_layout.addWidget(self._interface_list) self._selection_layout.addLayout(self._add_connection_layout) self._selection_widget = QWidget() self._selection_widget.setLayout(self._selection_layout) # TEXT EDITOR WITH THE RESULTING CDSL CODE self._editor = QTextEdit(self) self._editor.setHtml("") self._document = self._editor.document() self._component_directory = None # SPLITTER WITH THE SELECTION AND THE CODE self._body_splitter = QSplitter(Qt.Horizontal) self._body_splitter.addWidget(self._selection_widget) self._body_splitter.addWidget(self._editor) self._body_splitter.setStretchFactor(0, 2) self._body_splitter.setStretchFactor(1, 9) # CREATION BUTTONS self._create_button = QPushButton("Create .cdsl") self._create_button.clicked.connect(self.write_cdsl_file) self._creation_layout = QHBoxLayout() self._creation_layout.addStretch() self._creation_layout.addWidget(self._create_button) self._console = QConsole() self._command_process.readyReadStandardOutput.connect( self._console.standard_output) self._command_process.readyReadStandardError.connect( self._console.error_output) # ADDING WIDGETS TO MAIN LAYOUT self._main_widget.setLayout(self._main_layout) self._main_layout.addLayout(self._name_layout) self._main_layout.addLayout(self._dir_layout) self._main_layout.addWidget(self._body_splitter) self._main_layout.addLayout(self._creation_layout) self._main_layout.addWidget(self._console) self.setMinimumSize(800, 500) self._editor.setText(self._cdsl_doc.generate_doc()) # self.editor->show(); # def update_completer(self, path): # print "update_completer %s"%path # info = QFileInfo(path) # if info.exists() and info.isDir(): # if not path.endswith(os.path.pathsep): # new_path = os.path.join(path, os.sep) # # self._dir_line_edit.setText(new_path) # all_dirs_output = [dI for dI in os.listdir(path) if os.path.isdir(os.path.join(path, dI))] # print all_dirs_output # self._dir_completer.complete() def load_idsl_files(self, fullpath=None): if fullpath is None: fullpath = ROBOCOMP_INTERFACES idsls_dir = os.path.join(ROBOCOMP_INTERFACES, "IDSLs") if os.path.isdir(idsls_dir): for full_filename in os.listdir(idsls_dir): file_name, file_extension = os.path.splitext(full_filename) if "idsl" in file_extension.lower(): full_idsl_path = os.path.join(idsls_dir, full_filename) # self._idsl_paths.append(os.path.join(idsls_dir,full_filename)) self.parse_idsl_file(full_idsl_path) self._interface_list.addItems(self._interfaces.keys()) def parse_idsl_file(self, fullpath): with open(fullpath, 'r') as fin: interface_name = None for line in fin: result = re.findall(r'^\s*interface\s+(\w+)\s*\{?\s*$', line, flags=re.MULTILINE) if len(result) > 0: interface_name = result[0] print("%s for idsl %s" % (interface_name, fullpath)) if interface_name is not None: self._interfaces[interface_name] = fullpath def add_new_comunication(self): interface_names = self._interface_list.selectedItems() com_type = str(self._type_combo_box.currentText()) for iface_name_item in interface_names: iface_name = str(iface_name_item.text()) self._communications[com_type].append(iface_name) idsl_full_path = self._interfaces[iface_name] idsl_full_filename = os.path.basename(idsl_full_path) self._cdsl_doc.add_comunication(com_type, iface_name) self._cdsl_doc.add_import(idsl_full_filename) self.update_editor() def set_comunication(self): interface_names = self._interface_list.selectedItems() com_type = str(self._type_combo_box.currentText()) self._communications[com_type] = [] self._cdsl_doc.clear_comunication(com_type) for iface_name_item in interface_names: iface_name = str(iface_name_item.text()) self._communications[com_type].append(iface_name) self._cdsl_doc.add_comunication(com_type, iface_name) self.update_imports() self.update_editor() def update_imports(self): self._cdsl_doc.clear_imports() for com_type in self._communications: for iface_name in self._communications[com_type]: idsl_full_path = self._interfaces[iface_name] idsl_full_filename = os.path.basename(idsl_full_path) self._cdsl_doc.add_import(idsl_full_filename) def update_language(self): language = self._language_combo_box.currentText() self._cdsl_doc.set_language(str(language)) self.update_editor() def update_gui_selection(self): checked = self._gui_check_box.isChecked() if checked: self._cdsl_doc.set_qui(True) else: self._cdsl_doc.set_qui(False) self.update_editor() def update_component_name(self, name): self._cdsl_doc.set_name(name) self.update_editor() def update_editor(self): self._editor.setText(self._cdsl_doc.generate_doc()) def set_output_directory(self): dir_set = False while not dir_set: dir = QFileDialog.getExistingDirectory( self, "Select Directory", ROBOCOMP_COMP_DIR, QFileDialog.ShowDirsOnly | QFileDialog.DontResolveSymlinks) if self.check_dir_is_empty(str(dir)): self._dir_line_edit.setText(dir) dir_set = True def write_cdsl_file(self): component_dir = str(self._dir_line_edit.text()) text = self._cdsl_doc.generate_doc() if not self._name_line_edit.text(): component_name, ok = QInputDialog.getText(self, 'No component name set', 'Enter component name:') if ok: self.update_component_name(component_name) self._name_line_edit.setText(component_name) else: return False if not os.path.exists(component_dir): if QMessageBox.Yes == QMessageBox.question( self, "Directory doesn't exist.", "Do you want create the directory %s?" % component_dir, QMessageBox.Yes | QMessageBox.No): os.makedirs(component_dir) else: QMessageBox.question( self, "Directory not exist", "Can't create a component witout a valid directory") return False file_path = os.path.join(component_dir, str(self._name_line_edit.text()) + ".cdsl") if os.path.exists(file_path): if QMessageBox.No == QMessageBox.question( self, "File already exists", "Do you want to overwrite?", QMessageBox.Yes | QMessageBox.No): return False with open(file_path, 'w') as the_file: the_file.write(text) self.execute_robocomp_cdsl() return True def execute_robocomp_cdsl(self): cdsl_file_path = os.path.join( str(self._dir_line_edit.text()), str(self._name_line_edit.text()) + ".cdsl") command = "python -u %s/robocompdsl.py %s %s" % ( ROBOCOMPDSL_DIR, cdsl_file_path, os.path.join(str(self._dir_line_edit.text()))) self._console.append_custom_text("%s\n" % command) self._command_process.start(command, QProcess.Unbuffered | QProcess.ReadWrite) def reselect_existing(self): com_type = self._type_combo_box.currentText() selected = self._communications[com_type] self._interface_list.clearSelection() for iface in selected: items = self._interface_list.findItems(iface, Qt.MatchFlag.MatchExactly) if len(items) > 0: item = items[0] item.setSelected(True) def check_dir_is_empty(self, dir_path): if len(os.listdir(dir_path)) > 0: msgBox = QMessageBox() msgBox.setWindowTitle("Directory not empty") msgBox.setText( "The selected directory is not empty.\n" "For a new Component you usually want a new directory.\n" "Do you want to use this directory anyway?") msgBox.setStandardButtons(QMessageBox.Yes) msgBox.addButton(QMessageBox.No) msgBox.setDefaultButton(QMessageBox.No) if msgBox.exec_() == QMessageBox.Yes: return True else: return False else: return True
class TagSelecion(QGroupBox): def __init__(self, parent=None, opc=None): super(TagSelecion, self).__init__() self.opc = opc self.setTitle('Tag Selection') self.layout = QVBoxLayout(self) self.button_refresh = QPushButton('Refresh', self) self.button_refresh.clicked.connect(self._refresh) self.layout.addWidget(self.button_refresh) self.button_all = QPushButton('Select All', self) self.button_all.clicked.connect(self._select_all) self.layout.addWidget(self.button_all) self.button_none = QPushButton('Select None', self) self.button_none.clicked.connect(self._select_none) self.layout.addWidget(self.button_none) self.hbox_pattern = QHBoxLayout(self) self.button_pattern = QPushButton('Toggle Pattern', self) self.button_pattern.clicked.connect(self._toggle_pattern) self._next_toggle = 'select' self.hbox_pattern.addWidget(self.button_pattern) self.le_pattern = QLineEdit(self) self.le_pattern.setPlaceholderText('Pattern') self.hbox_pattern.addWidget(self.le_pattern) self.layout.addLayout(self.hbox_pattern) self.label = QLabel('Select Tags', self) self.layout.addWidget(self.label) self.listw_tags = QListWidget(self) self.listw_tags.setSelectionMode(QAbstractItemView.MultiSelection) self.layout.addWidget(self.listw_tags) self.setLayout(self.layout) def selected_tags(self): return [item.text() for item in self.listw_tags.selectedItems()] def _refresh(self): self.tags_all = opcda.tags(self.opc) self.listw_tags.clear() self.listw_tags.addItems(self.tags_all) def _select_all(self): self.listw_tags.selectAll() def _select_none(self): self.listw_tags.clearSelection() def _toggle_pattern(self): toggle = self._next_toggle if toggle is 'select': self._next_toggle = 'deselect' elif toggle is 'deselect': self._next_toggle = 'select' pattern = self.le_pattern.text().lower() if len(pattern) is 0: matched_tags = [] else: matched_tags = [ tag for tag in self.tags_all if pattern in tag.lower() ] for tag in matched_tags: item = self.listw_tags.findItems(tag, Qt.MatchExactly)[0] if toggle is 'select': item.setSelected(True) elif toggle is 'deselect': item.setSelected(False)
class CC_window(QWidget): def __init__(self): super(CC_window, self).__init__() self.version = '0.2' glo = QVBoxLayout(self) self.Runningo = None self.Looping = None self.P = CC_thread() if name == 'nt': ficon = r_path('images\\favicon.png') licon = r_path('images\\logo.png') else: ficon = r_path('images/favicon.png') licon = r_path('images/logo.png') self.tf = QFont("", 13, QFont.Bold) self.sf = QFont("", 10, QFont.Bold) self.SelfIinit(QIcon(ficon)) self.center() self.a_btn(licon, glo) self.ss_btns(glo) self.i_list(glo) self.cl_btn(glo) self.l_btns(glo) self.f_btns(glo) self.ds_bar(glo) self.setLayout(glo) self.activateWindow() self.show() def SelfIinit(self, icon): self.setWindowTitle('chrome-cut ' + self.version) self.setGeometry(300, 300, 200, 150) self.setMinimumWidth(600) self.setMaximumWidth(600) self.setMinimumHeight(500) self.setMaximumHeight(500) # Setting Icon self.setWindowIcon(icon) QToolTip.setFont(self.sf) def msgApp(self, title, msg): uinfo = QMessageBox.question(self, title, msg, QMessageBox.Yes | QMessageBox.No) if uinfo == QMessageBox.Yes: return 'y' if uinfo == QMessageBox.No: return 'n' def closeEvent(self, event=None): if self.P.isRunning(): response = self.msgApp("Making sure", "Sure, you want to exit while looping ?") if response == 'y': if event is not None: event.accept() self.P.stop() exit(0) else: if event is not None: event.ignore() else: if event is not None: event.accept() exit(0) def center(self): qrect = self.frameGeometry() cp = QDesktopWidget().availableGeometry().center() qrect.moveCenter(cp) self.move(qrect.topLeft()) def a_btn(self, icon, glo): def show_about(): Amsg = "<center>All credit reserved to the author of chrome-cut " Amsg += " version " + self.version Amsg += ", This work is a free, open-source project licensed " Amsg += " under Mozilla Public License version 2.0 . <br><br>" Amsg += " visit us for more infos and how-tos :<br> " Amsg += "<b><a href='https://github.io/mrf345/chrome-cut'> " Amsg += "https://github.io/mrf345/chrome-cut </a> </b></center>" Amsgb = "About chrome-cut" return QMessageBox.about(self, Amsgb, Amsg) self.abutton = QPushButton('', self) self.abutton.setIcon(QPixmap(icon)) self.abutton.setIconSize(QSize(500, 100)) self.abutton.setToolTip('About chrome-cut') self.abutton.clicked.connect(show_about) glo.addWidget(self.abutton) def ss_btns(self, glo): self.lebutton = QPushButton('Scan IP address', self) self.lebutton.setToolTip( 'Insert and check if a specific IP is a valid chrome cast') self.lebutton.setFont(self.tf) self.lebutton.clicked.connect(self.sip_input) glo.addWidget(self.lebutton) def sip_input(self): self.lebutton.setEnabled(False) self.s_bar.setStyleSheet(self.s_norm) self.s_bar.showMessage('> Checking out the IP ..') msg = "<center>Enter suspected IP to check : <br><i><u>Usually it is " msg += "on 192.168.what_have_you.188</i></center>" text, okPressed = QInputDialog.getText(self, "Scan IP", msg) if okPressed and text != '': self.setCursor(Qt.BusyCursor) ch = is_ccast(text) if ch: self.il_add([text]) self.unsetCursor() self.lebutton.setEnabled(True) return True else: self.s_bar.setStyleSheet(self.s_error) self.s_bar.showMessage( '! Error: inserted IP is not chrome cast device') self.unsetCursor() self.lebutton.setEnabled(True) return True self.s_bar.clearMessage() self.lebutton.setEnabled(True) return True def i_list(self, glo): self.ci_list = QListWidget() self.ci_list.setToolTip("List of discovered devices") self.ci_list.setEnabled(False) self.setFont(self.sf) glo.addWidget(self.ci_list) def cl_btn(self, glo): self.cle_btn = QPushButton('Clear devices list') self.cle_btn.setToolTip("Remove all devices from the list") self.cle_btn.setFont(self.sf) self.cle_btn.setEnabled(False) self.cle_btn.clicked.connect(self.il_add) glo.addWidget(self.cle_btn) def l_btns(self, glo): hlayout = QHBoxLayout() self.llo = QCheckBox("Looping") self.llo.setToolTip( 'Automate repeating the smae command on the smae device selected ') self.llo.setEnabled(False) self.nlo = QLineEdit() self.nlo.setPlaceholderText("duration in seconds. Default is 10") self.nlo.setEnabled(False) self.nlo.setToolTip("Duration of sleep after each loop") self.lbtn = QPushButton('Stop') self.lbtn.setEnabled(False) self.lbtn.setFont(self.tf) self.llo.setFont(self.sf) self.lbtn.setToolTip("stop on-going command or loop") self.llo.toggled.connect(self.loped) self.lbtn.clicked.connect(partial(self.inloop_state, out=True)) hlayout.addWidget(self.llo) hlayout.addWidget(self.nlo) hlayout.addWidget(self.lbtn) glo.addLayout(hlayout) def loped(self): if self.Looping: self.Looping = None self.llo.setChecked(False) self.nlo.setEnabled(False) else: self.Looping = True self.llo.setChecked(True) self.nlo.setEnabled(True) return True def f_btns(self, glo): hlayout = QHBoxLayout() self.ksbutton = QPushButton('Kill stream', self) self.ksbutton.setToolTip('Kill whetever been streamed') self.ksbutton.setFont(self.tf) self.ksbutton.clicked.connect(self.k_act) self.sbutton = QPushButton('Stream', self) self.sbutton.setFont(self.tf) self.sbutton.setToolTip('Stream a youtube video') self.fbutton = QPushButton('Factory reset', self) self.fbutton.setFont(self.tf) self.fbutton.setToolTip('Factory reset the device') self.fbutton.clicked.connect(self.fr_act) self.sbutton.clicked.connect(self.yv_input) self.ksbutton.setEnabled(False) self.sbutton.setEnabled(False) self.fbutton.setEnabled(False) hlayout.addWidget(self.ksbutton) hlayout.addWidget(self.sbutton) hlayout.addWidget(self.fbutton) glo.addLayout(hlayout) def ch_dur(th=None, dur=10): if len(dur) >= 1: try: dur = int(dur) except: dur = None if dur is None or not isinstance(dur, int): self.s_bar.setStyleSheet(self.s_error) self.s_bar.showMessage( '! Error: wrong duration entery, only integers') return None else: dur = 10 return dur def k_act(self): if self.ci_list.count() >= 1: cr = self.ci_list.currentRow() if cr is None or cr <= 0: cr = 0 ip = self.ci_list.item(cr).text() if self.Looping is not None: dur = self.ch_dur(self.nlo.text()) if dur is None: return True self.P = CC_thread(cancel_app, dur, ip) self.P.start() self.P.somesignal.connect(self.handleStatusMessage) self.P.setTerminationEnabled(True) self.inloop_state() return True else: ch = cancel_app(ip) if ch is None: self.s_bar.setStyleSheet(self.s_error) self.s_bar.showMessage('! Error: failed to kill stream ..') else: self.s_bar.setStyleSheet(self.s_norm) self.s_bar.showMessage('# Stream got killed ') else: self.s_bar.setStyleSheet(self.s_error) self.s_bar.showMessage('! Error: No items selected from the list') self.eout() return True def fr_act(self): if self.ci_list.count() >= 1: cr = self.ci_list.currentRow() if cr is None or cr <= 0: cr = 0 ip = self.ci_list.item(cr).text() if self.Looping is not None: dur = self.ch_dur(self.nlo.text()) if dur is None: return True self.P = CC_thread(reset_cc, dur, ip) self.P.start() self.P.somesignal.connect(self.handleStatusMessage) self.P.setTerminationEnabled(True) self.inloop_state() return True else: ch = reset_cc(ip) if ch is None: self.s_bar.setStyleSheet(self.s_error) self.s_bar.showMessage( '! Error: failed to factory reset ..') else: self.s_bar.setStyleSheet(self.s_norm) self.s_bar.showMessage('# Got factory reseted ') else: self.s_bar.setStyleSheet(self.s_error) self.s_bar.showMessage('! Error: No items selected from the list') self.eout() return True def y_act(self, link=None): if self.ci_list.count() >= 1: cr = self.ci_list.currentRow() if cr is None or cr <= 0: cr = 0 ip = self.ci_list.item(cr).text() if self.Looping is not None: dur = self.ch_dur(self.nlo.text()) if dur is None: return True self.P = CC_thread(send_app, dur, ip, link) self.P.start() self.P.somesignal.connect(self.handleStatusMessage) self.P.setTerminationEnabled(True) self.inloop_state() return True else: ch = reset_cc(ip) if ch is None: self.s_bar.setStyleSheet(self.s_error) self.s_bar.showMessage('! Error: failed to stream link ..') else: self.s_bar.setStyleSheet(self.s_norm) self.s_bar.showMessage('# Streamed the link ') else: self.s_bar.setStyleSheet(self.s_error) self.s_bar.showMessage('! Error: No items selected from the list') self.eout() return True def yv_input(self): text, okPressed = QInputDialog.getText( self, "Stream youtube", "Enter youtube video link to be streamed :") if okPressed and text != '': ntext = text.split('?') if len(ntext) <= 1: self.s_bar.setStyleSheet(self.s_error) self.s_bar.showMessage('! Error: Invalid youtube linkd ') return False ntext = ntext[1] if ntext != '' and len(ntext) > 1 and "youtube" in text: self.y_act(ntext) return True else: self.s_bar.setStyleSheet(self.s_error) self.s_bar.showMessage('! Error: Invalid youtube linkd ') return False @Slot(object) def handleStatusMessage(self, message): self.s_bar.setStyleSheet(self.s_loop) self.s_bar.showMessage(message) def il_add(self, items=[]): if len(items) >= 1: self.s_bar.setStyleSheet(self.s_norm) for i in items: fitem = self.ci_list.findItems(i, Qt.MatchExactly) if len(fitem) >= 1: self.s_bar.setStyleSheet(self.s_error) self.s_bar.showMessage( '! Error: Device exists in the list') else: self.s_bar.setStyleSheet(self.s_norm) self.ci_list.addItem(i) self.s_bar.showMessage('# Device was found and added') if not self.P.isRunning(): self.cle_btn.setEnabled(True) self.ci_list.setEnabled(True) self.llo.setEnabled(True) self.nlo.setEnabled(True) self.ksbutton.setEnabled(True) self.sbutton.setEnabled(True) self.fbutton.setEnabled(True) else: self.s_bar.setStyleSheet(self.s_norm) self.s_bar.showMessage('# Cleard devices list') self.ci_list.clear() self.cle_btn.setEnabled(False) self.ci_list.setEnabled(False) self.ksbutton.setEnabled(False) self.llo.setEnabled(False) self.nlo.setEnabled(False) self.sbutton.setEnabled(False) self.fbutton.setEnabled(False) return True def inloop_state(self, out=False): if not out: self.lbtn.setEnabled(True) self.cle_btn.setEnabled(False) self.ci_list.setEnabled(False) self.ksbutton.setEnabled(False) self.llo.setEnabled(False) self.nlo.setEnabled(False) self.sbutton.setEnabled(False) self.fbutton.setEnabled(False) else: if self.P.isRunning(): self.P.stop() self.lbtn.setEnabled(False) self.cle_btn.setEnabled(True) self.ci_list.setEnabled(True) self.ksbutton.setEnabled(True) self.llo.setEnabled(True) self.nlo.setEnabled(True) self.sbutton.setEnabled(True) self.fbutton.setEnabled(True) self.s_bar.clearMessage() return True def ds_bar(self, glo): self.s_bar = QStatusBar() self.s_error = "QStatusBar{color:red;font-weight:1000;}" self.s_loop = "QStatusBar{color:black;font-weight:1000;}" self.s_norm = "QStatusBar{color:blue;font-style:italic;" self.s_norm += "font-weight:500;}" self.s_bar.setStyleSheet(self.s_norm) glo.addWidget(self.s_bar) def eout(self): msgg = "<center>" msgg += " Opps, a critical error has occurred, we will be " msgg += " grateful if you can help fixing it, by reporting to us " msgg += " at : <br><br> " msgg += "<b><a href='https://github.io/mrf345/chrome-cut'> " msgg += "https://github.io/mrf345/chrome-cut </a></b> </center>" mm = QMessageBox.critical(self, "Critical Error", msgg, QMessageBox.Ok) exit(0)