def view_fit_grains_results(self): for result in self.fit_grains_results: print(result) # Build grains table num_grains = len(self.fit_grains_results) shape = (num_grains, 21) grains_table = np.empty(shape) gw = instrument.GrainDataWriter(array=grains_table) for result in self.fit_grains_results: gw.dump_grain(*result) gw.close() # Display grains table in popup dialog dialog = QDialog(self.parent) dialog.setWindowTitle('Fit Grains Results') model = FitGrainsResultsModel(grains_table, dialog) view = QTableView(dialog) view.setModel(model) view.verticalHeader().hide() view.resizeColumnToContents(0) layout = QVBoxLayout(dialog) layout.addWidget(view) dialog.setLayout(layout) dialog.resize(960, 320) dialog.exec_()
def quit_message(self) -> QDialog: """Displays a window while SCOUTS is exiting""" message = QDialog(self) message.setWindowTitle('Exiting SCOUTS') message.resize(300, 50) label = QLabel('SCOUTS is exiting, please wait...', message) label.setStyleSheet(self.style['label']) label.adjustSize() label.setAlignment(Qt.AlignCenter) label.move(int((message.width() - label.width()) / 2), int((message.height() - label.height()) / 2)) return message
def loading_message(self) -> QDialog: """Returns the message box to be displayed while the user waits for the input data to load.""" message = QDialog(self) message.setWindowTitle('Loading') message.resize(300, 50) label = QLabel('loading DataFrame into memory...', message) label.setStyleSheet(self.style['label']) label.adjustSize() label.setAlignment(Qt.AlignCenter) label.move(int((message.width() - label.width()) / 2), int((message.height() - label.height()) / 2)) return message
def demo_combine_meshes(): """ Demonstrates combining the mesh of two scene nodes Prompt user to select two nodes to be merged and merge them. """ dialog = QDialog(GetQMaxMainWindow()) dialog.resize(250, 100) dialog.setWindowTitle('DEMO - Combine 2 Nodes') main_layout = QVBoxLayout() label = QLabel("Combine 2 Nodes") main_layout.addWidget(label) combine_btn = QPushButton("Combine") combine_btn.clicked.connect(combine_two_meshes) main_layout.addWidget(combine_btn) dialog.setLayout(main_layout) dialog.show()
class batch_file_viewer(QTableWidget): def __init__(self, nf_settings_path): super(batch_file_viewer,self).__init__(parent = None) self.nf_settings_parser = custom_config_parser() self.nf_settings_parser.load(nf_settings_path) self.setRowCount(20) self.setColumnCount(2) # Fill all places so there are no "None" types in the table for row in range(self.rowCount()): for column in range(self.columnCount()): item = QTableWidgetItem() item.setText('') self.setItem(row, column, item) self.original_background = item.background() self.clipboard = QGuiApplication.clipboard() self.cellChanged.connect(self.check_cell) # Needs to be after "filling for loop" above self.header = self.horizontalHeader() self.header.setSectionResizeMode(0, QHeaderView.Stretch) self.setHorizontalHeaderLabels(["MS files", "Label"]) self.header.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.header.customContextMenuRequested.connect( self.right_click_menu ) self.saved_text = '' self.store_re_text = '' def right_click_menu(self, point): column = self.header.logicalIndexAt(point.x()) # show menu about the column if column 1 if column == 1: menu = QMenu(self) menu.addAction('Auto label (experimental)', self.auto_label) menu.popup(self.header.mapToGlobal(point)) elif column == 0: menu = QMenu(self) menu.addAction('Remove empty rows', self.remove_empty_rows) menu.popup(self.header.mapToGlobal(point)) def keyPressEvent(self, event): """Add functionallity to keyboard""" # Mac OS specifics is_mac_os_delete = (event.key() == QtCore.Qt.Key_H and event.modifiers() == QtCore.Qt.ControlModifier) if event.key() == QtCore.Qt.Key_Delete or is_mac_os_delete: # 16777223 for item in self.selectedItems(): item.setText('') elif event.key() == 16777221 or event.key() == 16777220: # *.221 is right enter if len(self.selectedIndexes()) == 0: # Quick check if anything is selected pass else: index = self.selectedIndexes()[0] # Take last if index.row() + 1 > self.rowCount() - 1: self.addRow_with_items() self.setCurrentCell(index.row() + 1, index.column()) else: modifiers = QGuiApplication.keyboardModifiers() if modifiers == QtCore.Qt.ControlModifier and event.key() == 67: # Copy self.saved_text = '' new_row = False for index in self.selectedIndexes(): if index.column() == 0: if new_row: self.saved_text += '\n' new_row = True self.saved_text += self.item(index.row(), index.column()).text() self.saved_text += '\t' elif index.column() == 1: self.saved_text += self.item(index.row(), index.column()).text() self.saved_text += '\n' new_row = False self.clipboard.setText(self.saved_text) elif modifiers == QtCore.Qt.ControlModifier and event.key() == 86: # Paste clipboard_text = self.clipboard.text() clipboard_text = clipboard_text.split('\n') paste_index = self.selectedIndexes()[0] row = paste_index.row() for text, index in zip(clipboard_text, range(len(clipboard_text))): text = text.split('\t') column = paste_index.column() for input in text: if input == '': continue if column > self.columnCount() - 1: pass else: if self.item(row, column) is None: self.addRow_with_items() self.item(row, column).setText(input) column += 1 row += 1 else: super().keyPressEvent(event) # Propagate to built in methods # This interferes with copy and paste if it is not else def check_cell(self, row, column): """Triggered when cell is changed""" self.blockSignals(True) self.pick_color(row, column) self.blockSignals(False) def addRow_with_items(self): item = QTableWidgetItem() item.setText('') self.setRowCount(self.rowCount() + 1) self.setItem(self.rowCount() - 1, 0, item) # Note: new rowcount here label = QTableWidgetItem() label.setText('') self.setItem(self.rowCount() - 1, 1, label) def pick_color(self, row, column): """Triggered by check_cell""" msfile = self.item(row, 0) label = self.item(row, 1) if label is None or msfile is None: # NOTE: item == None will give NotImplementedError. Must use "is" return # This might remove some weird errors in the future # Fix for adding empty spaces if msfile.text() == ' ': msfile.setText('') # Ms file if not os.path.isfile(msfile.text()): msfile.setForeground(QColor('red')) elif msfile.text().split('.')[-1] == "mzML": msfile.setForeground(QColor(0,255,150)) elif msfile.text().split('.')[-1] != "mzML": msfile.setForeground(QColor(30,150,255)) workflow = self.nf_settings_parser.get('params.workflow') if msfile.text() == '': label.setBackground(self.original_background) label.setForeground(QColor('white')) elif label.text() == '' and os.path.isfile(msfile.text()) and workflow == "Full": label.setBackground(QColor('red')) elif os.path.isfile(msfile.text()) and label.text() != '': label.setBackground(self.original_background) label.setForeground(QColor(0,255,150)) else: label.setBackground(self.original_background) label.setForeground(QColor('white')) def update(self): for row in range(self.rowCount()): for column in range(self.columnCount()): self.pick_color(row, column) def auto_assign(self, file): """Triggered when using file chooser button""" for row in range(self.rowCount()): item = self.item(row, 0) if item.text() == '': self.item(row, 0).setText(file) return # If we get here, add more rows self.blockSignals(True) self.addRow_with_items() self.item(self.rowCount() - 1, 0).setText(file) self.pick_color(self.rowCount() - 1, 0) self.blockSignals(False) def auto_label(self): all_labels = [] # Get current labels, we want unique for row in range(self.rowCount()): label = self.item(row, 1).text() all_labels.append(label) self.dialog = QDialog() lay = QVBoxLayout() self.dialog.setLayout(lay) self.line = QLineEdit() self.line.setText(self.store_re_text) bbox = QDialogButtonBox() bbox.addButton("Help", QDialogButtonBox.HelpRole) bbox.addButton("Apply", QDialogButtonBox.AcceptRole) bbox.addButton("Cancel", QDialogButtonBox.RejectRole) bbox.accepted.connect(self.apply_auto) bbox.rejected.connect(self.cancel_auto) bbox.helpRequested.connect(self.help_auto) lay.addWidget(self.line) lay.addWidget(bbox) self.dialog.resize(300, 100) ans = self.dialog.exec_() # This will block until the dialog closes if ans != 1: return re_text = self.line.text() self.store_re_text = re_text # Assign labels c = 0 for row in range(self.rowCount()): label = self.item(row, 1).text() file = self.item(row, 0).text() if label == '' and file != '': file_name = os.path.basename(file) search = re.search(re_text, file_name) if search is not None: c += 1 # idk how to check how many captured groups, so lets do this a hacky way for i in range(10,-1,-1): try: added_label = search.group(i) break except Exception as e: pass else: added_label = '' # Do not add label if you don't know what to add self.item(row, 1).setText(added_label) if c == 0: ERROR("No files matched the regex pattern") self.auto_label() def apply_auto(self): re_text = self.line.text() test_string = "This is a string to check if you put in a correct regex code" try: match = re.search(re_text, test_string) except Exception as e: ERROR("This is not a valid regex string. Error is: \n" + str(e)) return self.dialog.accept() def cancel_auto(self): self.dialog.reject() def help_auto(self): help_dialog = QDialog() lay = QVBoxLayout() help_string = '''<html> <center> Auto labeler uses regular expressions to determine which files belong to a certain groupself. <br> Write the regular expression in the box and press apply. <br> The labeler will then label your file depending on the naming convention you applied. <br> Example: You have 100s of files named like this <br> patient_X_<date>.RAW <br> Solution can be found <a href=\"https://regex101.com/r/dpCBo9/1/\">here</a> <br> NOTE: The auto labeler will always take the LAST capturing group </html> ''' help_label = QLabel(help_string) help_label.setOpenExternalLinks(True) lay.addWidget(help_label) help_dialog.setLayout(lay) ans = help_dialog.exec_() # This will block until the dialog closes def remove_empty_rows(self): row = 0 for _ in range(5000): file = self.item(row, 0) label = self.item(row, 1) if file is None or label is None: self.removeRow(row) elif file.text() == '' and label.text() == '': self.removeRow(row) else: row += 1 if self.rowCount() == 0: item = QTableWidgetItem() self.setRowCount(self.rowCount() + 1) row = self.rowCount() - 1 self.setItem(row, 0, item) # Note: new rowcount here label = QTableWidgetItem() label.setText('') self.setItem(row, 1, label)
class Slot(): """Slot class for handling signal of Qt objects """ def __init__(self, parent): self.parent = parent # decorator def display(func): def wrapper(self, *args, **kwargs): try: self.parent.is_display = False self.parent.camera.is_reading = False self.parent.stop_timer() func(self, *args, **kwargs) finally: self.parent.is_display = True self.parent.camera.is_reading = True self.parent.start_timer() return wrapper def switch_theme(self): """ Toggle the stylesheet to use the desired path in the Qt resource system (prefixed by `:/`) or generically (a path to a file on system). This is quoted : https://stackoverflow.com/questions/48256772/dark-theme-for-qt-widgets """ # get the QApplication instance, or crash if not set app = QApplication.instance() if app is None: raise RuntimeError("No Qt Application found.") text = "" if self.parent.style_theme == "light": self.parent.style_theme = "dark" self.parent.style_theme_sheet = ":/dark.qss" self.update_statusbar() text = "Light" elif self.parent.style_theme == "dark": self.parent.style_theme = "light" self.parent.style_theme_sheet = ":/light.qss" self.update_statusbar() text = "Dark" file = QFile(self.parent.style_theme_sheet) self.parent.theme_button.setText(text) file.open(QFile.ReadOnly | QFile.Text) stream = QTextStream(file) app.setStyleSheet(stream.readAll()) def set_fontsize(self, text: str): """Change the font-size of all widgets. Args: text (str): font-size """ size = int(text) font = self.parent.font() family = str(font.family()) font_css = str(self.parent.parent_dir / "font.qss") """ with open(font_css, "w") as f: f.write("* {\n") f.write(' font-family: "{}";\n'.format(family)) f.write(' font-size: {}px;\n'.format(size)) f.write("}") self.parent.setStyleSheet("") with open(font_css, "r") as f: self.parent.setStyleSheet(f.read()) """ self.parent.setStyleSheet('font-family: "{}"; font-size: {}px;'.format( family, size)) def switch_paramlist(self) -> list: """Change the number of sliders shown on the window. User can select which parameter is shown on the window with Checkbox. Returns: list: List of selected paramters. """ self.dialog = QDialog(self.parent) self.vbox2 = QVBoxLayout() self.check_boxes = [] self.button_box = QGroupBox("params") self.button_box.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) for label in self.parent.support_params: self.check_box = QCheckBox(label) if label in self.parent.current_params.keys(): self.check_box.setChecked(True) self.check_boxes.append(self.check_box) self.vbox2.addWidget(self.check_box) self.vbox2.addStretch(1) self.button_box.setLayout(self.vbox2) self.check_all = QCheckBox("Check all") self.check_all.setChecked(False) self.check_all.stateChanged.connect(self.ALLCheck) label = QLabel("Select parameters to create slider") self.qdbox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.qdbox.accepted.connect(self.dialog.accept) self.qdbox.rejected.connect(self.dialog.reject) self.hbox = QHBoxLayout() self.hbox.addWidget(self.check_all) self.hbox.addWidget(self.qdbox) self.vbox = QVBoxLayout() self.vbox.addWidget(label) self.vbox.addWidget(self.button_box) self.vbox.addLayout(self.hbox) self.dialog.setLayout(self.vbox) self.dialog.resize(400, 300) if self.dialog.exec_(): ret = [] for button, key in zip(self.check_boxes, self.parent.support_params): if button.isChecked(): ret.append(key) self.parent.update_params(ret) def ALLCheck(self): """Checkes all chekebox. """ if self.check_all.isChecked(): for cb in self.check_boxes: cb.setChecked(True) else: for cb in self.check_boxes: cb.setChecked(False) @display def about(self): """Show the about message on message box. """ msg = QMessageBox(self.parent) #msg.setTextFormat(Qt.MarkdownText) msg.setIcon(msg.Information) msg.setWindowTitle("About this tool") msg.setText(MessageText.about_text) ret = msg.exec_() @display def show_shortcut(self): """Show the list of valid keyboard shortcut. """ self.parent.dialog = QDialog(self.parent) table = QTableWidget() vbox = QVBoxLayout() self.parent.dialog.setLayout(vbox) self.parent.dialog.setWindowTitle("Keyboard shortcut") header = ["key", "description"] keys = MessageText.keylist table.setColumnCount(len(header)) table.setRowCount(len(keys)) table.setHorizontalHeaderLabels(header) table.verticalHeader().setVisible(False) table.setAlternatingRowColors(True) table.horizontalHeader().setStretchLastSection(True) table.setEditTriggers(QAbstractItemView.NoEditTriggers) table.setFocusPolicy(Qt.NoFocus) for row, content in enumerate(keys): for col, elem in enumerate(content): item = QTableWidgetItem(elem) item.setFlags(Qt.ItemIsDragEnabled | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled) table.setItem(row, col, item) button = QPushButton("&Ok") button.clicked.connect(self.close) button.setAutoDefault(True) vbox.addWidget(table) vbox.addWidget(button) self.parent.dialog.resize(640, 480) self.parent.dialog.exec_() @display def usage(self): """Show usage of the program. """ msg = QMessageBox(self.parent) msg.setWindowTitle("Usage") #msg.setTitle("Usage of this GUI") text = QLabel(MessageText.usage_text) msg.setIcon(QMessageBox.Information) scroll = QScrollArea(msg) scroll.setWidgetResizable(True) grid = msg.findChild(QGridLayout) text.setWordWrap(True) scroll.setWidget(text) scroll.setMinimumSize(800, 400) scroll.setStyleSheet(""" border: 1.5px solid black; padding: 15px; """) grid.addWidget(scroll, 0, 1) msg.exec_() def quit(self): """Quit the program. """ QApplication.quit() @display def change_frame_prop(self): """Change the properties of camera. """ self.dialog = QDialog(self.parent) self.dialog.setWindowTitle("Change frame properties") text = QLabel() text.setText("Select fourcc, size and FPS.") fourcc, width, height, fps = self.parent.get_properties() size = "{}x{}".format(width, height) self.parent.fourcc_label = QLabel("Fourcc") self.parent.size_label = QLabel("Size") self.parent.fps_label = QLabel("FPS") self.parent.fourcc_result = QLabel(str(fourcc)) self.parent.fourcc_result.setFrameShape(QFrame.StyledPanel) self.parent.size_result = QLabel(size) self.parent.size_result.setFrameShape(QFrame.Box) self.parent.size_result.setFrameStyle(QFrame.Panel | QFrame.Sunken) self.parent.fps_result = QLabel(str(fps)) self.parent.fps_result.setFrameStyle(QFrame.Panel | QFrame.Sunken) fourcc_button = QPushButton("...") fourcc_button.clicked.connect(self.select_fourcc) size_button = QPushButton("...") size_button.clicked.connect(self.select_size) fps_button = QPushButton("...") fps_button.clicked.connect(self.select_fps) grid = QGridLayout() grid.addWidget(self.parent.fourcc_label, 0, 0) grid.addWidget(self.parent.fourcc_result, 0, 1) grid.addWidget(fourcc_button, 0, 2) grid.addWidget(self.parent.size_label, 1, 0) grid.addWidget(self.parent.size_result, 1, 1) grid.addWidget(size_button, 1, 2) grid.addWidget(self.parent.fps_label, 2, 0) grid.addWidget(self.parent.fps_result, 2, 1) grid.addWidget(fps_button, 2, 2) grid.setSpacing(5) self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.button_box.accepted.connect(self.dialog.accept) self.button_box.rejected.connect(self.dialog.reject) vbox = QVBoxLayout() vbox.addLayout(grid, 3) vbox.addWidget(self.button_box, 1) self.dialog.setLayout(vbox) self.dialog.resize(480, 270) if self.dialog.exec_(): self.set_param() self.close() else: self.close() def select_fourcc(self): items = self.parent.camera.get_supported_fourcc() item, ok = QInputDialog.getItem(self.dialog, "Select", "Select Fourcc", items, 0, False) if ok: self.parent.fourcc_result.setText(item) else: return None def select_size(self): if not self.parent.fourcc_result.text(): msg = QMessageBox() msg.setIcon(QMessageBox.Critical) msg.setText("Error") msg.setInformativeText("Select fourcc") msg.setWindowTitle("Error") msg.exec_() self.select_fourcc() return True if self.parent.camtype == "usb_cam": items = self.parent.camera.get_supported_size( self.parent.fourcc_result.text()) #elif self.camtype == "raspi": else: items = self.parent.camera.raspicam_img_format() item, ok = QInputDialog.getItem(self.dialog, "Select", "Select Size", items, 0, False) if ok: self.parent.size_result.setText(item) else: return None def select_fps(self): if not self.parent.size_result.text(): msg = QMessageBox() msg.setIcon(QMessageBox.Critical) msg.setText("Error") msg.setInformativeText("Select size") msg.setWindowTitle("Error") msg.exec_() self.select_size() return True if self.parent.camtype == "usb_cam": width, height = map(str, self.parent.size_result.text().split("x")) items = self.parent.camera.get_supported_fps( self.parent.fourcc_result.text(), width, height) #elif self.camtype == "raspi": else: items = self.parent.raspicam_fps() item, ok = QInputDialog.getItem(self.dialog, "Select", "Select FPS", items, 0, False) if ok: self.parent.fps_result.setText(item) else: return None def search_size(self, *args): lst = [] for fourcc in args: lst.extend( [i for i in self.parent.v4l2.vidcap_format if fourcc in i]) size_lst = [] for i in lst: size = "{}x{}".format(i[1], i[2]) if size not in size_lst: size_lst.append(size) return size_lst def search_fps(self, fourcc, size): width, height = map(int, size.split("x")) match = [fourcc, width, height] fps_lst = [] for i in self.parent.v4l2.vidcap_format: if set(i) >= set(match): fps = i[-1] if fps not in fps_lst: fps_lst.append(str(fps)) return fps_lst def set_param(self): fourcc = self.parent.fourcc_result.text() size = self.parent.size_result.text() width, height = map(int, size.split("x")) fps = self.parent.fps_result.text() self.parent.camera.set_properties(fourcc, width, height, float(fps)) self.parent.scene.setSceneRect(0, 0, width, height) if fps: self.parent.msec = 1 / float(fps) * 1000 else: self.parent.msec = 1 / 30.0 * 1000 self.parent.update_prop_table() def close(self): """Close the dialog. """ try: self.parent.dialog.close() return True except: return False @display def show_paramlist(self): """Show the list of currently set parameters. """ self.parent.dialog = QDialog(self.parent) table = QTableWidget() vbox = QVBoxLayout() self.parent.dialog.setLayout(vbox) self.parent.dialog.setWindowTitle("Parameters list") header = ["param", "min", "max", "step", "default"] lst = [] for key, val in self.parent.current_params.items(): sub = [] sub.append(key) for head in header: if head in val: sub.append(val[head]) lst.append(sub) table.setColumnCount(len(header)) table.setRowCount(len(self.parent.current_params)) table.setHorizontalHeaderLabels(header) table.verticalHeader().setVisible(False) table.setAlternatingRowColors(True) table.horizontalHeader().setStretchLastSection(True) table.setEditTriggers(QAbstractItemView.NoEditTriggers) table.setFocusPolicy(Qt.NoFocus) for row, content in enumerate(lst): for col, elem in enumerate(content): item = QTableWidgetItem(str(elem)) item.setTextAlignment(Qt.AlignLeft | Qt.AlignVCenter) item.setFlags(Qt.ItemIsDragEnabled | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled) table.setItem(row, col, item) table.resizeColumnsToContents() table.resizeRowsToContents() table.setColumnWidth(0, 250) table.setColumnWidth(1, 80) table.setColumnWidth(2, 80) table.setColumnWidth(3, 80) table.setColumnWidth(4, 80) button = QPushButton("&Ok") button.clicked.connect(self.close) button.setAutoDefault(True) vbox.addWidget(table) vbox.addWidget(button) self.parent.dialog.resize(640, 480) ret = self.parent.dialog.exec_() @display def set_font(self): """Change the font of all widgets through QFontDialog. """ self.parent.dialog = QFontDialog(self.parent) self.parent.dialog.setOption(QFontDialog.DontUseNativeDialog) self.parent.dialog.resize(800, 600) ret = self.parent.dialog.exec_() if ret: font = self.parent.dialog.selectedFont() family = str(font.family()) size = str(font.pointSize()) font_css = str(self.parent.parent_dir / "font.qss") with open(font_css, "w") as f: f.write("* {\n") f.write(' font-family: "{}";\n'.format(family)) f.write(' font-size: {}px;\n'.format(size)) f.write("}") self.parent.setStyleSheet("") with open(font_css, "r") as f: self.parent.setStyleSheet(f.read()) def update_statusbar(self): """Update statubar's style This method will be called when swtitching the color theme. """ if self.parent.style_theme == "light": if self.parent.colorspace == "rgb": self.parent.stat_css = { "postion": "color: black;", "R": "color: red;", "G": "color: green;", "B": "color: blue;", "alpha": "color: black;", } else: self.parent.stat_css = { "postion": "color: black;", "gray": "color: black;" } elif self.parent.style_theme == "dark": if self.parent.colorspace == "rgb": self.parent.stat_css = { "postion": "color: white;", "R": "color: white;", "G": "color: white;", "B": "color: white;", "alpha": "color: white;", } else: self.parent.stat_css = { "postion": "color: white;", "gray": "color: white;" } for stat, st in zip(self.parent.statbar_list, self.parent.stat_css.values()): stat.setStyleSheet(st) def set_file_rule(self): """Change the style of naming when saving frame as an image. """ self.parent.filename_rule = next(self.parent.filename_rule_lst) self.parent.prop_table[5][1] = self.parent.filename_rule self.parent.update_prop_table() self.parent.write_text("change: {}".format(self.parent.filename_rule))
class ADBHelper(object): # 构造函数 def __init__(self): # 加载UI文件 self.ui = QUiLoader().load('adbwindow.ui') # 设置窗口背景 palette = QPalette() pix = QPixmap('bg.jpg') #pix = pix.scaled(self.ui.width(), self.ui.height()) palette.setBrush(self.ui.backgroundRole(), QBrush(pix)) self.ui.setAutoFillBackground(True) self.ui.setPalette(palette) self.ui.setWindowIcon(QIcon('adb.png')) # self.ui.setWindowFlags(qcore.Qt.WindowStaysOnTopHint) # 绑定保存参数菜单项点击事件 self.ui.save_action.triggered.connect(self.save_params) # 绑定加载参数菜单项点击事件 self.ui.load_action.triggered.connect(self.load_params) # 绑定退出菜单项点击事件 self.ui.exit_action.triggered.connect(self.exit) # 绑定断开连接菜单项点击事件 self.ui.disconnect_action.triggered.connect(self.disconnect) # 绑定清空输出信息菜单项点击事件 self.ui.clear_action.triggered.connect(self.clear) # 绑定坐标工具菜单项点击事件 self.ui.tool_action.triggered.connect(self.tool) # 绑定关于菜单项点击事件 self.ui.about_action.triggered.connect(self.about) # 绑定启动ADB按钮点击事件 self.ui.openadb_pushButton.clicked.connect(self.open_adb) # 绑定连接按钮点击事件 self.ui.connect_pushButton.clicked.connect(self.connect) # 绑定显示设备列表按钮点击事件 self.ui.show_devices_pushButton.clicked.connect(self.show_devices) # 绑定开始点击按钮点击事件 self.ui.tap_pushButton.clicked.connect(self.tap) # 绑定开始滑屏按钮点击事件 self.ui.swipe_pushButton.clicked.connect(self.swipe) # 绑定停止按钮点击事件 self.ui.stop_pushButton.clicked.connect(self.stop) # 绑定坐标工具按钮点击事件 self.ui.tool_pushButton.clicked.connect(self.tool) # 限制数值范围 self.ui.swipe_x1_min_spinBox.valueChanged.connect(self.x1_min) self.ui.swipe_x1_max_spinBox.valueChanged.connect(self.x1_max) self.ui.swipe_y1_min_spinBox.valueChanged.connect(self.y1_min) self.ui.swipe_y1_max_spinBox.valueChanged.connect(self.y1_max) self.ui.swipe_x2_min_spinBox.valueChanged.connect(self.x2_min) self.ui.swipe_x2_max_spinBox.valueChanged.connect(self.x2_max) self.ui.swipe_y2_min_spinBox.valueChanged.connect(self.y2_min) self.ui.swipe_y2_max_spinBox.valueChanged.connect(self.y2_max) # ADB工具 self.adbtool = None # 连接标志 self.connected = False # 坐标工具窗口 self.tool_dialog = None # 截图文件 self.screen_cap_file = None # 当前时间 def nowtime(self): return str( _time.strftime('%Y-%m-%d %H:%M:%S', _time.localtime(_time.time()))) # 输出信息 def output_message(self, message): mess = "<font color='orange'>[</font><font color='blue'>"+self.nowtime() + \ "</font><font color='orange'>]</font><font color='green'>"+message+"</font>" self.ui.output_textEdit.append(mess) # 移动光标到最底 self.ui.output_textEdit.moveCursor(QTextCursor.End) # 输出结果和错误信息 def output_result_error(self, result, error): # 输出信息 self.output_message(result) # 判断错误信息是否为空 if len(error.strip()) != 0: self.output_message(error) # 保存参数 def save_params(self): # 弹出文件选择器 filepath, filetype = QFileDialog.getSaveFileName( self.ui, "保存参数", os.getcwd(), "Json File (*.json)") # 判断 if filepath != "": # IP地址 ip = self.ui.ip_lineEdit.text() # 端口号 port = self.ui.port_spinBox.value() # 模拟点击 tap_x = self.ui.tap_x_spinBox.value() tap_y = self.ui.tap_y_spinBox.value() tap_times = self.ui.tap_times_spinBox.value() tap_interval = round(self.ui.tap_interval_doubleSpinBox.value(), 1) tap_random_interval = self.ui.tap_random_interval_checkBox.isChecked( ) # 模拟滑屏 swipe_x1_min = self.ui.swipe_x1_min_spinBox.value() swipe_x1_max = self.ui.swipe_x1_max_spinBox.value() swipe_y1_min = self.ui.swipe_y1_min_spinBox.value() swipe_y1_max = self.ui.swipe_y1_max_spinBox.value() swipe_x2_min = self.ui.swipe_x2_min_spinBox.value() swipe_x2_max = self.ui.swipe_x2_max_spinBox.value() swipe_y2_min = self.ui.swipe_y2_min_spinBox.value() swipe_y2_max = self.ui.swipe_y2_max_spinBox.value() swipe_times = self.ui.swipe_times_spinBox.value() # 浮点数取整 swipe_interval = round( self.ui.swipe_interval_doubleSpinBox.value(), 1) swipe_random_interval = self.ui.swipe_random_interval_checkBox.isChecked( ) swipe_60 = self.ui.swipe_60_checkBox.isChecked() # 组成字典 params = { 'ip': ip, 'port': port, 'tap_x': tap_x, 'tap_y': tap_y, 'tap_times': tap_times, 'tap_interval': tap_interval, 'tap_random_interval': tap_random_interval, 'swipe_x1_min': swipe_x1_min, 'swipe_x1_max': swipe_x1_max, 'swipe_y1_min': swipe_y1_min, 'swipe_y1_max': swipe_y1_max, 'swipe_x2_min': swipe_x2_min, 'swipe_x2_max': swipe_x2_max, 'swipe_y2_min': swipe_y2_min, 'swipe_y2_max': swipe_y2_max, 'swipe_times': swipe_times, 'swipe_interval': swipe_interval, 'swipe_random_interval': swipe_random_interval, 'swipe_60': swipe_60 } # 写入文件 if not filepath.endswith('.json'): filepath = filepath + '.json' with open(filepath, 'w', encoding='utf-8') as f: json.dump(params, f) # 输出信息 self.output_message('保存参数完成!文件路径:' + os.path.abspath(filepath)) # 加载参数 def load_params(self): # 弹出文件选择器 filepath, filetype = QFileDialog.getOpenFileName( self.ui, "请选择文件", os.getcwd(), "Json File (*.json)") # 判断 if filepath != "": # 读取文件 with open(filepath, 'r', encoding='utf-8') as f: params = json.load(f) # IP和端口 self.ui.ip_lineEdit.setText(params['ip']) self.ui.port_spinBox.setValue(params['port']) # 模拟点击 self.ui.tap_x_spinBox.setValue(params['tap_x']) self.ui.tap_y_spinBox.setValue(params['tap_y']) self.ui.tap_times_spinBox.setValue(params['tap_times']) self.ui.tap_interval_doubleSpinBox.setValue( params['tap_interval']) self.ui.tap_random_interval_checkBox.setChecked( params['tap_random_interval']) # 模拟滑屏 self.ui.swipe_x1_max_spinBox.setValue(params['swipe_x1_max']) self.ui.swipe_x1_min_spinBox.setValue(params['swipe_x1_min']) self.ui.swipe_y1_max_spinBox.setValue(params['swipe_y1_max']) self.ui.swipe_y1_min_spinBox.setValue(params['swipe_y1_min']) self.ui.swipe_x2_max_spinBox.setValue(params['swipe_x2_max']) self.ui.swipe_x2_min_spinBox.setValue(params['swipe_x2_min']) self.ui.swipe_y2_max_spinBox.setValue(params['swipe_y2_max']) self.ui.swipe_y2_min_spinBox.setValue(params['swipe_y2_min']) self.ui.swipe_times_spinBox.setValue(params['swipe_times']) self.ui.swipe_interval_doubleSpinBox.setValue( params['swipe_interval']) self.ui.swipe_random_interval_checkBox.setChecked( params['swipe_random_interval']) self.ui.swipe_60_checkBox.setChecked(params['swipe_60']) # 输出信息 self.output_message('加载参数完成!文件路径:' + os.path.abspath(filepath)) # 退出 def exit(self): # 保存日志 # 断开连接 self.disconnect() # 退出 self.ui.close() # 断开连接 def disconnect(self): # 停止 self.stop() # 断开连接 result, error = ADBTool().disconnect() # 输出信息 self.output_result_error(result, error) self.connected = False self.output_message('已经断开连接') # 按钮可点 self.ui.tap_pushButton.setEnabled(True) self.ui.swipe_pushButton.setEnabled(True) # 清空输出信息 def clear(self): self.ui.output_textEdit.setText('') # 关于 def about(self): # 显示弹窗 QMessageBox.about( self.ui, '关于', 'ADB助手\n© Copyright 2020\n作者:ordinary-student\n版本:v1.0.0') # 打开ADB def open_adb(self): # 获取IP地址 ip = self.ui.ip_lineEdit.text() # IP校验 if len(ip.strip()) == 0: self.ui.ip_lineEdit.setText('') # 显示弹窗 QMessageBox.information(self.ui, '提示', '请填写IP地址!') return # 获取端口号 port = self.ui.port_spinBox.value() # 创建ADB工具 adbtool = ADBTool(ip, port) # 启动ADB result, error = adbtool.open() # 输出信息 self.output_result_error(result, error) # 判断是否已经启动 if ('restarting' in result) and ('error' not in error): self.adbtool = adbtool self.output_message('ADB已经启动') # 连接设备 def connect(self): # 判断 if self.adbtool == None: # 显示弹窗 QMessageBox.information(self.ui, '提示', 'ADB尚未启动!') return # 连接 try: result, error = self.adbtool.connect() # 输出信息 self.output_result_error(result, error) # 判断是否已经启动 if ('connected' in result) and ('error' not in error): self.connected = True self.output_message('已经连接设备') self.adbtool.tap(700, 1860) except: self.output_message('设备连接失败') # 显示弹窗 QMessageBox.information( self.ui, '提示', '设备连接失败!\n请检查设备和电脑是否处于同一局域网,\n检查设备的USB调试模式是否已经打开。\n若还是不行,请重启软件再尝试。' ) return # 显示设备列表 def show_devices(self): # 显示设备列表 result, error = ADBTool().show_devices() # 输出信息 self.output_result_error(result, error) # 开始点击 def tap(self): # 判断ADB是否启动 if self.adbtool == None: # 显示弹窗 QMessageBox.information(self.ui, '提示', 'ADB尚未启动!') return # 判断是否连接 if self.connected: tap_thread = Thread(target=self.tap2) tap_thread.start() else: # 显示弹窗 QMessageBox.information(self.ui, '提示', '尚未连接设备!') return # 开始点击 def tap2(self): # 按钮不可点 self.ui.tap_pushButton.setEnabled(False) # 获取参数 tap_x = self.ui.tap_x_spinBox.value() tap_y = self.ui.tap_y_spinBox.value() tap_times = self.ui.tap_times_spinBox.value() tap_interval = round(self.ui.tap_interval_doubleSpinBox.value(), 1) tap_random_interval = self.ui.tap_random_interval_checkBox.isChecked() if tap_random_interval: message = '点击坐标({},{})处,{}次,时间间隔随机'.format(tap_x, tap_y, tap_times) else: message = '点击坐标({},{})处,{}次,时间间隔{}秒'.format( tap_x, tap_y, tap_times, tap_interval) self.output_message(message) # 模拟点击 if self.adbtool.continuous_tap(tap_x, tap_y, tap_times, tap_interval, tap_random_interval): self.output_message('模拟点击已完成') else: self.output_message('模拟点击已停止') # 按钮可点 self.ui.tap_pushButton.setEnabled(True) # 开始滑屏 def swipe(self): # 判断ADB是否启动 if self.adbtool == None: # 显示弹窗 QMessageBox.information(self.ui, '提示', 'ADB尚未启动!') return # 判断是否连接 if self.connected: swipe_thread = Thread(target=self.swipe2) swipe_thread.start() else: # 显示弹窗 QMessageBox.information(self.ui, '提示', '尚未连接设备!') return # 开始滑屏 def swipe2(self): # 按钮不可点 self.ui.swipe_pushButton.setEnabled(False) # 获取参数 swipe_x1_min = self.ui.swipe_x1_min_spinBox.value() swipe_x1_max = self.ui.swipe_x1_max_spinBox.value() swipe_y1_min = self.ui.swipe_y1_min_spinBox.value() swipe_y1_max = self.ui.swipe_y1_max_spinBox.value() swipe_x2_min = self.ui.swipe_x2_min_spinBox.value() swipe_x2_max = self.ui.swipe_x2_max_spinBox.value() swipe_y2_min = self.ui.swipe_y2_min_spinBox.value() swipe_y2_max = self.ui.swipe_y2_max_spinBox.value() swipe_times = self.ui.swipe_times_spinBox.value() swipe_interval = round(self.ui.swipe_interval_doubleSpinBox.value(), 1) swipe_random_interval = self.ui.swipe_random_interval_checkBox.isChecked( ) swipe_60 = self.ui.swipe_60_checkBox.isChecked() # 定时60秒 if swipe_60: message = '从坐标({}~{},{}~{})滑到({}~{},{}~{})处,连续滑屏60秒,时间间隔{}秒'.format( swipe_x1_min, swipe_x1_max, swipe_y1_min, swipe_y1_max, swipe_x2_min, swipe_x2_max, swipe_y2_min, swipe_y2_max, swipe_interval) self.output_message(message) # 模拟连续滑屏60秒 if self.adbtool.timing_swipe( (swipe_x1_min, swipe_x1_max), (swipe_y1_min, swipe_y1_max), (swipe_x2_min, swipe_x2_max), (swipe_y2_min, swipe_y2_max), swipe_interval, swipe_random_interval): self.output_message('模拟滑屏已完成') else: self.output_message('模拟滑屏已停止') else: if swipe_random_interval: message = '从坐标({}~{},{}~{})滑到({}~{},{}~{})处,{}次,时间间隔随机'.format( swipe_x1_min, swipe_x1_max, swipe_y1_min, swipe_y1_max, swipe_x2_min, swipe_x2_max, swipe_y2_min, swipe_y2_max, swipe_times) else: message = '从坐标({}~{},{}~{})滑到({}~{},{}~{})处,{}次,时间间隔{}秒'.format( swipe_x1_min, swipe_x1_max, swipe_y1_min, swipe_y1_max, swipe_x2_min, swipe_x2_max, swipe_y2_min, swipe_y2_max, swipe_times, swipe_interval) self.output_message(message) # 模拟滑屏 if self.adbtool.random_swipe( (swipe_x1_min, swipe_x1_max), (swipe_y1_min, swipe_y1_max), (swipe_x2_min, swipe_x2_max), (swipe_y2_min, swipe_y2_max), swipe_times, swipe_interval, swipe_random_interval): self.output_message('模拟滑屏已完成') else: self.output_message('模拟滑屏已停止') # 按钮可点 self.ui.swipe_pushButton.setEnabled(True) # 停止 def stop(self): if self.adbtool != None: self.adbtool.stop() # 按钮可点 self.ui.tap_pushButton.setEnabled(True) self.ui.swipe_pushButton.setEnabled(True) # 截图 def screen_cap(self): if self.adbtool != None: # 截图 photo_path = self.adbtool.screen_cap() # 获取图片到本地 result, error = self.adbtool.pull_file(photo_path) self.output_result_error(result, error) # 判断 if ('png' in result) and ('error' not in error): path = photo_path[(photo_path.rindex('/') + 1):] self.output_message('截图成功!图片路径:' + os.path.abspath(path)) return os.path.abspath(path) else: self.output_message('截图失败!') return None else: return None # 坐标工具 def tool(self): # 判断ADB是否启动 if self.adbtool == None: # 显示弹窗 QMessageBox.information(self.ui, '提示', 'ADB尚未启动!') return # 判断是否连接 if self.connected: self.axis_tool() else: # 显示弹窗 QMessageBox.information(self.ui, '提示', '尚未连接设备!') return # 坐标工具 def axis_tool(self): w = 300 h = 600 # 判断 if self.tool_dialog == None: # 创建坐标工具窗 self.tool_dialog = QDialog(self.ui) self.tool_dialog.setWindowTitle('坐标工具') self.tool_dialog.resize(w, h) # 截图 photo_path = self.screen_cap() if photo_path != None: if self.screen_cap_file != None: os.remove(self.screen_cap_file) self.screen_cap_file = photo_path pix = QPixmap(photo_path) w = pix.width() h = pix.height() # 设置面板大小 self.tool_dialog.setFixedSize(w / 4, h / 4) # 调色板 palette = QPalette() # 缩小图片 pix = pix.scaled(w / 4, h / 4) palette.setBrush(self.tool_dialog.backgroundRole(), QBrush(pix)) self.tool_dialog.setAutoFillBackground(True) self.tool_dialog.setPalette(palette) self.tool_dialog.setMouseTracking(True) # 绑定鼠标移动事件 self.tool_dialog.mouseMoveEvent = self.mouse_move # 显示窗口 self.tool_dialog.show() # 十字光标 self.tool_dialog.setCursor(Qt.CrossCursor) # 鼠标移动 def mouse_move(self, event): x = event.pos().x() y = event.pos().y() #print(x, y) self.tool_dialog.setWindowTitle('X:{},Y:{}'.format(x * 4, y * 4)) # 限制数值范围 def x1_min(self): if self.ui.swipe_x1_min_spinBox.value( ) >= self.ui.swipe_x1_max_spinBox.value(): self.ui.swipe_x1_min_spinBox.setValue( self.ui.swipe_x1_max_spinBox.value()) # def x1_max(self): if self.ui.swipe_x1_max_spinBox.value( ) <= self.ui.swipe_x1_min_spinBox.value(): self.ui.swipe_x1_max_spinBox.setValue( self.ui.swipe_x1_min_spinBox.value()) # def y1_min(self): if self.ui.swipe_y1_min_spinBox.value( ) >= self.ui.swipe_y1_max_spinBox.value(): self.ui.swipe_y1_min_spinBox.setValue( self.ui.swipe_y1_max_spinBox.value()) # def y1_max(self): if self.ui.swipe_y1_max_spinBox.value( ) <= self.ui.swipe_y1_min_spinBox.value(): self.ui.swipe_y1_max_spinBox.setValue( self.ui.swipe_y1_min_spinBox.value()) # def x2_min(self): if self.ui.swipe_x2_min_spinBox.value( ) >= self.ui.swipe_x2_max_spinBox.value(): self.ui.swipe_x2_min_spinBox.setValue( self.ui.swipe_x2_max_spinBox.value()) # def x2_max(self): if self.ui.swipe_x2_max_spinBox.value( ) <= self.ui.swipe_x2_min_spinBox.value(): self.ui.swipe_x2_max_spinBox.setValue( self.ui.swipe_x2_min_spinBox.value()) # def y2_min(self): if self.ui.swipe_y2_min_spinBox.value( ) >= self.ui.swipe_y2_max_spinBox.value(): self.ui.swipe_y2_min_spinBox.setValue( self.ui.swipe_y2_max_spinBox.value()) # def y2_max(self): if self.ui.swipe_y2_max_spinBox.value( ) <= self.ui.swipe_y2_min_spinBox.value(): self.ui.swipe_y2_max_spinBox.setValue( self.ui.swipe_y2_min_spinBox.value())
class DeviceListDialog(QObject): show_tray_notification = Signal(str) management_action = Signal( str, # action name str, # action type str, # node id bool) # is_itself start_transfers = Signal() _update = Signal(list) def __init__(self, parent=None, initial_data=(), disk_usage=0, node_status=SS_STATUS_SYNCED, node_substatus=None, dp=1, nodes_actions=(), license_type=None): QObject.__init__(self) self._update.connect(self._update_data, Qt.QueuedConnection) self._dialog = QDialog(parent) self._dialog.setWindowIcon(QIcon(':/images/icon.png')) self._ui = Ui_Dialog() self._ui.setupUi(self._dialog) self._dialog.setAttribute(Qt.WA_MacFrameworkScaled) self._ui.device_list_view.setFont(QFont('Nano', 10 * dp)) self._license_type = license_type self._model = TableModel(disk_usage, node_status, node_substatus) QTimer.singleShot(100, lambda: self.update(initial_data)) self._view = self._ui.device_list_view self._view.setModel(self._model) self._view.setSelectionMode(QAbstractItemView.NoSelection) self._ui.centralWidget.setFrameShape(QFrame.NoFrame) self._ui.centralWidget.setLineWidth(1) self._nodes_actions = nodes_actions def show(self, on_finished): def finished(): self._dialog.finished.disconnect(finished) self._view.resizeRowsToContents() self._model.beginResetModel() on_finished() screen_width = QApplication.desktop().width() parent_x = self._dialog.parent().x() parent_width = self._dialog.parent().width() width = self._dialog.width() offset = 16 if parent_x + parent_width / 2 > screen_width / 2: x = parent_x - width - offset if x < 0: x = 0 else: x = parent_x + parent_width + offset diff = x + width - screen_width if diff > 0: x -= diff self._dialog.move(x, self._dialog.parent().y()) if width > screen_width - offset: self._dialog.resize(screen_width - offset, self._dialog.height()) self._view.setMouseTracking(True) self._old_mouse_move_event = self._view.mouseMoveEvent self._view.mouseMoveEvent = self._mouse_moved self._old_mouse_release_event = self._view.mouseMoveEvent self._view.mouseReleaseEvent = self._mouse_released logger.info("Opening device list dialog...") # Execute dialog self._dialog.finished.connect(finished) self._dialog.raise_() self._dialog.show() def update(self, nodes_info): self._update.emit(nodes_info) def _update_data(self, nodes_info): changed_nodes, deleted_nodes = self._model.update(nodes_info) for node_id in changed_nodes | deleted_nodes: self._nodes_actions.pop(node_id, None) self._view.resizeRowsToContents() def update_download_speed(self, value): self._model.update_node_download_speed(value) self._view.resizeRowsToContents() def update_upload_speed(self, value): self._model.update_node_upload_speed(value) self._view.resizeRowsToContents() def update_sync_dir_size(self, value): self._model.update_node_sync_dir_size(int(value)) self._view.resizeRowsToContents() def update_node_status(self, value, substatus): self._model.update_node_status(value, substatus) self._view.resizeRowsToContents() def close(self): self._dialog.reject() def set_license_type(self, license_type): self._license_type = license_type def _mouse_moved(self, event): pos = event.pos() index = self._view.indexAt(pos) if index.isValid(): if self._model.to_manage(index) and \ not self._pos_is_in_scrollbar_header(pos): self._view.setCursor(Qt.PointingHandCursor) else: self._view.setCursor(Qt.ArrowCursor) else: self._view.setCursor(Qt.ArrowCursor) self._old_mouse_move_event(event) def _mouse_released(self, event): pos = event.pos() index = self._view.indexAt(pos) if index.isValid(): if self._model.to_manage(index) and \ not self._pos_is_in_scrollbar_header(pos): self._show_menu(index, pos) self._old_mouse_release_event(event) def _pos_is_in_scrollbar_header(self, pos): # mouse is not tracked as in view when in header or scrollbar area # so pretend we are there if we are near pos_in_header = pos.y() < 10 if pos_in_header: return True scrollbars = self._view.findChildren(QScrollBar) if not scrollbars: return False pos_x = self._view.mapToGlobal(pos).x() for scrollbar in scrollbars: if not scrollbar.isVisible(): continue scrollbar_x = scrollbar.mapToGlobal(QPoint(0, 0)).x() if scrollbar_x - 10 <= pos_x <= scrollbar_x + scrollbar.width(): return True return False def _show_menu(self, index, pos): node_id, \ node_name, \ is_online, \ is_itself, \ is_wiped = self._model.get_node_id_online_itself(index) if not node_id: return license_free = self._license_type == FREE_LICENSE menu = QMenu(self._view) menu.setStyleSheet("background-color: #EFEFF4; ") menu.setToolTipsVisible(license_free) if license_free: menu.setStyleSheet( 'QToolTip {{background-color: #222222; color: white;}}') menu.hovered.connect(lambda a: self._on_menu_hovered(a, menu)) def add_menu_item(caption, index=None, action_name=None, action_type="", start_transfers=False, disabled=False, tooltip=""): action = menu.addAction(caption) action.setEnabled(not disabled) action.tooltip = tooltip if tooltip else "" if not start_transfers: action.triggered.connect(lambda: self._on_menu_clicked( index, action_name, action_type)) else: action.triggered.connect(self.start_transfers.emit) tooltip = tr("Not available for free license") \ if license_free and not is_itself else "" if not is_online: action_in_progress = ("hideNode", "") in \ self._nodes_actions.get(node_id, set()) item_text = tr("Remove node") if not action_in_progress \ else tr("Remove node in progress...") add_menu_item(item_text, index, "hideNode", disabled=action_in_progress) elif is_itself: add_menu_item(tr("Transfers..."), start_transfers=True) if not is_wiped: wipe_in_progress = ("execute_remote_action", "wipe") in \ self._nodes_actions.get(node_id, set()) if not wipe_in_progress: action_in_progress = ("execute_remote_action", "logout") in \ self._nodes_actions.get(node_id, set()) item_text = tr("Log out") if not action_in_progress \ else tr("Log out in progress...") add_menu_item(item_text, index, "execute_remote_action", "logout", disabled=action_in_progress or license_free and not is_itself, tooltip=tooltip) item_text = tr("Log out && wipe") if not wipe_in_progress \ else tr("Wipe in progress...") add_menu_item(item_text, index, "execute_remote_action", "wipe", disabled=wipe_in_progress or license_free and not is_itself, tooltip=tooltip) pos_to_show = QPoint(pos.x(), pos.y() + 20) menu.exec_(self._view.mapToGlobal(pos_to_show)) def _on_menu_clicked(self, index, action_name, action_type): node_id, \ node_name, \ is_online, \ is_itself, \ is_wiped = self._model.get_node_id_online_itself(index) if action_name == "hideNode" and is_online: self.show_tray_notification.emit( tr("Action unavailable for online node")) return if (action_name == "hideNode" or action_type == "wipe"): if action_name == "hideNode": alert_str = tr( '"{}" node will be removed ' 'from list of devices. Files will not be wiped.'.format( node_name)) else: alert_str = tr( 'All files from "{}" node\'s ' 'pvtbox secured folder will be wiped. '.format(node_name)) if not self._user_confirmed_action(alert_str): return if not is_itself: self._nodes_actions[node_id].add((action_name, action_type)) self.management_action.emit(action_name, action_type, node_id, is_itself) def _on_menu_hovered(self, action, menu): if not action.tooltip: return a_geometry = menu.actionGeometry(action) point = menu.mapToGlobal( QPoint(a_geometry.x() + 30, a_geometry.y() + 5)) QToolTip.showText(point, action.tooltip, menu, a_geometry, 60 * 60 * 1000) def _user_confirmed_action(self, alert_str): msg = tr("<b>Are</b> you <b>sure</b>?<br><br>{}".format(alert_str)) userAnswer = msgbox(msg, title=' ', buttons=[ (tr('Cancel'), 'Cancel'), (tr('Yes'), 'Yes'), ], parent=self._dialog, default_index=0, enable_close_button=True) return userAnswer == 'Yes' def on_management_action_in_progress(self, action_name, action_type, node_id): self._nodes_actions[node_id].add((action_name, action_type))