class ViewMenuButton(QPushButton): sig_action = None def __init__(self, btn_name: str, show_field, actions_map: list): """ Creates a QPushButton with a dropdown menu on it. :param btn_name: Button name (always displayed) :type btn_name: str :param actions_map: list of actions to put in the dropdown menu [(action_name, action_key), (separator_name), ...] :type actions_map: list :param show_field: entry field to display on create operations :type show_field: function """ QPushButton.__init__(self, btn_name) self.menu = QMenu(self) for a in actions_map: if a == 'sep': # This is a separator self.menu.addSeparator() else: # Regular action t, k = a if k.startswith( 'create_' ): # If we have a create feature, we display the entry field self.menu.addAction(t, lambda k=k: show_field(k)) else: self.menu.addAction(t, lambda k=k: self.sig_action.emit(k)) self.setMenu(self.menu) self.menu.setStyleSheet(get_stylesheet("menu"))
class ProjectItemButton(ProjectItemButtonBase): def __init__(self, toolbox, icon, item_type, parent=None): super().__init__(toolbox, icon, item_type, parent=parent) self.setToolTip( f"<p>Drag-and-drop this onto the Design View to create a new <b>{item_type}</b> item.</p>" ) if not toolbox.supports_specification(item_type): self._list_view = None self._menu = None return self.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self._list_view = ProjectItemDragListView() self._list_view.doubleClicked.connect(toolbox.edit_specification) self._list_view.context_menu_requested.connect( toolbox.show_specification_context_menu) self._list_widget = _CreateNewSpecListWidget(item_type) self._list_widget.itemClicked.connect( lambda _, item_type=item_type: toolbox.show_specification_form( item_type)) self._menu = QMenu(self) # Drop-down menu widget_action = CustomWidgetAction(self._menu) widget_action.setDefaultWidget(self._list_view) self._menu.addAction(widget_action) widget_action = CustomWidgetAction(self._menu) widget_action.setDefaultWidget(self._list_widget) self._menu.addAction(widget_action) self.setMenu(self._menu) self.setPopupMode(QToolButton.MenuButtonPopup) self._resize() model = toolbox.filtered_spec_factory_models.get(self.item_type) self._list_view.setModel(model) model.rowsInserted.connect(lambda *args: self._resize()) model.rowsRemoved.connect(lambda *args: self._resize()) model.modelReset.connect(lambda *args: self._resize()) self._list_view.drag_about_to_start.connect(self._menu.hide) def set_menu_color(self, color): if self._menu: self._menu.setStyleSheet( f"QMenu{{background: {make_icon_background(color)};}}") def setIconSize(self, size): super().setIconSize(size) if self._list_view: self._list_view.setIconSize(size) def _resize(self): self._list_view._set_preferred_height() self._list_widget._set_preferred_height() width = max(self._list_view._get_preferred_width(), self._list_widget._get_preferred_width()) self._list_view.setFixedWidth(width) self._list_widget.setFixedWidth(width) event = QResizeEvent(QSize(), self.menu().size()) QApplication.sendEvent(self.menu(), event) def _make_mime_data_text(self): return ",".join([self.item_type, ""])
def __init__(self, root): self.root = root self.app = QApplication([]) self.icons = { "timelapse": QIcon(resource_path('icons/timelapse.png')), "sync": QIcon(resource_path('icons/sync.png')), "sync_disabled": QIcon(resource_path('icons/sync_disabled.png')), "logo": QIcon(resource_path('icons/logo.png')), "settings": QIcon(resource_path('icons/settings.png')), "github": QIcon(resource_path('icons/github.png')) } self.main_window = MainWindow(self) self.offset_window = None self.settings_window = None self.messages = [[], []] self.section_labels = [ self.main_window.ui.right_status, self.main_window.ui.right_status_2 ] menu = QMenu('FS Time Sync') menu.setStyleSheet(""" QMenu { background-color: #151515; color: #ffffff; } QMenu::item { padding: 5px 10px 5px 10px; } QMenu::item:selected { background-color: #ffffff; color: #151515; } """) self.tray_actions = {} self.tray_actions["sync_now"] = menu.addAction("Sync Now") self.tray_actions["sync_now"].triggered.connect( lambda: self.root.sync_sim(force=True)) self.tray_actions["hide_show"] = menu.addAction("Hide") self.tray_actions["hide_show"].triggered.connect(self.hide) self.tray_actions["exit"] = menu.addAction("Exit") self.tray_actions["exit"].triggered.connect(self.exit) self.tray = QSystemTrayIcon() self.tray.setIcon(self.icons['logo']) self.tray.setToolTip("FS Time Sync") self.tray.setContextMenu(menu) self.tray.activated.connect(self.trayActivated) self.tray.show()
def contextMenuEvent(self, event): if not self.is_selected: self.select() # build context menu menu = QMenu(self) menu.setStyleSheet("background-color: rgb(68,68,68);") # instance_displ_act = menu.addAction("Display Instances") # bb_displ_act = menu.addAction("Display Bounding Box") # bbs_displ_act = menu.addAction("Display Bounding Boxes") # hide_act = menu.addAction("Hide Selected Setup(s)") # menu.addSeparator() # convert_particle_act = menu.addAction("Convert to Particles") # convert_particle_act = menu.addAction("Write to Alembic") # convert_particle_act = menu.addAction("Write to Katana Live Group") # convert_particle_act = menu.addAction("Combine Selected Setups") menu.addSeparator() remove_act = menu.addAction("Delete") action = menu.exec_(self.mapToGlobal(event.pos())) self.context_requested.emit(self, action)
def __init__(self, toolbox, icon, item_type, supports_specs, parent=None): super().__init__(parent=parent) self._toolbox = toolbox self.item_type = item_type self.setIcon(icon) self.setMouseTracking(True) self.setToolTip( f"<p>Drag-and-drop this onto the Design View to create a new <b>{item_type}</b> item.</p>" ) if not supports_specs: self._list_view = None return self._list_view = ProjectItemDragListView() self._list_view.doubleClicked.connect(self._toolbox.edit_specification) self._list_view.context_menu_requested.connect( self._toolbox.show_specification_context_menu) self._list_widget = CreateNewSpecListWidget(item_type) self._list_widget.itemClicked.connect( lambda _, item_type=item_type: self._toolbox. show_specification_form(item_type)) menu = QMenu(self) widget_action = CustomWidgetAction(menu) widget_action.setDefaultWidget(self._list_view) menu.addAction(widget_action) widget_action = CustomWidgetAction(menu) widget_action.setDefaultWidget(self._list_widget) menu.addAction(widget_action) menu.setStyleSheet(f"QMenu{{background: {ICON_BACKGROUND};}}") self.setMenu(menu) self.setPopupMode(QToolButton.MenuButtonPopup) self.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self._resize() model = self._toolbox.filtered_spec_factory_models.get(self.item_type) self._list_view.setModel(model) model.rowsInserted.connect(lambda *args: self._resize()) model.rowsRemoved.connect(lambda *args: self._resize()) self.drag_about_to_start.connect(self._handle_drag_about_to_start) self._list_view.drag_about_to_start.connect(menu.hide)
def _show_menu(self, colleague, pos): if not self._is_owner and not colleague.is_you or colleague.is_deleting: return menu = QMenu(self._ui.colleagues_list) menu.setStyleSheet("background-color: #EFEFF4; ") if colleague.is_you: if colleague.is_owner: action = menu.addAction(tr("Quit collaboration")) action.triggered.connect(self._on_quit_collaboration) else: action = menu.addAction(tr("Leave collaboration")) action.triggered.connect(self._on_leave_collaboration) else: rights_group = QActionGroup(menu) rights_group.setExclusive(True) menu.addSection(tr("Access rights")) action = menu.addAction(tr("Can view")) action.setCheckable(True) rights_action = rights_group.addAction(action) rights_action.setData(False) rights_action.setChecked(not colleague.can_edit) action = menu.addAction(tr("Can edit")) action.setCheckable(True) rights_action = rights_group.addAction(action) rights_action.setChecked(colleague.can_edit) rights_action.setData(True) rights_group.triggered.connect( lambda a: self._on_grant_edit(colleague, a)) menu.addSeparator() action = menu.addAction(tr("Remove user")) action.triggered.connect(lambda: self._on_remove_user(colleague)) pos_to_show = QPoint(pos.x(), pos.y() + 10) menu.exec_(pos_to_show)
class Window(QMainWindow): def __init__(self): super(Window, self).__init__() # Config -------------------------------------- self.objectList = {} self.systemIcon = { 'folder': QIcon(QApplication.style().standardIcon(QStyle.SP_DirIcon)), 'folderLink': QIcon(QApplication.style().standardIcon(QStyle.SP_DirLinkIcon)), 'file': QIcon(QApplication.style().standardIcon(QStyle.SP_FileIcon)), 'quit': QIcon(QApplication.style().standardIcon( QStyle.SP_DialogCloseButton)) } self.filetypeIcon = Icons.icon_filetypes_flat(self) self.includeExclude = IncludeExclude() self.includeExclude.includePaths = ['Examples'] self.includeExclude.includeExtensions = ['md', 'py', 'png'] self.includeExclude.excludeFilenames = ['gitignore'] self.includeExclude.excludePaths = ['git', '__pycache__'] # -------------------------------------------- self.create_menu() self.add_directory( '&Scripts', '/media/m/Data/Development/Phyton/TreeViewUltraMenu/Python-UltraMenu' ) self.add_exit() def create_menu(self): self.menu = QMenu(self) # Enable theme, uncomment: self.menu.setStyleSheet(appStyle) self.menu.setCursor(QtCore.Qt.PointingHandCursor) tt1 = self.menu.addAction('Tooltip 1') tt1.setToolTip('t1') tt2 = self.menu.addAction('Tooltip 2') tt2.setToolTip('t2') tt3 = self.menu.addAction('Tooltip 3') tt3.setWhatsThis('setWhatsThis') tt3.setIconText('q') tt3.iconText() # custom style label text = QLabel("Label with custom text", self) text.setProperty('class', 'singlestyle') styleItem = QWidgetAction(self) styleItem.setDefaultWidget(text) styleItem.setProperty('class', 'singlestyle') tt4 = self.menu.addAction(styleItem) # tt4.setProperty('class', 'singlestyle') # tt4.Priority # tt4.setStyleSheet("QLabel { color: rgb(50, 50, 50); font-size: 11px; background-color: rgba(188, 188, 188, 50); border: 1px solid rgba(188, 188, 188, 250); }") self.menu.setToolTipsVisible(True) # self.menu.setIcon # self.menu.setwha(True) # self.menu.hovered.connect(lambda tt1= tt1, tt1()) # self.menu.keyPressEvent(self.keyPressEvent) # pos = tt3. self.menu.installEventFilter(self) # widgetRect = self.menu.mapToGlobal() p = (0, 0) # print(self.menu.mapToGlobal(0,0)) widgetRect = self.geometry() print(widgetRect) # tt2.hovered.connect(lambda tt2=self.tt2, tt2.tooltip()) tt3.hovered.connect(lambda pos=[self], parent=self.menu, index=2: self. show_toolTip(pos, parent, index)) # Listen for All KeyPress/Mouse events def eventFilter(self, widget, event): if (event.type() == QtCore.QEvent.KeyRelease and widget is self.menu): self.menu.toolTip() print('all') # self. key = event.key() if key == QtCore.Qt.Key_Escape: print('escape') else: if key == QtCore.Qt.Key_Return: print('escape') elif key == QtCore.Qt.Key_Enter: print('escape') elif key == QtCore.Qt.Key_C: print('Key - c') return True # return QtGui.QWidget.eventFilter(self, widget, event) def show_toolTip(self, pos, parent, index): ''' **Parameters** pos: list list of all parent widget up to the upmost parent: PySide.QtGui.QAction the parent QAction index: int place within the QMenu, beginning with zero ''' position_x = 0 position_y = 0 for widget in pos: position_x += widget.pos().x() position_y += widget.pos().y() point = QtCore.QPoint() point.setX(position_x) point.setY(position_y + index * 22) # set y Position of QToolTip QToolTip.showText(point, parent.toolTip()) def add_directory(self, label, dir): qtParentMenuItem = self.menu.addMenu(self.systemIcon['folder'], label) parentMenuClass = FolderItem(qtMenuItem=qtParentMenuItem, label=label, iconPath='', globalHotkey='', path=Path(dir)) uid = parentMenuClass.getUid() self.objectList[uid] = parentMenuClass self.add_directory_submenu(parentMenuClass) def add_directory_submenu(self, parentMenuClass: FolderItem): qtParentMenuItem = parentMenuClass.getQtMenuItem() # Don't add the menu items multiple times, every time you hover a submenu. if parentMenuClass.isSubmenuItemsAdded == True: return None parentMenuClass.isSubmenuItemsAdded = True dirPath: str = parentMenuClass.getFullPath() osPaths = sorted(Path(dirPath).glob('*')) osPaths.sort(key=lambda x: x.is_file()) filesAndFoldersArr = [] for item in osPaths: # path = dirPath + '/' + item.name if item.is_dir( ) == True and self.includeExclude.folderIsNotExcluded(item): folderIcon = self.systemIcon['folder'] if item.is_symlink(): folderIcon = self.systemIcon['folderLink'] qtFolderItem = qtParentMenuItem.addMenu( folderIcon, '&' + item.name) folderItemClass = FolderItem(qtMenuItem=qtFolderItem, label=item.name, iconPath='', globalHotkey='', path=item) uid = folderItemClass.getUid() self.objectList[uid] = folderItemClass filesAndFoldersArr.append(folderItemClass) qtFolderItem.aboutToShow.connect( lambda folderItemClass=folderItemClass: self. add_directory_submenu(folderItemClass)) # newFolder.triggered.connect(self.action_directory(item.uid)) elif item.is_file() and self.includeExclude.fileIsNotExcluded( item): fileExt = item.suffix qtIcon = self.systemIcon['file'] # If we have a icon for the filetype, use that instead if fileExt in self.filetypeIcon: qtIcon = QIcon(str(self.filetypeIcon[fileExt])) qtFileItem = qtParentMenuItem.addAction(qtIcon, item.name) folderItemClass = FolderItem(qtMenuItem=qtFileItem, label=item.name, iconPath='', globalHotkey='', path=item) uid = folderItemClass.getUid() self.objectList[uid] = folderItemClass filesAndFoldersArr.append(folderItemClass) # filename # item.name # extension # item.suffix # newFile.triggered.connect(self.action_directory(item.uid)) # # newFile.hovered.connect(self.exit_app) # # newFile.hovered.connect(lambda: item.printUid()) # # func = self.hover() # # newFile.hovered.connect(lambda f=func,arg=newFile:f(arg)) else: print("It is a special file (socket, FIFO, device file)") def action_directory(self, uid): print(uid) def add_exit(self): exit = self.menu.addAction(self.systemIcon['quit'], '&Quit') self.menu.insertSeparator(exit) exit.triggered.connect(self.exit_app) self.menu.exec_(QCursor.pos()) def exit_app(self): self.close() sys.exit(0)
class MainApp(QWidget): def ScreenChanged(self, screen): self.client.setMaximumSize(screen.size()) def addServerAction(self, text, function, args=[], kwargs={}): action = QWidgetAction(self) aButton = QPushButton(text) aButton.clicked.connect(lambda: function(*args, **kwargs)) action.setDefaultWidget(aButton) self.ServerActions.addAction(action) def setupTopBar(self): tb = QWidget() tbl = QHBoxLayout() tb.setLayout(tbl) tb.setStyleSheet("border: 3px solid black; background-color: #7289da;") tbl.setAlignment(Qt.AlignLeft) homeBtn = QPushButton() homeBtn.setIcon(self.client.ico) saTB = QToolBar() self.ServerActions = QMenu() self.ServerActions.setTitle("⠀ Home ⠀") saTB.addAction(self.ServerActions.menuAction()) tbl.addWidget(homeBtn) tbl.addWidget(saTB) saTB.setStyleSheet("border: 1px solid black;") homeBtn.setStyleSheet("border: none;") self.ServerActions.setStyleSheet("border: none;") return tb def __init__(self, parent): super().__init__() self.client = parent parent.maw = self parent.l.addWidget(self) l = QVBoxLayout() self.setLayout(l) l.setAlignment(Qt.AlignTop) tb = self.setupTopBar() l.addWidget(tb) parent.setMinimumSize(parent.size()) parent.setMaximumSize(parent.screen().size()) parent.screenChanged.connect(self.ScreenChanged) parent.hide() parent.setWindowFlag(Qt.CustomizeWindowHint) parent.setWindowFlag(Qt.WindowMaximizeButtonHint) parent.show() self.show()
class FileNode(Node): def __init__(self): super(FileNode, self).__init__() self.menu = QMenu() self.menu.setStyleSheet("background-color: #3E3E42; color: white;") self.eventManager = FileEventManager() self.proxy: FileProxy = None self.saveAction = QAction(QIcon(main.resource_path("resources/save_file.png")), "Save file") self.renameAction = QAction(QIcon(main.resource_path("resources/rename_file.png")), "Rename file") self.deleteAction = QAction(QIcon(main.resource_path("resources/delete_file.png")), "Delete file") self.showInfilesAction = QAction(QIcon(main.resource_path("resources/open_folder.png")), "Show in files") self.menu.addAction(self.showInfilesAction) self.menu.addAction(self.saveAction) self.menu.addAction(self.renameAction) self.menu.addAction(self.deleteAction) self.connectActions() def renameFile(self): if not os.path.exists(self.proxy.getFilePath()): self.eventManager.invalidFile.emit(self) return type = self.path.split(".")[1] name, entered = QInputDialog.getText(None, "Rename file", "Enter new file name: ", QLineEdit.Normal, self.path.split(".")[0]) if entered: name += "."+type parentDir = os.path.abspath(os.path.join(self.proxy.getFilePath(), os.pardir)) newPath = os.path.join(parentDir, name) regex = re.compile('[@!#$%^&*()<>?/\|}{~:]') if " " in name or regex.search(name): msg = QMessageBox() msg.setStyleSheet("background-color: #2D2D30; color: white;") msg.setModal(True) msg.setIcon(QMessageBox.Critical) msg.setText("File name cannot contain whitespace or special characters.") msg.setWindowTitle("File rename error") msg.exec_() return if os.path.exists(newPath): msg = QMessageBox() msg.setStyleSheet("background-color: #2D2D30; color: white;") msg.setModal(True) msg.setIcon(QMessageBox.Critical) msg.setText("File with the same name already exists.") msg.setWindowTitle("File rename error") msg.exec_() return os.rename(self.proxy.getFilePath(), newPath) oldPath = self.path self.proxy.path = self.path = os.path.basename(newPath) self.setText(0, self.path) self.eventManager.fileRename.emit(oldPath, self.proxy) def getContextMenu(self) -> QMenu: return self.menu def connectActions(self): self.deleteAction.triggered.connect(self.deleteActionTriggered) self.saveAction.triggered.connect(self.saveFile) self.renameAction.triggered.connect(self.renameFile) self.showInfilesAction.triggered.connect(self.openInFileExplorer) def openInFileExplorer(self): path = self.getFilePath() if platform.system() == "Windows": os.startfile(path) elif platform.system() == "Darwin": subprocess.Popen(["open", path]) else: subprocess.Popen(["nautilus", "--select", path]) def deleteActionTriggered(self): if not os.path.exists(self.proxy.getFilePath()): self.eventManager.invalidFile.emit(self) return self.eventManager.fileRemoveRequsted.emit(self) def saveFile(self): if self.proxy.hasUnsavedChanges: try: self.proxy.saveFile() self.eventManager.fileSave.emit(self.proxy) except: msg = QMessageBox() msg.setStyleSheet("background-color: #2D2D30; color: white;") msg.setModal(True) msg.setIcon(QMessageBox.Critical) msg.setText("The following file could not be saved: {}".format(self.proxy.path)) msg.setWindowTitle("File save error") msg.exec_() def getFilePath(self): return os.path.join(self.proxy.parent.parent.path, self.proxy.parent.path, self.proxy.path)
class ProjectNode(Node): def __init__(self): super(ProjectNode, self).__init__() self.eventManager = ProjectEventManager() self.menu = QMenu() self.menu.setStyleSheet("background-color: #3E3E42; color: white;") self.proxy = ProjectProxy() self.saveAction = QAction( QIcon(main.resource_path("resources/save_folder.png")), "Save project") self.deleteAction = QAction( QIcon(main.resource_path("resources/remove_folder.png")), "Remove project") self.eraseAction = QAction( QIcon(main.resource_path("resources/delete_folder.png")), "Delete project from disk") self.renameAction = QAction( QIcon(main.resource_path("resources/rename_folder.png")), "Rename project") self.compileAction = QAction( QIcon(main.resource_path("resources/compile.png")), "Compile project") self.runAction = QAction( QIcon(main.resource_path("resources/run.png")), "Run project") self.debugAction = QAction( QIcon(main.resource_path("resources/debug.png")), "Debug project") self.newFileAction = QAction( QIcon(main.resource_path("resources/new_file.png")), "New file") self.importFileAction = QAction( QIcon(main.resource_path("resources/import_file.png")), "Import file") self.compilerOptionsAction = QAction( QIcon(main.resource_path("resources/compiler_options.png")), "Compiler options") self.showInFiles = QAction( QIcon(main.resource_path("resources/open_folder.png")), "Show in files") self.menu.addAction(self.saveAction) self.menu.addAction(self.compilerOptionsAction) self.menu.addAction(self.compileAction) self.menu.addAction(self.debugAction) self.menu.addAction(self.runAction) self.menu.addAction(self.showInFiles) self.menu.addSeparator() self.menu.addAction(self.newFileAction) self.menu.addAction(self.importFileAction) self.menu.addSeparator() self.menu.addAction(self.renameAction) self.menu.addAction(self.deleteAction) self.menu.addAction(self.eraseAction) self.connectActions() def getContextMenu(self) -> QMenu: return self.menu def __str__(self): return self.proxy.path def connectFileEventHandlers(self, file: FileNode): file.eventManager.fileRemoveRequsted.connect(self.removeFile) file.eventManager.fileRename.connect( lambda oldPath, fileProxy: self.eventManager.fileRename.emit( oldPath, fileProxy)) file.eventManager.fileSave.connect( lambda fileProxy: self.eventManager.fileSave.emit(fileProxy)) file.eventManager.invalidFile.connect(self.detachFile) def connectActions(self): self.saveAction.triggered.connect(self.saveProject) self.newFileAction.triggered.connect(self.createNewFile) self.importFileAction.triggered.connect(self.importFile) self.deleteAction.triggered.connect(self.deleteProject) self.renameAction.triggered.connect(self.renameProject) self.compilerOptionsAction.triggered.connect(self.editCompilerOptions) self.eraseAction.triggered.connect(self.eraseActionTriggered) self.compileAction.triggered.connect(self.compileActionTriggered) self.debugAction.triggered.connect(self.debugActionTriggered) self.runAction.triggered.connect(self.runActionTriggered) self.showInFiles.triggered.connect(self.openInFileExplorer) def openInFileExplorer(self): if not os.path.exists(self.proxy.getProjectPath()): self.eventManager.invalidProject.emit(self) return path = self.proxy.getProjectPath() if platform.system() == "Windows": os.startfile(path) elif platform.system() == "Darwin": subprocess.Popen(["open", path]) else: subprocess.Popen(["xdg-open", path]) def saveProject(self): if not os.path.exists(self.proxy.getProjectPath()): self.eventManager.invalidProject.emit(self) return self.eventManager.projectSave.emit(self.proxy) def detachFile(self, fileNode: FileNode): msg = QMessageBox() msg.setStyleSheet("background-color: #2D2D30; color: white;") msg.setModal(True) msg.setIcon(QMessageBox.Critical) msg.setText("File '{}' has been deleted from the disk.".format( fileNode.proxy.path)) msg.setWindowTitle("Invalid file") msg.exec_() self.proxy.files.remove(fileNode.proxy) self.removeChild(fileNode) self.parent().saveWorkspace() self.eventManager.fileRemove.emit(fileNode.proxy) def eraseActionTriggered(self): if not os.path.exists(self.proxy.getProjectPath()): self.eventManager.invalidProject.emit(self) return self.eventManager.projectDeleteFromDiskRequested.emit(self) def compileActionTriggered(self): if not os.path.exists(self.proxy.getProjectPath()): self.eventManager.invalidProject.emit(self) return self.eventManager.projectCompileRequested.emit(self.proxy) def debugActionTriggered(self): if not os.path.exists(self.proxy.getProjectPath()): self.eventManager.invalidProject.emit(self) return self.eventManager.projectDebugRequested.emit(self.proxy) def runActionTriggered(self): if not os.path.exists(self.proxy.getProjectPath()): self.eventManager.invalidProject.emit(self) return self.eventManager.projectRunRequested.emit(self.proxy) def editCompilerOptions(self): if not os.path.exists(self.proxy.getProjectPath()): self.eventManager.invalidProject.emit(self) return editor = CompilerOptionsEditor(self.proxy) if editor.exec_(): self.parent().saveWorkspace() def renameProject(self): if not os.path.exists(self.proxy.getProjectPath()): self.eventManager.invalidProject.emit(self) return name, entered = QInputDialog.getText(None, "Rename project", "Enter new workspace name: ", QLineEdit.Normal, self.path) if entered: parentDir = os.path.abspath( os.path.join(self.proxy.getProjectPath(), os.pardir)) newPath = os.path.join(parentDir, name) # print(newPath) regex = re.compile('[@!#$%^&*()<>?/\|}{~:]') if " " in name or regex.search(name): msg = QMessageBox() msg.setStyleSheet("background-color: #2D2D30; color: white;") msg.setModal(True) msg.setIcon(QMessageBox.Critical) msg.setText( "Project name cannot contain whitespace or special characters." ) msg.setWindowTitle("Project rename error") msg.exec_() return if os.path.exists(newPath): msg = QMessageBox() msg.setStyleSheet("background-color: #2D2D30; color: white;") msg.setModal(True) msg.setIcon(QMessageBox.Critical) msg.setText("Folder with the same name already exists.") msg.setWindowTitle("Project rename error") msg.exec_() return os.rename(self.path, newPath) oldPath = self.path self.proxy.path = self.path = os.path.basename(newPath) self.setText(0, self.path) self.parent().saveWorkspace() self.eventManager.projectRename.emit(oldPath, self) def removeFile(self, file: FileNode): answer = QMessageBox.question( None, "Delete file", "Are you sure you want to delete file {} from the disk?".format( file.proxy.path), QMessageBox.Yes | QMessageBox.No) if not answer == QMessageBox.Yes: return self.proxy.files.remove(file.proxy) os.remove(file.proxy.getFilePath()) self.eventManager.fileRemove.emit(file.proxy) self.removeChild(file) self.parent().saveWorkspace() def importFile(self, path=None): if not os.path.exists(self.proxy.getProjectPath()): self.eventManager.invalidProject.emit(self) return if not path: name, entered = QFileDialog.getOpenFileName( None, "Select file to import", ".", "Assembly files (*.S);;C files (*.c)") else: name = path if name: fileName = os.path.basename(name) filePath = os.path.join(self.proxy.getProjectPath(), fileName) regex = re.compile('[@!#$%^&*()<>?/\|}{~:]') if " " in filePath or regex.search(fileName): msg = QMessageBox() msg.setStyleSheet("background-color: #2D2D30; color: white;") msg.setModal(True) msg.setIcon(QMessageBox.Critical) msg.setText( "File name cannot contain whitespace or special characters." ) msg.setWindowTitle("File import error") msg.exec_() return alreadyInProject = False inSameDir = False if os.path.exists(filePath): for proxy in self.proxy.files: if proxy.getFilePath() == name: alreadyInProject = True inSameDir = os.path.join(self.proxy.getProjectPath(), os.path.basename(filePath)) == name if alreadyInProject: if path: return name msg = QMessageBox() msg.setStyleSheet( "background-color: #2D2D30; color: white;") msg.setModal(True) msg.setIcon(QMessageBox.Critical) msg.setText("File is already a part of the project.") msg.setWindowTitle("File import error") msg.exec_() return if not inSameDir: if path: return name msg = QMessageBox() msg.setStyleSheet( "background-color: #2D2D30; color: white;") msg.setModal(True) msg.setIcon(QMessageBox.Critical) msg.setText( "File with the same name ('{}') already exists.". format(fileName)) msg.setWindowTitle("File import error") msg.exec_() return node = None if fileName[-1] == "S": node = AssemblyFileNode() node.setIcon(0, QIcon(main.resource_path("resources/s.png"))) else: node = CFileNode() node.setIcon(0, QIcon(main.resource_path("resources/c.png"))) node.setText(0, fileName) node.path = fileName if isinstance(node, AssemblyFileNode): node.proxy = AssemblyFileProxy() elif isinstance(node, CFileNode): node.proxy = CFileProxy() node.proxy.path = fileName node.proxy.parent = self.proxy self.addChild(node) # if not inSameDir: # os.mknod(filePath) self.proxy.addFile(node.proxy) self.connectFileEventHandlers(node) if not inSameDir: shutil.copy2(name, filePath) self.setExpanded(True) self.parent().saveWorkspace() self.eventManager.newFile.emit(node.proxy) # with open(filePath, 'w') as file: # with open(name, 'r') as inputFile: # file.write(inputFile.read()) def createQuickAssemblyFile(self): fileName = self.path + ".S" node = AssemblyFileNode() node.setIcon(0, QIcon(main.resource_path("resources/s.png"))) node.setText(0, fileName) node.path = fileName node.proxy = AssemblyFileProxy() node.proxy.path = fileName node.proxy.parent = self.proxy self.addChild(node) os.mknod(os.path.join(self.proxy.getProjectPath(), fileName)) self.proxy.addFile(node.proxy) self.connectFileEventHandlers(node) self.setExpanded(True) return node def createNewFile(self): if not os.path.exists(self.proxy.getProjectPath()): self.eventManager.invalidProject.emit(self) return dialog = NewFileDialog() dialog.exec_() if dialog.result: rootPath = os.path.join(self.parent().path, self.path, dialog.result) regex = re.compile('[@!#$%^&*()<>?/\|}{~:]') if " " in dialog.result or regex.search(dialog.result): msg = QMessageBox() msg.setStyleSheet("background-color: #2D2D30; color: white;") msg.setModal(True) msg.setIcon(QMessageBox.Critical) msg.setText( "File name cannot contain whitespace or special characters." ) msg.setWindowTitle("File creation error") msg.exec_() return if os.path.exists(rootPath): msg = QMessageBox() msg.setStyleSheet("background-color: #2D2D30; color: white;") msg.setModal(True) msg.setIcon(QMessageBox.Critical) msg.setText("File with the same name already exists.") msg.setWindowTitle("File creation error") msg.exec_() return node = None if dialog.result[-1] == "S": node = AssemblyFileNode() node.setIcon(0, QIcon(main.resource_path("resources/s.png"))) else: node = CFileNode() node.setIcon(0, QIcon(main.resource_path("resources/c.png"))) node.setText(0, dialog.result) node.path = dialog.result if isinstance(node, AssemblyFileNode): node.proxy = AssemblyFileProxy() elif isinstance(node, CFileNode): node.proxy = CFileProxy() node.proxy.path = dialog.result node.proxy.parent = self.proxy self.addChild(node) os.mknod(rootPath) self.proxy.addFile(node.proxy) self.connectFileEventHandlers(node) self.setExpanded(True) self.eventManager.newFile.emit(node.proxy) def deleteProject(self): if not os.path.exists(self.proxy.getProjectPath()): self.eventManager.invalidProject.emit(self) return self.eventManager.projectRemoveRequested.emit(self) def loadProjectBackup(self): for proxy in self.proxy.files: node = None if isinstance(proxy, AssemblyFileProxy): node = AssemblyFileNode() node.setIcon(0, QIcon(main.resource_path("resources/s.png"))) elif isinstance(proxy, CFileProxy): node = CFileNode() node.setIcon(0, QIcon(main.resource_path("resources/c.png"))) if node: proxy.parent = self.proxy node.setText(0, proxy.path) node.path = proxy.path node.proxy = proxy try: if proxy.text: with open(proxy.getFilePath(), 'w') as file: file.write(proxy.text) proxy.hasUnsavedChanges = False except: print("Could not write to file {}".format( proxy.getFilePath())) self.addChild(node) self.connectFileEventHandlers(node) def loadProject(self, sourcePath=None): toBeDeleted = [] for proxy in self.proxy.files: file = None if os.path.exists(os.path.join(proxy.getFilePath())): if isinstance(proxy, AssemblyFileProxy): file = AssemblyFileNode() file.setIcon(0, QIcon(main.resource_path("resources/s.png"))) elif isinstance(proxy, CFileProxy): file = CFileNode() file.setIcon(0, QIcon(main.resource_path("resources/c.png"))) if file: proxy.parent = self.proxy file.setText(0, proxy.path) file.path = proxy.path file.proxy = proxy self.addChild(file) self.connectFileEventHandlers(file) else: msg = QMessageBox() msg.setStyleSheet("background-color: #2D2D30; color: white;") msg.setModal(True) msg.setIcon(QMessageBox.Critical) msg.setText( "Failed to import file '{}' because it is deleted from the disk." .format(proxy.path)) msg.setWindowTitle("Failed to load a file.") msg.exec_() toBeDeleted.append(proxy) for proxy in toBeDeleted: self.proxy.files.remove(proxy) projectPath = self.proxy.getProjectPath( ) if not sourcePath else sourcePath for filePath in os.listdir(projectPath): if filePath not in [file.path for file in self.proxy.files]: node = None proxy = None if filePath.lower().endswith(".s"): node = AssemblyFileNode() node.setIcon(0, QIcon(main.resource_path("resources/s.png"))) proxy = AssemblyFileProxy() elif filePath.lower().endswith(".c"): node = CFileNode() node.setIcon(0, QIcon(main.resource_path("resources/c.png"))) proxy = CFileProxy() if node: proxy.path = filePath node.setText(0, filePath) node.path = filePath node.proxy = proxy node.proxy.parent = self.proxy self.addChild(node) self.proxy.files.append(node.proxy) self.connectFileEventHandlers(node) if sourcePath: shutil.copy2( os.path.join(sourcePath, filePath), os.path.join(self.proxy.getProjectPath(), filePath))
class WorkspaceNode(Node): def __init__(self): super(WorkspaceNode, self).__init__() self.deleted = False self.eventManager = WorkspaceEventManager() self.menu = QMenu() self.menu.setStyleSheet("background-color: #3E3E42; color: white;") self.proxy = WorkspaceProxy() self.newProjectAction = QAction(QIcon(main.resource_path("resources/new_folder.png")), "New project") self.quickAssemblyProjectAction = QAction(QIcon(main.resource_path("resources/new_s.png")), "Quick assembly project") self.openFileAsAssemblyProjectAction = QAction(QIcon(main.resource_path("resources/open_s.png")), "Open file as assembly project") self.importProjectAction = QAction(QIcon(main.resource_path("resources/open_folder.png")), "Import project") self.saveAction = QAction(QIcon(main.resource_path("resources/save_folder.png")), "Save workspace") self.renameAction = QAction(QIcon(main.resource_path("resources/rename_folder.png")), "Rename workspace") self.switchAction = QAction(QIcon(main.resource_path("resources/switch_folder.png")), "Switch workspace") self.updateAction = QAction(QIcon(main.resource_path("resources/update_folder.png")), "Update workspace") self.showInfilesAction = QAction(QIcon(main.resource_path("resources/open_folder.png")), "Show in files") self.menu.addAction(self.newProjectAction) self.menu.addAction(self.importProjectAction) self.menu.addAction(self.showInfilesAction) self.menu.addSeparator() self.menu.addAction(self.quickAssemblyProjectAction) self.menu.addAction(self.openFileAsAssemblyProjectAction) self.menu.addSeparator() self.menu.addAction(self.saveAction) self.menu.addAction(self.switchAction) self.menu.addAction(self.renameAction) self.menu.addAction(self.updateAction) self.connectActions() def getContextMenu(self) -> QMenu: return self.menu def connectActions(self): self.renameAction.triggered.connect(self.renameWorkspace) self.newProjectAction.triggered.connect(self.createNewProject) self.quickAssemblyProjectAction.triggered.connect(self.createQuickAssemblyProject) self.openFileAsAssemblyProjectAction.triggered.connect(self.openFileAsAssemblyProject) self.importProjectAction.triggered.connect(self.importProject) self.saveAction.triggered.connect(self.saveWorkspace) self.updateAction.triggered.connect(self.reloadWorkspace) self.showInfilesAction.triggered.connect(self.openInFileExplorer) def openInFileExplorer(self): if not os.path.exists(self.path): self.eventManager.invalidWorkspace.emit(self) return path = self.path if platform.system() == "Windows": os.startfile(path) elif platform.system() == "Darwin": subprocess.Popen(["open", path]) else: subprocess.Popen(["xdg-open", path]) def reloadWorkspace(self): if not os.path.exists(self.path): self.eventManager.invalidWorkspace.emit(self) return self.eventManager.workspaceReload.emit(self.proxy) def renameWorkspace(self): if not os.path.exists(self.path): self.eventManager.invalidWorkspace.emit(self) return name, entered = QInputDialog.getText(None, "Rename workspace", "Enter new workspace name: ", QLineEdit.Normal, os.path.basename(self.path)) if entered: parentDir = os.path.abspath(os.path.join(self.path, os.pardir)) newPath = os.path.join(parentDir, name) regex = re.compile('[@!#$%^&*()<>?/\|}{~:]') if " " in name or regex.search(name): msg = QMessageBox() msg.setStyleSheet("background-color: #2D2D30; color: white;") msg.setModal(True) msg.setIcon(QMessageBox.Critical) msg.setText("Workspace name cannot contain whitespace or special characters.") msg.setWindowTitle("Workspace rename error") msg.exec_() return if os.path.exists(newPath): msg = QMessageBox() msg.setStyleSheet("background-color: #2D2D30; color: white;") msg.setModal(True) msg.setIcon(QMessageBox.Critical) msg.setText("Folder with the same name already exists.") msg.setWindowTitle("Workspace rename error") msg.exec_() return os.rename(self.path, newPath) oldPath = self.path self.proxy.path = self.path = newPath self.setText(0, os.path.basename(self.path)) self.saveWorkspace() self.eventManager.workspaceRename.emit(oldPath, self.proxy) def importProject(self, path=None): if not os.path.exists(self.path): self.eventManager.invalidWorkspace.emit(self) return if not path: name = QFileDialog.getExistingDirectory(None, "Import project", ".", QFileDialog.ShowDirsOnly | QFileDialog.DontResolveSymlinks) else: name = path if name: projectName = os.path.basename(name) regex = re.compile('[@!#$%^&*()<>?/\|}{~:]') if " " in projectName or regex.search(projectName): msg = QMessageBox() msg.setStyleSheet("background-color: #2D2D30; color: white;") msg.setModal(True) msg.setIcon(QMessageBox.Critical) msg.setText("Project name cannot contain whitespace or special characters.") msg.setWindowTitle("Project import error") msg.exec_() return if os.path.exists(os.path.join(self.path, projectName)) and os.path.join(self.path, projectName) != name: msg = QMessageBox() msg.setStyleSheet("background-color: #2D2D30; color: white;") msg.setModal(True) msg.setIcon(QMessageBox.Critical) msg.setText("Folder with the same name already exists.") msg.setWindowTitle("Project import error") msg.exec_() return for projectProxy in self.proxy.projects: if projectProxy.getProjectPath() == name: msg = QMessageBox() msg.setStyleSheet("background-color: #2D2D30; color: white;") msg.setModal(True) msg.setIcon(QMessageBox.Critical) msg.setText("Project is already a part of the workspace.") msg.setWindowTitle("Project import error") msg.exec_() return project = ProjectNode() project.path = projectName project.proxy.path = projectName project.proxy.parent = self.proxy project.setIcon(0, QIcon(main.resource_path("resources/project.png"))) project.setText(0, projectName) self.addChild(project) newPath = os.path.join(self.path, projectName) if os.path.join(self.path, projectName) != name: os.mkdir(newPath) self.proxy.addProject(project.proxy) sourcePath = name if os.path.join(self.path, projectName) != name else None project.loadProject(sourcePath) self.saveWorkspace() self.connectProjectEventHandlers(project) self.eventManager.projectAdded.emit(project) def createQuickAssemblyProject(self): project = self.createNewProject(quickAssembly=True) if not project: return fileNode = project.createQuickAssemblyFile() self.saveWorkspace() self.eventManager.quickAssemblyFile.emit(fileNode.proxy) def openFileAsAssemblyProject(self): if not os.path.exists(self.path): self.eventManager.invalidWorkspace.emit(self) return name, entered = QFileDialog.getOpenFileName(None, "Select assembly file to open", ".","Assembly files (*.S)") if not name: return regex = re.compile('[@!#$%^&*()<>?/\|}{~:]') fileName = os.path.basename(name) if " " in fileName or regex.search(fileName): msg = QMessageBox() msg.setStyleSheet("background-color: #2D2D30; color: white;") msg.setModal(True) msg.setIcon(QMessageBox.Critical) msg.setText("File name cannot contain whitespace or special characters.") msg.setWindowTitle("File import error") msg.exec_() return fileNode = AssemblyFileNode() fileNode.setIcon(0, QIcon(main.resource_path("resources/s.png"))) fileNode.setText(0, fileName) fileNode.path = fileName fileNode.proxy = AssemblyFileProxy() fileNode.proxy.path = fileName projectName = fileName.strip(".S") projectPath = os.path.join(self.path, projectName) while os.path.exists(projectPath): projectName += "_1" projectPath = os.path.join(self.path, projectName) os.mkdir(projectPath) project = ProjectNode() project.path = projectName project.proxy.path = projectName project.proxy.parent = self.proxy fileNode.proxy.parent = project.proxy project.setIcon(0, QIcon(main.resource_path("resources/project.png"))) project.setText(0, projectName) self.addChild(project) project.addChild(fileNode) project.proxy.addFile(fileNode.proxy) self.proxy.addProject(project.proxy) filePath = os.path.join(projectPath, fileName) shutil.copy2(name, filePath) project.connectFileEventHandlers(fileNode) self.saveWorkspace() self.connectProjectEventHandlers(project) project.setExpanded(True) self.eventManager.projectAdded.emit(project) self.eventManager.quickAssemblyFile.emit(fileNode.proxy) def createNewProject(self, quickAssembly=False, path=None): if not os.path.exists(self.path): self.eventManager.invalidWorkspace.emit(self) return dialogText = "New assembly project" if quickAssembly else "New project" if not path: name, entered = QInputDialog.getText(None, dialogText, "Enter project name: ", QLineEdit.Normal, "New project") else: name = os.path.basename(path) entered = True if entered: regex = re.compile('[@!#$%^&*()<>?/\|}{~:]') if " " in name or regex.search(name): msg = QMessageBox() msg.setStyleSheet("background-color: #2D2D30; color: white;") msg.setModal(True) msg.setIcon(QMessageBox.Critical) msg.setText("Project name cannot contain whitespace or special characters.") msg.setWindowTitle("Project creation error") msg.exec_() return if os.path.exists(os.path.join(self.path, name)): msg = QMessageBox() msg.setStyleSheet("background-color: #2D2D30; color: white;") msg.setModal(True) msg.setIcon(QMessageBox.Critical) msg.setText("Folder with the same name already exists.") msg.setWindowTitle("Project creation error") msg.exec_() return project = ProjectNode() project.path = name project.proxy.path = name project.proxy.parent = self.proxy project.setIcon(0, QIcon(main.resource_path("resources/project.png"))) project.setText(0, name) self.addChild(project) newPath = os.path.join(self.path, name) os.mkdir(newPath) self.proxy.addProject(project.proxy) self.saveWorkspace() self.connectProjectEventHandlers(project) self.eventManager.projectAdded.emit(project) return project def saveBackup(self, ws_path=None): try: previous_state = self.proxy.closedNormally except: self.proxy.closedNormally = True previous_state = self.proxy.closedNormally self.proxy.closedNormally = True if ws_path: save_path = ws_path else: save_path = self.proxy.path try: with open(os.path.join(save_path, '.backup'), 'wb') as backup: pickle.dump(self.proxy, backup, protocol=pickle.HIGHEST_PROTOCOL) except: return self.proxy.closedNormally = previous_state def saveWorkspace(self, ws_path=None): if not os.path.exists(self.path): self.eventManager.invalidWorkspace.emit(self) return try: test = self.proxy.closedNormally except: self.proxy.closedNormally = True if ws_path: save_path = ws_path else: save_path = self.proxy.path try: with open(os.path.join(save_path, '.metadata'), 'wb') as metadata: pickle.dump(self.proxy, metadata, protocol=pickle.HIGHEST_PROTOCOL) except: return self.saveBackup(save_path) def connectProjectEventHandlers(self, project: ProjectNode): project.eventManager.projectCompileRequested.connect(lambda proxy: self.eventManager.projectCompile.emit(proxy)) project.eventManager.projectDebugRequested.connect(lambda proxy: self.eventManager.projectDebug.emit(proxy)) project.eventManager.projectRunRequested.connect(lambda proxy: self.eventManager.projectRun.emit(proxy)) project.eventManager.projectRemoveRequested.connect(self.removeProject) project.eventManager.projectDeleteFromDiskRequested.connect(self.deleteProjectFromDisk) project.eventManager.projectRename.connect(lambda oldPath, project: self.eventManager.projectRename.emit(oldPath, project)) project.eventManager.fileRemove.connect(lambda fileProxy: self.eventManager.fileRemove.emit(fileProxy)) project.eventManager.fileRename.connect(lambda oldPath, fileProxy: self.renameFile(oldPath, fileProxy)) project.eventManager.fileSave.connect(lambda fileProxy: self.eventManager.fileSave.emit(fileProxy)) project.eventManager.invalidProject.connect(lambda projectNode: self.removeProject(projectNode, ask=False)) project.eventManager.projectSave.connect(lambda projectProxy: self.eventManager.projectSave.emit(projectProxy)) project.eventManager.newFile.connect(lambda fileProxy: self.eventManager.newFile.emit(fileProxy)) def renameFile(self, oldPath, fileProxy): self.saveWorkspace() self.eventManager.fileRename.emit(oldPath, fileProxy) def removeProject(self, project: ProjectNode, ask=True): if ask: answer = QMessageBox.question(None, "Delete project", "Are you sure you want to remove project {} from the workspace?".format(project.proxy.path), QMessageBox.Yes | QMessageBox.No) if not answer == QMessageBox.Yes: return else: msg = QMessageBox() msg.setStyleSheet("background-color: #2D2D30; color: white;") msg.setModal(True) msg.setIcon(QMessageBox.Critical) msg.setText("Project '{}' has been deleted from the disk.".format(project.proxy.path)) msg.setWindowTitle("Invalid project") msg.exec_() self.proxy.projects.remove(project.proxy) self.eventManager.projectRemove.emit(project) self.removeChild(project) self.saveWorkspace() def deleteProjectFromDisk(self, project: ProjectNode): answer = QMessageBox.question(None, "Delete project from disk", "Are you sure you want to delete project {} and all its content from the disk?".format( project.proxy.path), QMessageBox.Yes | QMessageBox.No) if not answer == QMessageBox.Yes: return self.proxy.projects.remove(project.proxy) shutil.rmtree(project.proxy.getProjectPath()) self.eventManager.projectRemove.emit(project) self.removeChild(project) self.saveWorkspace() def loadBackupWorkspace(self, ws_path=None): for projectProxy in self.proxy.projects: projectProxy.parent = self.proxy project = ProjectNode() project.setIcon(0, QIcon(main.resource_path("resources/project.png"))) project.setText(0, projectProxy.path) project.path = projectProxy.path project.proxy = projectProxy project.loadProjectBackup() self.addChild(project) self.connectProjectEventHandlers(project) return True def loadWorkspace(self): toBeDeleted = [] for projectProxy in self.proxy.projects: if os.path.exists(projectProxy.getProjectPath()): projectProxy.parent = self.proxy project = ProjectNode() project.setIcon(0, QIcon(main.resource_path("resources/project.png"))) project.setText(0, projectProxy.path) project.path = projectProxy.path project.proxy = projectProxy project.loadProject() self.addChild(project) self.connectProjectEventHandlers(project) else: msg = QMessageBox() msg.setStyleSheet("background-color: #2D2D30; color: white;") msg.setModal(True) msg.setIcon(QMessageBox.Critical) msg.setText("Failed to import project '{}' because it is deleted from the disk.".format(projectProxy.path)) msg.setWindowTitle("Failed to load a project.") msg.exec_() toBeDeleted.append(projectProxy) for proxy in toBeDeleted: self.proxy.projects.remove(proxy) try: self.saveWorkspace() return True except: 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))
class QPushColorButton(QPushButton): colorSelected = Signal(QColor) def __init__(self, parent=None, columns=int(0), rows=int(0), palette=list): super(QPushColorButton, self).__init__(parent, "") self.setMaximumWidth(25) self.setMaximumHeight(25) self.currentColor = QColor(255,255,255) self.setContextMenuPolicy(Qt.CustomContextMenu) self.palette = palette self._columns = columns self._rows = rows def paintEvent(self,event): super(QPushColorButton, self).paintEvent(event) size = 13 height = (self.height() - size)/2 width = (self.width() - size)/2 qp = QPainter(self) # qp.begin(self) qp.setPen(Qt.NoPen) qp.setBrush(self.currentColor) qp.drawRect(width, height, size, size) def color_menu(self, QPos=list): ''' Show color menu. Parameters ---------- QPos: (list) list of x and y location. ''' self.mainMenu = QMenu() self.mainMenu.setStyleSheet("QMenu {background-color: #222222;}") colorAction = ColorAction(self.mainMenu, self._columns, self._rows, self.palette) colorAction.colorSelected.connect(self.handleColorSelected) self.mainMenu.addAction(colorAction) pos = self.mapToGlobal(QPoint(0,0)) self.mainMenu.move(pos + QPos) self.mainMenu.show() def get_palette(self): return self.palette def set_palette(self, value=list): self.palette = value Palette= property(get_palette, set_palette) def get_columns(self): return self._columns def set_columns(self, value=int): self._columns = value Columns = property(get_columns,set_columns) def get_rows(self): return self._rows def set_rows(self, value=int): self._rows = value Rows = property(get_rows,set_rows) def handleColorSelected(self, color=QColor): self.currentColor = color self.colorSelected.emit(color) def mousePressEvent(self, event): if event.button() == Qt.LeftButton: self.color_menu(event.pos()) super(QPushColorButton, self).mousePressEvent(event) def get_current_color(self): return self.currentColor def set_current_color(self, color=QColor): self.currentColor = color self.update() CurrentColor = property(get_current_color,set_current_color)
def contextMenuEvent(self, event): menu = QMenu() menu.setStyleSheet(Theme.contextMenu.style) menu.setFont(Theme.contextMenu.font) menu.addActions(self.actions()) menu.exec_(event.globalPos())