def __zoom(self): """Zoom functions. + 'zoom to fit' function connections. + Zoom text buttons """ self.action_zoom_to_fit.triggered.connect(self.main_canvas.zoom_to_fit) self.ResetCanvas.clicked.connect(self.main_canvas.zoom_to_fit) zoom_menu = QMenu(self) def zoom_level(value: int): """Return a function that set the specified zoom value.""" @Slot() def func(): self.zoom_bar.setValue(value) return func for level in range( self.zoom_bar.minimum() - self.zoom_bar.minimum() % 100 + 100, 500 + 1, 100): action = QAction(f'{level}%', self) action.triggered.connect(zoom_level(level)) zoom_menu.addAction(action) action = QAction("customize", self) action.triggered.connect(self.customize_zoom) zoom_menu.addAction(action) self.zoom_button.setMenu(zoom_menu)
def __free_move(self): """Menu of free move mode.""" free_move_mode_menu = QMenu(self) def free_move_mode_func(j: int, icon_qt: QIcon): @Slot() def func() -> None: self.free_move_button.setIcon(icon_qt) self.main_canvas.set_free_move(j) self.entities_tab.setCurrentIndex(0) self.inputs_widget.variable_stop.click() return func for i, (text, icon, tip) in enumerate([ ("View mode", "free_move_off", "Disable free move mode."), ("Translate mode", "translate", "Edit by 2 DOF moving."), ("Rotate mode", "rotate", "Edit by 1 DOF moving."), ("Reflect mode", "reflect", "Edit by flip axis."), ]): action = QAction(QIcon(QPixmap(f":/icons/{icon}.png")), text, self) action.triggered.connect(free_move_mode_func(i, action.icon())) action.setShortcut(QKeySequence(f"Ctrl+{i + 1}")) action.setShortcutContext(Qt.WindowShortcut) action.setStatusTip(tip) free_move_mode_menu.addAction(action) if i == 0: self.free_move_disable = action self.free_move_button.setMenu(free_move_mode_menu) # "Link adjust" function self.link_free_move_confirm.clicked.connect( self.main_canvas.emit_free_move_all)
def __free_move(self): """Menu of free move mode.""" free_move_mode_menu = QMenu(self) def free_move_mode_func(j: int, icon_qt: QIcon): @pyqtSlot() def func(): self.free_move_button.setIcon(icon_qt) self.MainCanvas.setFreeMove(j) self.EntitiesTab.setCurrentIndex(0) self.InputsWidget.variable_stop.click() return func for i, (text, icon, tip) in enumerate(( ("View mode", "free_move_off", "Disable free move mode."), ("Translate mode", "translate", "Edit by 2 DOF moving."), ("Rotate mode", "rotate", "Edit by 1 DOF moving."), ("Reflect mode", "reflect", "Edit by flip axis."), )): action = QAction(QIcon(QPixmap(f":/icons/{icon}.png")), text, self) action.triggered.connect(free_move_mode_func(i, action.icon())) action.setShortcut(QKeySequence(f"Ctrl+{i + 1}")) action.setShortcutContext(Qt.WindowShortcut) action.setStatusTip(tip) free_move_mode_menu.addAction(action) if i == 0: self.free_move_disable = action self.free_move_button.setMenu(free_move_mode_menu) # Link free move by expression table. self.link_free_move_slider.sliderReleased.connect( self.MainCanvas.emit_free_move_all)
def __enable_link_context(self): """Enable / disable link's QAction, same as point table.""" selection = self.EntitiesLink.selectedRows() count = len(selection) row = self.EntitiesLink.currentRow() self.action_link_context_add.setVisible(count == 0) selected_one = count == 1 not_ground = row > 0 any_link = row > -1 self.action_link_context_edit.setVisible(any_link and selected_one) self.action_link_context_delete.setVisible(not_ground and (count > 0)) self.action_link_context_copydata.setVisible(any_link and selected_one) self.action_link_context_release.setVisible((row == 0) and selected_one) self.action_link_context_constrain.setVisible(not_ground and selected_one) self.pop_menu_link_merge.menuAction().setVisible(count > 1) def ml_func(order: int) -> Callable[[], None]: """Generate a merge function.""" @pyqtSlot(int) def func(): self.__merge_link(order, selection) return func for i, row in enumerate(selection): name = self.EntitiesLink.item(row, 0).text() action = QAction(f"Base on \"{name}\"", self) action.triggered.connect(ml_func(i)) self.pop_menu_link_merge.addAction(action)
def __init__(self): super(MainWindow, self).__init__() self.setupUi(self) self.nc_editor = NCEditor(self) self.nc_code_layout.addWidget(self.nc_editor) h_size = 15 self.parameter_table = MathTableWidget([ r"$a$", r"$b$", r"$\zeta$", r"$\omega_n$", ], h_size, self) self.parameter_table.verticalHeader().hide() self.parameter_table.setRowCount(1) self.parameter_table.setFixedHeight(h_size * 4) self.parameter_table.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.parameter_table.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) for i, v in enumerate([1., 20., 0.707, 1000.]): self.parameter_table.setCellWidget(0, i, _spinbox(v)) self.nc_code_layout.insertWidget(1, self.parameter_table) self.env = "" self.file_name = "" self.set_locate(QStandardPaths.writableLocation(QStandardPaths.DesktopLocation)) # Default RE compiler. self.re_compiler.setPlaceholderText(DEFAULT_NC_SYNTAX) # Chart widgets. self.charts = [QChart() for _ in range(8)] for chart, layout in zip(self.charts, [self.s_layout, self.v_layout, self.a_layout, self.j_layout] * 2): chart.setTheme(QChart.ChartThemeLight) view = QChartView(chart) view.setContextMenuPolicy(Qt.CustomContextMenu) view.customContextMenuRequested.connect(self.__save_chart_func(view)) view.setRenderHint(QPainter.Antialiasing) layout.addWidget(view) # Chart menu self.chart_menu = QMenu(self) self.save_chart_action = QAction("Save as image", self) self.chart_menu.addAction(self.save_chart_action) self.copy_chart_action = QAction("Copy as pixmap", self) self.chart_menu.addAction(self.copy_chart_action) # Splitter self.main_splitter.setSizes([300, 500])
def contextMenuEvent(self, event): """Custom context menu.""" # Spell refactor. menu: QMenu = self.createStandardContextMenu() menu.addSeparator() correction_action = QAction("&Refactor Words", self) correction_action.triggered.connect(self.__refactor) menu.addAction(correction_action) menu.exec(self.mapToGlobal(event.pos()))
def __point_context_menu(self): """EntitiesPoint context menu + Add /////// + New Link + Edit + Grounded + Multiple joint - Point0 - Point1 - ... + Copy table data + Copy coordinate + Clone ------- + Delete """ self.entities_point_widget.customContextMenuRequested.connect( self.point_context_menu) self.pop_menu_point = QMenu(self) self.pop_menu_point.setSeparatorsCollapsible(True) self.action_point_context_add = QAction("&Add", self) self.action_point_context_add.triggered.connect(self.new_point) self.pop_menu_point.addAction(self.action_point_context_add) # New Link self.pop_menu_point.addAction(self.action_new_link) self.action_point_context_edit = QAction("&Edit", self) self.action_point_context_edit.triggered.connect(self.edit_point) self.pop_menu_point.addAction(self.action_point_context_edit) self.action_point_context_lock = QAction("&Grounded", self) self.action_point_context_lock.setCheckable(True) self.action_point_context_lock.triggered.connect(self.lock_points) self.pop_menu_point.addAction(self.action_point_context_lock) self.pop_menu_point_merge = QMenu(self) self.pop_menu_point_merge.setTitle("Multiple joint") self.pop_menu_point.addMenu(self.pop_menu_point_merge) self.action_point_context_copydata = QAction("&Copy table data", self) self.action_point_context_copydata.triggered.connect( self.copy_points_table) self.pop_menu_point.addAction(self.action_point_context_copydata) self.action_point_context_copy_coord = QAction("&Copy coordinate", self) self.action_point_context_copy_coord.triggered.connect(self.copy_coord) self.pop_menu_point.addAction(self.action_point_context_copy_coord) self.action_point_context_clone = QAction("C&lone", self) self.action_point_context_clone.triggered.connect(self.clone_point) self.pop_menu_point.addAction(self.action_point_context_clone) self.pop_menu_point.addSeparator() self.action_point_context_delete = QAction("&Delete", self) self.action_point_context_delete.triggered.connect( self.delete_selected_points) self.pop_menu_point.addAction(self.action_point_context_delete)
def __enable_point_context(self): """Adjust the status of QActions. What ever we have least one point or not, need to enable / disable QAction. """ selection = self.EntitiesPoint.selectedRows() count = len(selection) # If connecting with the ground. if count: self.action_point_context_lock.setChecked( all('ground' in self.EntitiesPoint.item(row, 1).text() for row in self.EntitiesPoint.selectedRows())) # If no any points selected. for action in ( self.action_point_context_add, self.action_canvas_context_add, self.action_canvas_context_grounded_add, ): action.setVisible(count == 0) self.action_point_context_lock.setVisible(count > 0) self.action_point_context_delete.setVisible(count > 0) # If a point selected. for action in ( self.action_point_context_edit, self.action_point_context_copyPoint, self.action_point_context_copydata, self.action_point_context_copyCoord, ): action.setVisible(count == 1) # If two or more points selected. self.action_new_link.setVisible(count > 1) self.pop_menu_point_merge.menuAction().setVisible(count > 1) def mj_func(order: int): """Generate a merge function.""" @pyqtSlot() def func(): self.__to_multiple_joint(order, selection) return func for i, p in enumerate(selection): action = QAction(f"Base on Point{p}", self) action.triggered.connect(mj_func(i)) self.pop_menu_point_merge.addAction(action)
def _enablePointContext(self): """Adjust the status of QActions. What ever we have least one point or not, need to enable / disable QAction. """ selectedRows = self.EntitiesPoint.selectedRows() selectionCount = len(selectedRows) row = self.EntitiesPoint.currentRow() #If connecting with the ground. if selectionCount: self.action_point_context_lock.setChecked( all('ground' in self.EntitiesPoint.item(row, 1).text() for row in self.EntitiesPoint.selectedRows())) #If no any points selected. for action in ( self.action_point_context_add, self.action_canvas_context_add, self.action_canvas_context_fix_add, ): action.setVisible(selectionCount <= 0) self.action_point_context_lock.setVisible(row > -1) self.action_point_context_delete.setVisible(row > -1) #If a point selected. for action in ( self.action_point_context_edit, self.action_point_context_copyPoint, self.action_point_context_copydata, self.action_point_context_copyCoord, ): action.setVisible(row > -1) action.setEnabled(selectionCount == 1) #If two or more points selected. self.action_New_Link.setVisible(selectionCount > 1) self.popMenu_point_merge.menuAction().setVisible(selectionCount > 1) def mjFunc(i): """Generate a merge function.""" return lambda: _toMultipleJoint(self, i, selectedRows) for i, p in enumerate(selectedRows): action = QAction("Base on Point{}".format(p), self) action.triggered.connect(mjFunc(i)) self.popMenu_point_merge.addAction(action)
def __init__(self, parent: 'mw.MainWindow'): """Reference names: + IO functions from main window. + Table data from PMKS expression. + Graph data function from main window. """ super(StructureSynthesis, self).__init__(parent) self.setupUi(self) self.save_edges_auto_label.setStatusTip( self.save_edges_auto.statusTip()) # Function references self.outputTo = parent.outputTo self.saveReplyBox = parent.saveReplyBox self.inputFrom = parent.inputFrom self.jointDataFunc = parent.EntitiesPoint.dataTuple self.linkDataFunc = parent.EntitiesLink.dataTuple self.getGraph = parent.getGraph # Splitters self.splitter.setStretchFactor(0, 2) self.splitter.setStretchFactor(1, 15) # Answer list. self.answer: List[Graph] = [] # Signals self.NL_input.valueChanged.connect(self.__adjust_structure_data) self.NJ_input.valueChanged.connect(self.__adjust_structure_data) self.graph_engine.addItems(engines) self.structure_list.customContextMenuRequested.connect( self.__topologic_result_context_menu) """Context menu + Add to collections + Copy edges + Copy image """ self.pop_menu_topo = QMenu(self) self.add_collection = QAction( QIcon(QPixmap(":/icons/collections.png")), "Add to collections", self) self.copy_edges = QAction("Copy edges", self) self.copy_image = QAction("Copy image", self) self.pop_menu_topo.addActions( [self.add_collection, self.copy_edges, self.copy_image]) self.NL_input_old_value = 0 self.NJ_input_old_value = 0 self.clear()
def __link_context_menu(self): """EntitiesLink context menu + Add + Edit + Merge links - Link0 - Link1 - ... + Copy table data + Release + Constrain ------- + Delete """ self.entities_link_widget.customContextMenuRequested.connect( self.link_context_menu) self.pop_menu_link = QMenu(self) self.pop_menu_link.setSeparatorsCollapsible(True) self.action_link_context_add = QAction("&Add", self) self.action_link_context_add.triggered.connect(self.new_link) self.pop_menu_link.addAction(self.action_link_context_add) self.action_link_context_edit = QAction("&Edit", self) self.action_link_context_edit.triggered.connect(self.edit_link) self.pop_menu_link.addAction(self.action_link_context_edit) self.pop_menu_link_merge = QMenu(self) self.pop_menu_link_merge.setTitle("Merge links") self.pop_menu_link.addMenu(self.pop_menu_link_merge) self.action_link_context_copydata = QAction("&Copy table data", self) self.action_link_context_copydata.triggered.connect( self.copy_links_table) self.pop_menu_link.addAction(self.action_link_context_copydata) self.action_link_context_release = QAction("&Release", self) self.action_link_context_release.triggered.connect(self.release_ground) self.pop_menu_link.addAction(self.action_link_context_release) self.action_link_context_constrain = QAction("C&onstrain", self) self.action_link_context_constrain.triggered.connect( self.constrain_link) self.pop_menu_link.addAction(self.action_link_context_constrain) self.pop_menu_link.addSeparator() self.action_link_context_delete = QAction("&Delete", self) self.action_link_context_delete.triggered.connect( self.delete_selected_links) self.pop_menu_link.addAction(self.action_link_context_delete)
def __init__(self, parent): super(NumberAndTypeSynthesis, self).__init__(parent) self.setupUi(self) self.outputTo = parent.outputTo self.saveReplyBox = parent.saveReplyBox self.inputFrom = parent.inputFrom self.splitter.setStretchFactor(0, 2) self.splitter.setStretchFactor(1, 15) self.answer = [] self.save_edges_auto_label.setStatusTip(self.save_edges_auto.statusTip()) self.NL_input.valueChanged.connect(self.adjust_NJ_NL_dof) self.NJ_input.valueChanged.connect(self.adjust_NJ_NL_dof) self.graph_engine.addItems(EngineList) self.graph_engine.setCurrentIndex(2) self.graph_link_as_node.clicked.connect(self.on_reload_atlas_clicked) self.graph_engine.currentIndexChanged.connect( self.on_reload_atlas_clicked ) self.Topologic_result.customContextMenuRequested.connect( self.Topologic_result_context_menu ) """Context menu + Add to collections + Copy edges + Copy image """ self.popMenu_topo = QMenu(self) self.add_collection = QAction( QIcon(QPixmap(":/icons/collections.png")), "Add to collections", self ) self.copy_edges = QAction("Copy edges", self) self.copy_image = QAction("Copy image", self) self.popMenu_topo.addActions([ self.add_collection, self.copy_edges, self.copy_image ]) self.jointDataFunc = parent.Entities_Point.data self.linkDataFunc = parent.Entities_Link.data self.clear()
class MainWindow(QMainWindow, Ui_MainWindow): """Main window of kmol editor.""" def __init__(self): super(MainWindow, self).__init__(None) self.setupUi(self) # Start new window. @pyqtSlot() def new_main_window(): XStream.back() run = self.__class__() run.show() self.action_New_Window.triggered.connect(new_main_window) # Text editor self.text_editor = TextEditor(self) self.h_splitter.addWidget(self.text_editor) self.text_editor.word_changed.connect(self.__set_not_saved_title) self.edge_line_option.toggled.connect(self.text_editor.setEdgeMode) self.trailing_blanks_option.toggled.connect(self.text_editor.set_remove_trailing_blanks) # Highlighters self.highlighter_option.addItems(sorted(QSCIHIGHLIGHTERS)) self.highlighter_option.setCurrentText("Markdown") self.highlighter_option.currentTextChanged.connect( self.text_editor.set_highlighter ) # Tree widget context menu. self.tree_widget.customContextMenuRequested.connect( self.on_tree_widget_context_menu ) self.popMenu_tree = QMenu(self) self.popMenu_tree.setSeparatorsCollapsible(True) self.popMenu_tree.addAction(self.action_new_project) self.popMenu_tree.addAction(self.action_open) self.tree_add = QAction("&Add Node", self) self.tree_add.triggered.connect(self.add_node) self.tree_add.setShortcut("Ctrl+I") self.tree_add.setShortcutContext(Qt.WindowShortcut) self.popMenu_tree.addAction(self.tree_add) self.popMenu_tree.addSeparator() self.tree_path = QAction("Set Path", self) self.tree_path.triggered.connect(self.set_path) self.popMenu_tree.addAction(self.tree_path) self.tree_refresh = QAction("&Refresh from Path", self) self.tree_refresh.triggered.connect(self.refresh_proj) self.popMenu_tree.addAction(self.tree_refresh) self.tree_openurl = QAction("&Open from Path", self) self.tree_openurl.triggered.connect(self.open_path) self.popMenu_tree.addAction(self.tree_openurl) self.action_save.triggered.connect(self.save_proj) self.popMenu_tree.addAction(self.action_save) self.tree_copy = QAction("Co&py", self) self.tree_copy.triggered.connect(self.copy_node) self.popMenu_tree.addAction(self.tree_copy) self.tree_clone = QAction("C&lone", self) self.tree_clone.triggered.connect(self.clone_node) self.popMenu_tree.addAction(self.tree_clone) self.tree_copy_tree = QAction("Recursive Copy", self) self.tree_copy_tree.triggered.connect(self.copy_node_recursive) self.popMenu_tree.addAction(self.tree_copy_tree) self.tree_clone_tree = QAction("Recursive Clone", self) self.tree_clone_tree.triggered.connect(self.clone_node_recursive) self.popMenu_tree.addAction(self.tree_clone_tree) self.popMenu_tree.addSeparator() self.tree_delete = QAction("&Delete", self) self.tree_delete.triggered.connect(self.delete_node) self.popMenu_tree.addAction(self.tree_delete) self.tree_close = QAction("&Close", self) self.tree_close.triggered.connect(self.close_file) self.popMenu_tree.addAction(self.tree_close) self.tree_main.header().setSectionResizeMode(QHeaderView.ResizeToContents) # Console self.console.setFont(self.text_editor.font) if not ARGUMENTS.debug_mode: XStream.stdout().messageWritten.connect(self.__append_to_console) XStream.stderr().messageWritten.connect(self.__append_to_console) for info in INFO: print(info) print('-' * 7) # Searching function. find_next = QShortcut(QKeySequence("F3"), self) find_next.activated.connect(self.find_next_button.click) find_previous = QShortcut(QKeySequence("F4"), self) find_previous.activated.connect(self.find_previous_button.click) find_tab = QShortcut(QKeySequence("Ctrl+F"), self) find_tab.activated.connect(lambda: self.panel_widget.setCurrentIndex(1)) find_project = QShortcut(QKeySequence("Ctrl+Shift+F"), self) find_project.activated.connect(self.find_project_button.click) # Replacing function. replace = QShortcut(QKeySequence("Ctrl+R"), self) replace.activated.connect(self.replace_node_button.click) replace_project = QShortcut(QKeySequence("Ctrl+Shift+R"), self) replace_project.activated.connect(self.replace_project_button.click) # Translator. self.panel_widget.addTab(TranslatorWidget(self), "Translator") # Node edit function. (Ctrl + ArrowKey) move_up_node = QShortcut(QKeySequence("Ctrl+Up"), self) move_up_node.activated.connect(self.__move_up_node) move_down_node = QShortcut(QKeySequence("Ctrl+Down"), self) move_down_node.activated.connect(self.__move_down_node) move_right_node = QShortcut(QKeySequence("Ctrl+Right"), self) move_right_node.activated.connect(self.__move_right_node) move_left_node = QShortcut(QKeySequence("Ctrl+Left"), self) move_left_node.activated.connect(self.__move_left_node) # Run script button. run_sript = QShortcut(QKeySequence("F5"), self) run_sript.activated.connect(self.exec_button.click) self.macros_toolbar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) # Splitter self.h_splitter.setStretchFactor(0, 10) self.h_splitter.setStretchFactor(1, 60) self.v_splitter.setStretchFactor(0, 30) self.v_splitter.setStretchFactor(1, 10) # Data self.data = DataDict() self.data.not_saved.connect(self.__set_not_saved_title) self.data.all_saved.connect(self.__set_saved_title) self.env = QStandardPaths.writableLocation(QStandardPaths.DesktopLocation) for filename in ARGUMENTS.r: filename = QFileInfo(filename).canonicalFilePath() if not filename: return root_node = QTreeRoot(QFileInfo(filename).baseName(), filename, '') self.tree_main.addTopLevelItem(root_node) parse(root_node, self.data) self.__add_macros() def dragEnterEvent(self, event): """Drag file in to our window.""" if event.mimeData().hasUrls(): event.acceptProposedAction() def dropEvent(self, event): """Drop file in to our window.""" for url in event.mimeData().urls(): filename = url.toLocalFile() root_node = QTreeRoot(QFileInfo(filename).baseName(), filename, '') self.tree_main.addTopLevelItem(root_node) parse(root_node, self.data) self.tree_main.setCurrentItem(root_node) self.__add_macros() event.acceptProposedAction() @pyqtSlot() def __set_not_saved_title(self): """Show star sign on window title.""" if '*' not in self.windowTitle(): self.setWindowTitle(self.windowTitle() + '*') @pyqtSlot() def __set_saved_title(self): """Remove star sign on window title.""" self.setWindowTitle(self.windowTitle().replace('*', '')) @pyqtSlot(str) def __append_to_console(self, log): """After inserted the text, move cursor to end.""" self.console.moveCursor(QTextCursor.End) self.console.insertPlainText(log) self.console.moveCursor(QTextCursor.End) @pyqtSlot(QPoint, name='on_tree_widget_context_menu') def __tree_context_menu(self, point: QPoint): """Operations.""" self.__action_changed() self.popMenu_tree.exec_(self.tree_widget.mapToGlobal(point)) @pyqtSlot(name='on_action_new_project_triggered') def new_proj(self): """New file.""" filename, _ = QFileDialog.getSaveFileName( self, "New Project", self.env, SUPPORT_FILE_FORMATS ) if not filename: return self.env = QFileInfo(filename).absolutePath() root_node = QTreeRoot( QFileInfo(filename).baseName(), filename, str(self.data.new_num()) ) suffix_text = file_suffix(filename) if suffix_text == 'md': root_node.setIcon(0, file_icon("markdown")) elif suffix_text == 'html': root_node.setIcon(0, file_icon("html")) elif suffix_text == 'kmol': root_node.setIcon(0, file_icon("kmol")) else: root_node.setIcon(0, file_icon("txt")) self.tree_main.addTopLevelItem(root_node) @pyqtSlot(name='on_action_open_triggered') def open_proj(self): """Open file.""" file_names, ok = QFileDialog.getOpenFileNames( self, "Open Projects", self.env, SUPPORT_FILE_FORMATS ) if not ok: return def in_widget(path: str) -> int: """Is name in tree widget.""" for i in range(self.tree_main.topLevelItemCount()): if path == self.tree_main.topLevelItem(i).text(1): return i return -1 for file_name in file_names: self.env = QFileInfo(file_name).absolutePath() index = in_widget(file_name) if index == -1: root_node = QTreeRoot(QFileInfo(file_name).baseName(), file_name, '') self.tree_main.addTopLevelItem(root_node) parse(root_node, self.data) self.tree_main.setCurrentItem(root_node) else: self.tree_main.setCurrentItem(self.tree_main.topLevelItem(index)) self.__add_macros() @pyqtSlot() def refresh_proj(self): """Re-parse the file node.""" node = self.tree_main.currentItem() if not node.text(1): QMessageBox.warning( self, "No path", "Can only refresh from valid path." ) parse(node, self.data) self.tree_main.setCurrentItem(node) self.text_editor.setText(self.data[int(node.text(2))]) @pyqtSlot() def open_path(self): """Open path of current node.""" node = self.tree_main.currentItem() filename = getpath(node) QDesktopServices.openUrl(QUrl(filename)) print("Open: {}".format(filename)) @pyqtSlot() def add_node(self): """Add a node at current item.""" node = self.tree_main.currentItem() new_node = QTreeItem( "New node", "", str(self.data.new_num()) ) if node.childCount() or node.text(1): node.addChild(new_node) return parent = node.parent() if parent: parent.insertChild(parent.indexOfChild(node) + 1, new_node) return self.tree_main.indexOfTopLevelItem( self.tree_main.indexOfTopLevelItem(node) + 1, new_node ) @pyqtSlot() def set_path(self): """Set file directory.""" node = self.tree_main.currentItem() filename, ok = QFileDialog.getOpenFileName( self, "Open File", self.env, SUPPORT_FILE_FORMATS ) if not ok: return self.env = QFileInfo(filename).absolutePath() project_path = QDir(_get_root(node).text(1)) project_path.cdUp() node.setText(1, project_path.relativeFilePath(filename)) @pyqtSlot() def copy_node(self): """Copy current node.""" node_origin = self.tree_main.currentItem() parent = node_origin.parent() node = node_origin.clone() node.takeChildren() code = self.data.new_num() self.data[code] = self.data[int(node.text(2))] node.setText(2, str(code)) parent.insertChild(parent.indexOfChild(node_origin) + 1, node) @pyqtSlot() def clone_node(self): """Copy current node with same pointer.""" node_origin = self.tree_main.currentItem() parent = node_origin.parent() node = node_origin.clone() node.takeChildren() parent.insertChild(parent.indexOfChild(node_origin) + 1, node) @pyqtSlot() def copy_node_recursive(self): """Copy current node and its sub-nodes.""" node_origin = self.tree_main.currentItem() parent = node_origin.parent() node_origin_copy = node_origin.clone() def new_pointer(node: QTreeWidgetItem): """Give a new pointer code for node.""" code = self.data.new_num() self.data[code] = self.data[int(node.text(2))] node.setText(2, str(code)) for i in range(node.childCount()): new_pointer(node.child(i)) new_pointer(node_origin_copy) parent.insertChild(parent.indexOfChild(node_origin) + 1, node_origin_copy) @pyqtSlot() def clone_node_recursive(self): """Copy current node and its sub-nodes with same pointer.""" node_origin = self.tree_main.currentItem() parent = node_origin.parent() parent.insertChild(parent.indexOfChild(node_origin) + 1, node_origin.clone()) @pyqtSlot() def save_proj(self, index: Optional[int] = None, *, for_all: bool = False): """Save project and files.""" if for_all: for row in range(self.tree_main.topLevelItemCount()): self.save_proj(row) return node = self.tree_main.currentItem() if not node: return if index is None: root = _get_root(node) else: root = self.tree_main.topLevelItem(index) self.__save_current() save_file(root, self.data) self.data.save_all() def __save_current(self): """Save the current text of editor.""" self.text_editor.remove_trailing_blanks() item = self.tree_main.currentItem() if item: self.data[int(item.text(2))] = self.text_editor.text() @pyqtSlot() def delete_node(self): """Delete the current item.""" node = self.tree_main.currentItem() parent = node.parent() self.tree_main.setCurrentItem(parent) self.__delete_node_data(node) parent.removeChild(node) def __delete_node_data(self, node: QTreeWidgetItem): """Delete data from data structure.""" name = node.text(0) if name.startswith('@'): for action in self.macros_toolbar.actions(): if action.text() == name[1:]: self.macros_toolbar.removeAction(action) del self.data[int(node.text(2))] for i in range(node.childCount()): self.__delete_node_data(node.child(i)) @pyqtSlot() def close_file(self): """Close project node.""" if not self.data.is_all_saved(): reply = QMessageBox.question( self, "Not saved", "Do you went to save the project?", QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel, QMessageBox.Save ) if reply == QMessageBox.Save: self.save_proj() elif reply == QMessageBox.Cancel: return root = self.tree_main.currentItem() self.__delete_node_data(root) self.tree_main.takeTopLevelItem(self.tree_main.indexOfTopLevelItem(root)) self.text_editor.clear() @pyqtSlot() def __move_up_node(self): """Move up current node.""" node = self.tree_main.currentItem() if not node: return tree_main = node.treeWidget() parent = node.parent() if parent: # Is sub-node. index = parent.indexOfChild(node) if index == 0: return parent.removeChild(node) parent.insertChild(index - 1, node) else: # Is root. index = tree_main.indexOfTopLevelItem(node) if index == 0: return tree_main.takeTopLevelItem(index) tree_main.insertTopLevelItem(index - 1, node) tree_main.setCurrentItem(node) self.__root_unsaved() @pyqtSlot() def __move_down_node(self): """Move down current node.""" node = self.tree_main.currentItem() if not node: return tree_main = node.treeWidget() parent = node.parent() if parent: # Is sub-node. index = parent.indexOfChild(node) if index == parent.childCount() - 1: return parent.removeChild(node) parent.insertChild(index + 1, node) else: # Is root. index = tree_main.indexOfTopLevelItem(node) if index == tree_main.topLevelItemCount() - 1: return tree_main.takeTopLevelItem(index) tree_main.insertTopLevelItem(index + 1, node) tree_main.setCurrentItem(node) self.__root_unsaved() @pyqtSlot() def __move_right_node(self): """Move right current node.""" node = self.tree_main.currentItem() if not node: return tree_main = node.treeWidget() parent = node.parent() if parent: # Is sub-node. index = parent.indexOfChild(node) if index == 0: return parent.removeChild(node) parent.child(index - 1).addChild(node) else: # Is root. index = tree_main.indexOfTopLevelItem(node) if index == 0: return tree_main.takeTopLevelItem(index) tree_main.topLevelItem(index - 1).addChild(node) tree_main.setCurrentItem(node) self.__root_unsaved() @pyqtSlot() def __move_left_node(self): """Move left current node.""" node = self.tree_main.currentItem() if not node: return tree_main = node.treeWidget() parent = node.parent() if not parent: return # Must be a sub-node. grand_parent = parent.parent() if not grand_parent: return index = grand_parent.indexOfChild(parent) parent.removeChild(node) grand_parent.insertChild(index + 1, node) tree_main.setCurrentItem(node) self.__root_unsaved() @pyqtSlot(name='on_action_about_qt_triggered') def __about_qt(self): """Qt about.""" QMessageBox.aboutQt(self) @pyqtSlot(name='on_action_about_triggered') def __about(self): """Kmol editor about.""" QMessageBox.about(self, "About Kmol Editor", '\n'.join(INFO + ( '', "Author: " + __author__, "Email: " + __email__, __copyright__, "License: " + __license__, ))) @pyqtSlot(name='on_action_mde_tw_triggered') def __mde_tw(self): """Mde website.""" QDesktopServices.openUrl(QUrl("http://mde.tw")) @pyqtSlot(name='on_exec_button_clicked') def __exec(self): """Run the script from current text editor.""" self.__exec_script(self.text_editor.text()) def __exec_script(self, code: Union[int, str]): """Run a script in a new thread.""" self.__save_current() variables = { # Qt file operation classes. 'QStandardPaths': QStandardPaths, 'QFileInfo': QFileInfo, 'QDir': QDir, } node = self.tree_main.currentItem() variables['node'] = node if node: root = _get_root(node) variables['root'] = root variables['root_path'] = QFileInfo(root.text(1)).absoluteFilePath() variables['node_path'] = getpath(node) def chdir_tree(path: str): if QFileInfo(path).isDir(): chdir(path) elif QFileInfo(path).isFile(): chdir(QFileInfo(path).absolutePath()) variables['chdir'] = chdir_tree thread = Thread( target=exec, args=(self.data[code] if type(code) == int else code, variables) ) thread.start() @pyqtSlot(QTreeWidgetItem, QTreeWidgetItem, name='on_tree_main_currentItemChanged') def __switch_data( self, current: QTreeWidgetItem, previous: QTreeWidgetItem ): """Switch node function. + Auto collapse and expand function. + Important: Store the string data. """ if self.auto_expand_option.isChecked(): self.tree_main.expandItem(current) self.tree_main.scrollToItem(current) if previous: self.data[int(previous.text(2))] = self.text_editor.text() if current: # Auto highlight. path = current.text(1) filename = QFileInfo(path).fileName() suffix = QFileInfo(filename).suffix() if current.text(0).startswith('@'): self.highlighter_option.setCurrentText("Python") else: self.highlighter_option.setCurrentText("Markdown") if path: for name_m, suffix_m in HIGHLIGHTER_SUFFIX.items(): if suffix in suffix_m: self.highlighter_option.setCurrentText(name_m) break else: for name_m, filename_m in HIGHLIGHTER_FILENAME.items(): if filename in filename_m: self.highlighter_option.setCurrentText(name_m) break self.text_editor.setText(self.data[int(current.text(2))]) self.__action_changed() @pyqtSlot(QTreeWidgetItem, int, name='on_tree_main_itemChanged') def __reload_nodes(self, node: QTreeWidgetItem, _: int): """Mark edited node as unsaved.""" name = node.text(0) code = int(node.text(2)) if name.startswith('@'): self.__add_macro(name[1:], code) self.__root_unsaved() def __root_unsaved(self): """Let tree to re-save.""" node = self.tree_main.currentItem() if node: self.data.set_saved(int(_get_root(node).text(2)), False) def __action_changed(self): node = self.tree_main.currentItem() has_item = bool(node) is_root = (not node.parent()) if has_item else False for action in ( self.action_open, self.action_new_project, ): action.setVisible(is_root or not has_item) self.tree_close.setVisible(has_item and is_root) for action in ( self.tree_add, self.tree_refresh, self.tree_openurl, self.action_save, ): action.setVisible(has_item) for action in ( self.tree_copy, self.tree_clone, self.tree_copy_tree, self.tree_clone_tree, self.tree_path, self.tree_delete, ): action.setVisible(has_item and not is_root) def __add_macros(self): """Add macro buttons from data structure.""" for name, code in self.data.macros(): self.__add_macro(name, code) def __add_macro(self, name: str, code: Union[int, Hashable]): """Add macro button.""" for action in self.macros_toolbar.actions(): if action.text() == name: break else: action = self.macros_toolbar.addAction(QIcon(QPixmap(":icons/python.png")), name) action.triggered.connect(lambda: self.__exec_script(code)) def __find_text(self, forward: bool): """Find text by options.""" if not self.search_bar.text(): self.search_bar.setText(self.search_bar.placeholderText()) pos = self.text_editor.positionFromLineIndex( *self.text_editor.getCursorPosition() ) if not self.text_editor.findFirst( self.search_bar.text(), self.re_option.isChecked(), self.match_case_option.isChecked(), self.whole_word_option.isChecked(), self.wrap_around.isChecked(), forward, *self.text_editor.lineIndexFromPosition(pos if forward else pos - 1) ): QMessageBox.information( self, "Text not found.", "\"{}\" is not in current document".format( self.search_bar.text() ) ) @pyqtSlot(name='on_find_next_button_clicked') def __find_next(self): """Find to next.""" self.__find_text(True) @pyqtSlot(name='on_find_previous_button_clicked') def __find_previous(self): """Find to previous.""" self.__find_text(False) @pyqtSlot(name='on_replace_node_button_clicked') def __replace(self): """Replace current text by replace bar.""" self.text_editor.replace(self.replace_bar.text()) self.text_editor.findNext() @pyqtSlot(name='on_find_project_button_clicked') def __find_project(self): """Find in all project.""" self.find_list.clear() node_current = self.tree_main.currentItem() if not node_current: return root = _get_root(node_current) if not self.search_bar.text(): self.search_bar.setText(self.search_bar.placeholderText()) text = self.search_bar.text() flags = re.MULTILINE if not self.re_option.isChecked(): text = re.escape(text) if self.whole_word_option.isChecked(): text = r'\b' + text + r'\b' if not self.match_case_option.isChecked(): flags |= re.IGNORECASE def add_find_result(code: int, last_name: str, start: int, end: int): """Add result to list.""" item = QListWidgetItem("{}: [{}, {}]".format(code, start, end)) item.setToolTip(last_name) self.find_list.addItem(item) def find_in_nodes(node: QTreeWidgetItem, last_name: str = ''): """Find the word in all nodes.""" last_name += node.text(0) if node.childCount(): last_name += '->' code = int(node.text(2)) doc = self.data[code] pattern = re.compile(text, flags) for m in pattern.finditer(doc): add_find_result(code, last_name, *m.span()) for i in range(node.childCount()): find_in_nodes(node.child(i), last_name) find_in_nodes(root) @pyqtSlot( QListWidgetItem, QListWidgetItem, name='on_find_list_currentItemChanged') def __find_results(self, *_: QListWidgetItem): """TODO: Switch to target node."""
def __init__(self): super(MainWindowBase, self).__init__() self.setupUi(self) # Start new window @Slot() def new_main_window(): XStream.back() run = self.__class__() run.show() self.action_New_Window.triggered.connect(new_main_window) # Settings self.settings = QSettings("Kmol", "Kmol Editor") # Text editor self.text_editor = TextEditor(self) self.h2_splitter.addWidget(self.text_editor) self.html_previewer = QWebEngineView() self.html_previewer.setContextMenuPolicy(Qt.NoContextMenu) self.html_previewer.setContent(b"", "text/plain") self.h2_splitter.addWidget(self.html_previewer) self.text_editor.word_changed.connect(self.reload_html_viewer) self.text_editor.word_changed.connect(self.set_not_saved_title) self.edge_line_option.toggled.connect(self.text_editor.setEdgeMode) self.trailing_blanks_option.toggled.connect( self.text_editor.set_remove_trailing_blanks) # Highlighters self.highlighter_option.addItems(sorted(QSCI_HIGHLIGHTERS)) self.highlighter_option.setCurrentText("Markdown") self.highlighter_option.currentTextChanged.connect( self.text_editor.set_highlighter) self.highlighter_option.currentTextChanged.connect( self.reload_html_viewer) # Tree widget context menu self.tree_widget.customContextMenuRequested.connect( self.tree_context_menu) self.pop_menu_tree = QMenu(self) self.pop_menu_tree.setSeparatorsCollapsible(True) self.pop_menu_tree.addAction(self.action_new_project) self.pop_menu_tree.addAction(self.action_open) self.tree_add = QAction("&Add Node", self) self.tree_add.triggered.connect(self.add_node) self.tree_add.setShortcutContext(Qt.WindowShortcut) self.pop_menu_tree.addAction(self.tree_add) self.pop_menu_tree.addSeparator() self.tree_path = QAction("Set Path", self) self.tree_path.triggered.connect(self.set_path) self.pop_menu_tree.addAction(self.tree_path) self.tree_refresh = QAction("&Refresh from Path", self) self.tree_refresh.triggered.connect(self.refresh_proj) self.pop_menu_tree.addAction(self.tree_refresh) self.tree_openurl = QAction("&Open from Path", self) self.tree_openurl.triggered.connect(self.open_path) self.pop_menu_tree.addAction(self.tree_openurl) self.action_save.triggered.connect(self.save_proj) self.pop_menu_tree.addAction(self.action_save) self.tree_copy = QAction("Co&py", self) self.tree_copy.triggered.connect(self.copy_node) self.pop_menu_tree.addAction(self.tree_copy) self.tree_clone = QAction("C&lone", self) self.tree_clone.triggered.connect(self.clone_node) self.pop_menu_tree.addAction(self.tree_clone) self.tree_copy_tree = QAction("Recursive Copy", self) self.tree_copy_tree.triggered.connect(self.copy_node_recursive) self.pop_menu_tree.addAction(self.tree_copy_tree) self.tree_clone_tree = QAction("Recursive Clone", self) self.tree_clone_tree.triggered.connect(self.clone_node_recursive) self.pop_menu_tree.addAction(self.tree_clone_tree) self.pop_menu_tree.addSeparator() self.tree_delete = QAction("&Delete", self) self.tree_delete.triggered.connect(self.delete_node) self.pop_menu_tree.addAction(self.tree_delete) self.tree_close = QAction("&Close", self) self.tree_close.triggered.connect(self.close_proj) self.pop_menu_tree.addAction(self.tree_close) self.tree_main.header().setSectionResizeMode( QHeaderView.ResizeToContents) # Console self.console.setFont(self.text_editor.font) if not ARGUMENTS.debug_mode: XStream.stdout().message_written.connect(self.append_to_console) XStream.stderr().message_written.connect(self.append_to_console) for info in INFO: print(info) print('-' * 7) # Searching function find_next = QShortcut(QKeySequence("F3"), self) find_next.activated.connect(self.find_next_button.click) find_previous = QShortcut(QKeySequence("F4"), self) find_previous.activated.connect(self.find_previous_button.click) find_tab = QShortcut(QKeySequence("Ctrl+F"), self) find_tab.activated.connect(self.start_finder) find_project = QShortcut(QKeySequence("Ctrl+Shift+F"), self) find_project.activated.connect(self.find_project_button.click) self.find_list_node: Dict[int, QTreeWidgetItem] = {} # Replacing function replace = QShortcut(QKeySequence("Ctrl+R"), self) replace.activated.connect(self.replace_node_button.click) replace_project = QShortcut(QKeySequence("Ctrl+Shift+R"), self) replace_project.activated.connect(self.replace_project_button.click) # Node edit function (Ctrl + ArrowKey) new_node = QShortcut(QKeySequence("Ctrl+Ins"), self) new_node.activated.connect(self.add_node) del_node = QShortcut(QKeySequence("Ctrl+Del"), self) del_node.activated.connect(self.delete_node) move_up_node = QShortcut(QKeySequence("Ctrl+Up"), self) move_up_node.activated.connect(self.move_up_node) move_down_node = QShortcut(QKeySequence("Ctrl+Down"), self) move_down_node.activated.connect(self.move_down_node) move_right_node = QShortcut(QKeySequence("Ctrl+Right"), self) move_right_node.activated.connect(self.move_right_node) move_left_node = QShortcut(QKeySequence("Ctrl+Left"), self) move_left_node.activated.connect(self.move_left_node) # Run script button run_sript = QShortcut(QKeySequence("F5"), self) run_sript.activated.connect(self.exec_button.click) self.macros_toolbar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) # File keeper self.keeper = None # Data self.data = DataDict() self.data.not_saved.connect(self.set_not_saved_title) self.data.all_saved.connect(self.set_saved_title) self.env = QStandardPaths.writableLocation( QStandardPaths.DesktopLocation)
class MainWindowBase(QMainWindow, Ui_MainWindow, metaclass=QABCMeta): """External UI settings.""" @abstractmethod def __init__(self): super(MainWindowBase, self).__init__() self.setupUi(self) self.setAttribute(Qt.WA_DeleteOnClose) # Initialize custom UI self.__undo_redo() self.__appearance() self.__free_move() self.__options() self.__zoom() self.__point_context_menu() self.__link_context_menu() self.__canvas_context_menu() # Environment path self.env = "" if ARGUMENTS.c: self.set_locate(QFileInfo(ARGUMENTS.c).canonicalFilePath()) else: desktop = QStandardPaths.writableLocation( QStandardPaths.DesktopLocation) self.set_locate(self.settings.value("ENV", desktop, type=str)) def show(self): """Overridden function to zoom the canvas's size after startup.""" super(MainWindowBase, self).show() self.main_canvas.zoom_to_fit() def set_locate(self, locate: str): """Set environment variables.""" if locate == self.env: # If no changed. return self.env = locate logger.debug(f"~Set workplace to: [\"{self.env}\"]") def __undo_redo(self): """Undo list settings. + Undo stack. + Undo view widget. + Hot keys. """ self.command_stack = QUndoStack(self) self.command_stack.setUndoLimit(self.undo_limit_option.value()) self.undo_limit_option.valueChanged.connect( self.command_stack.setUndoLimit) self.command_stack.indexChanged.connect(self.command_reload) self.undo_view = QUndoView(self.command_stack) self.undo_view.setEmptyLabel("~ Start Pyslvs") self.undo_redo_layout.addWidget(self.undo_view) self.action_redo = self.command_stack.createRedoAction(self, "Redo") self.action_undo = self.command_stack.createUndoAction(self, "Undo") self.action_redo.setShortcuts([ QKeySequence("Ctrl+Shift+Z"), QKeySequence("Ctrl+Y"), ]) self.action_redo.setStatusTip("Backtracking undo action.") self.action_redo.setIcon(QIcon(QPixmap(":/icons/redo.png"))) self.action_undo.setShortcut("Ctrl+Z") self.action_undo.setStatusTip("Recover last action.") self.action_undo.setIcon(QIcon(QPixmap(":/icons/undo.png"))) self.menu_edit.addAction(self.action_undo) self.menu_edit.addAction(self.action_redo) def __appearance(self): """Start up and initialize custom widgets.""" # Version label self.version_label.setText(__version__) # Entities tables self.entities_tab.tabBar().setStatusTip( "Switch the tabs to change to another view mode.") self.entities_point = PointTableWidget(self.entities_point_widget) self.entities_point.cellDoubleClicked.connect(self.edit_point) self.entities_point.delete_request.connect(self.delete_selected_points) self.entities_point_layout.addWidget(self.entities_point) self.entities_link = LinkTableWidget(self.entities_link_widget) self.entities_link.cellDoubleClicked.connect(self.edit_link) self.entities_link.delete_request.connect(self.delete_selected_links) self.entities_link_layout.addWidget(self.entities_link) self.entities_expr = ExprTableWidget(self.EntitiesExpr_widget) self.entities_expr_layout.insertWidget(0, self.entities_expr) # Select all button on the Point and Link tab as corner widget. select_all_button = QPushButton() select_all_button.setIcon(QIcon(QPixmap(":/icons/select_all.png"))) select_all_button.setToolTip("Select all") select_all_button.setStatusTip("Select all item of point table.") @Slot() def table_select_all(): """Distinguish table by tab index.""" tables: List[BaseTableWidget] = [ self.entities_point, self.entities_link, self.entities_expr, ] tables[self.entities_tab.currentIndex()].selectAll() select_all_button.clicked.connect(table_select_all) self.entities_tab.setCornerWidget(select_all_button) select_all_action = QAction("Select all point", self) select_all_action.triggered.connect(table_select_all) select_all_action.setShortcut("Ctrl+A") select_all_action.setShortcutContext(Qt.WindowShortcut) self.addAction(select_all_action) # QPainter canvas window self.main_canvas = DynamicCanvas(self) select_tips = QLabel(self, Qt.ToolTip) self.entities_tab.currentChanged.connect( self.main_canvas.set_selection_mode) @Slot(QPoint, str) def show_select_tips(pos: QPoint, text: str): select_tips.setText(text) select_tips.move(pos - QPoint(0, select_tips.height())) select_tips.show() self.main_canvas.selected_tips.connect(show_select_tips) self.main_canvas.selected_tips_hide.connect(select_tips.hide) @Slot(tuple, bool) def table_set_selection(selections: Sequence[int], key_detect: bool): """Distinguish table by tab index.""" tables: List[BaseTableWidget] = [ self.entities_point, self.entities_link, self.entities_expr, ] tables[self.entities_tab.currentIndex()].set_selections( selections, key_detect) self.main_canvas.selected.connect(table_set_selection) self.entities_point.row_selection_changed.connect( self.main_canvas.set_selection) @Slot() def table_clear_selection(): """Distinguish table by tab index.""" tables: List[BaseTableWidget] = [ self.entities_point, self.entities_link, self.entities_expr, ] tables[self.entities_tab.currentIndex()].clearSelection() self.main_canvas.noselected.connect(table_clear_selection) clean_selection_action = QAction("Clean selection", self) clean_selection_action.triggered.connect(table_clear_selection) clean_selection_action.setShortcut("Esc") clean_selection_action.setShortcutContext(Qt.WindowShortcut) self.addAction(clean_selection_action) self.main_canvas.free_moved.connect(self.set_free_move) self.main_canvas.alt_add.connect(self.q_add_normal_point) self.main_canvas.doubleclick_edit.connect(self.edit_point) self.main_canvas.zoom_changed.connect(self.zoom_bar.setValue) self.main_canvas.tracking.connect(self.set_mouse_pos) self.canvas_splitter.insertWidget(0, self.main_canvas) self.canvas_splitter.setSizes([600, 10, 30]) # Selection label on status bar right side selection_label = SelectionLabel(self) self.entities_point.selectionLabelUpdate.connect( selection_label.update_select_point) self.main_canvas.browse_tracking.connect( selection_label.update_mouse_position) self.status_bar.addPermanentWidget(selection_label) # FPS label on status bar right side fps_label = FPSLabel(self) self.main_canvas.fps_updated.connect(fps_label.update_text) self.status_bar.addPermanentWidget(fps_label) # Inputs widget self.inputs_widget = InputsWidget(self) self.inputs_tab_layout.addWidget(self.inputs_widget) self.free_move_button.toggled.connect( self.inputs_widget.variable_value_reset) self.inputs_widget.about_to_resolve.connect(self.resolve) @Slot(tuple, bool) def inputs_set_selection(selections: Sequence[int], _: bool): """Distinguish table by tab index.""" self.inputs_widget.clear_selection() if self.entities_tab.currentIndex() == 0: self.inputs_widget.set_selection(selections) self.main_canvas.selected.connect(inputs_set_selection) self.main_canvas.noselected.connect(self.inputs_widget.clear_selection) self.inputs_widget.update_preview_button.clicked.connect( self.main_canvas.update_preview_path) # Number and type synthesis self.structure_synthesis = StructureSynthesis(self) self.synthesis_tab_widget.addTab(self.structure_synthesis, self.structure_synthesis.windowIcon(), "Structural") # Synthesis collections self.collection_tab_page = Collections(self) self.synthesis_tab_widget.addTab(self.collection_tab_page, self.collection_tab_page.windowIcon(), "Collections") self.structure_synthesis.addCollection = ( self.collection_tab_page.structure_widget.add_collection) # Dimensional synthesis self.dimensional_synthesis = DimensionalSynthesis(self) self.main_canvas.set_target_point.connect( self.dimensional_synthesis.set_point) self.synthesis_tab_widget.addTab( self.dimensional_synthesis, self.dimensional_synthesis.windowIcon(), "Dimensional") @Slot() def set_design_progress(): """Synthesis progress bar.""" pos = self.synthesis_tab_widget.currentIndex() if pos == 1: pos += self.collection_tab_page.tab_widget.currentIndex() elif pos == 2: pos += 1 self.synthesis_progress.setValue(pos) self.synthesis_tab_widget.currentChanged.connect(set_design_progress) self.collection_tab_page.tab_widget.currentChanged.connect( set_design_progress) # File widget settings self.database_widget = DatabaseWidget(self) self.vc_layout.addWidget(self.database_widget) self.database_widget.commit_add.clicked.connect(self.commit) self.database_widget.branch_add.clicked.connect(self.commit_branch) self.action_stash.triggered.connect(self.database_widget.stash) # YAML editor self.yaml_editor = YamlEditor(self) # Console dock will hide when startup self.console_widget.hide() # Connect to GUI button self.console_disconnect_button.setEnabled(not ARGUMENTS.debug_mode) self.console_connect_button.setEnabled(ARGUMENTS.debug_mode) # Splitter stretch factor self.main_splitter.setStretchFactor(0, 4) self.main_splitter.setStretchFactor(1, 15) self.mechanism_panel_splitter.setSizes([500, 200]) # Enable mechanism menu actions when shows. self.menu_mechanism.aboutToShow.connect(self.enable_mechanism_actions) @Slot() def new_main_window(): """Start a new window.""" run = self.__class__() run.show() self.action_new_window.triggered.connect(new_main_window) def __free_move(self): """Menu of free move mode.""" free_move_mode_menu = QMenu(self) def free_move_mode_func(j: int, icon_qt: QIcon): @Slot() def func() -> None: self.free_move_button.setIcon(icon_qt) self.main_canvas.set_free_move(j) self.entities_tab.setCurrentIndex(0) self.inputs_widget.variable_stop.click() return func for i, (text, icon, tip) in enumerate([ ("View mode", "free_move_off", "Disable free move mode."), ("Translate mode", "translate", "Edit by 2 DOF moving."), ("Rotate mode", "rotate", "Edit by 1 DOF moving."), ("Reflect mode", "reflect", "Edit by flip axis."), ]): action = QAction(QIcon(QPixmap(f":/icons/{icon}.png")), text, self) action.triggered.connect(free_move_mode_func(i, action.icon())) action.setShortcut(QKeySequence(f"Ctrl+{i + 1}")) action.setShortcutContext(Qt.WindowShortcut) action.setStatusTip(tip) free_move_mode_menu.addAction(action) if i == 0: self.free_move_disable = action self.free_move_button.setMenu(free_move_mode_menu) # "Link adjust" function self.link_free_move_confirm.clicked.connect( self.main_canvas.emit_free_move_all) def __options(self): """Signal connection for option widgets. + Spin boxes + Combo boxes + Check boxes """ # While value change, update the canvas widget. self.settings = QSettings( QStandardPaths.writableLocation(QStandardPaths.HomeLocation) + '/.pyslvs.ini', QSettings.IniFormat, self) self.zoom_bar.valueChanged.connect(self.main_canvas.set_zoom) self.line_width_option.valueChanged.connect( self.main_canvas.set_link_width) self.path_width_option.valueChanged.connect( self.main_canvas.set_path_width) self.font_size_option.valueChanged.connect( self.main_canvas.set_font_size) self.action_show_point_mark.toggled.connect( self.main_canvas.set_point_mark) self.action_show_dimensions.toggled.connect( self.main_canvas.set_show_dimension) self.selection_radius_option.valueChanged.connect( self.main_canvas.set_selection_radius) self.link_trans_option.valueChanged.connect( self.main_canvas.set_transparency) self.margin_factor_option.valueChanged.connect( self.main_canvas.set_margin_factor) self.joint_size_option.valueChanged.connect( self.main_canvas.set_joint_size) self.zoom_by_option.currentIndexChanged.connect( self.main_canvas.set_zoom_by) self.snap_option.valueChanged.connect(self.main_canvas.set_snap) self.background_option.textChanged.connect( self.main_canvas.set_background) self.background_opacity_option.valueChanged.connect( self.main_canvas.set_background_opacity) self.background_scale_option.valueChanged.connect( self.main_canvas.set_background_scale) self.background_offset_x_option.valueChanged.connect( self.main_canvas.set_background_offset_x) self.background_offset_y_option.valueChanged.connect( self.main_canvas.set_background_offset_y) self.monochrome_option.toggled.connect( self.main_canvas.set_monochrome_mode) self.monochrome_option.toggled.connect( self.collection_tab_page.configure_widget.configure_canvas. set_monochrome_mode) self.monochrome_option.toggled.connect( self.dimensional_synthesis.preview_canvas.set_monochrome_mode) # Resolve after change current kernel. self.planar_solver_option.addItems(kernel_list) self.path_preview_option.addItems(kernel_list + ("Same as solver kernel", )) self.planar_solver_option.currentIndexChanged.connect(self.solve) self.path_preview_option.currentIndexChanged.connect(self.solve) self.settings_reset.clicked.connect(self.reset_options) def __zoom(self): """Zoom functions. + 'zoom to fit' function connections. + Zoom text buttons """ self.action_zoom_to_fit.triggered.connect(self.main_canvas.zoom_to_fit) self.ResetCanvas.clicked.connect(self.main_canvas.zoom_to_fit) zoom_menu = QMenu(self) def zoom_level(value: int): """Return a function that set the specified zoom value.""" @Slot() def func(): self.zoom_bar.setValue(value) return func for level in range( self.zoom_bar.minimum() - self.zoom_bar.minimum() % 100 + 100, 500 + 1, 100): action = QAction(f'{level}%', self) action.triggered.connect(zoom_level(level)) zoom_menu.addAction(action) action = QAction("customize", self) action.triggered.connect(self.customize_zoom) zoom_menu.addAction(action) self.zoom_button.setMenu(zoom_menu) def __point_context_menu(self): """EntitiesPoint context menu + Add /////// + New Link + Edit + Grounded + Multiple joint - Point0 - Point1 - ... + Copy table data + Copy coordinate + Clone ------- + Delete """ self.entities_point_widget.customContextMenuRequested.connect( self.point_context_menu) self.pop_menu_point = QMenu(self) self.pop_menu_point.setSeparatorsCollapsible(True) self.action_point_context_add = QAction("&Add", self) self.action_point_context_add.triggered.connect(self.new_point) self.pop_menu_point.addAction(self.action_point_context_add) # New Link self.pop_menu_point.addAction(self.action_new_link) self.action_point_context_edit = QAction("&Edit", self) self.action_point_context_edit.triggered.connect(self.edit_point) self.pop_menu_point.addAction(self.action_point_context_edit) self.action_point_context_lock = QAction("&Grounded", self) self.action_point_context_lock.setCheckable(True) self.action_point_context_lock.triggered.connect(self.lock_points) self.pop_menu_point.addAction(self.action_point_context_lock) self.pop_menu_point_merge = QMenu(self) self.pop_menu_point_merge.setTitle("Multiple joint") self.pop_menu_point.addMenu(self.pop_menu_point_merge) self.action_point_context_copydata = QAction("&Copy table data", self) self.action_point_context_copydata.triggered.connect( self.copy_points_table) self.pop_menu_point.addAction(self.action_point_context_copydata) self.action_point_context_copy_coord = QAction("&Copy coordinate", self) self.action_point_context_copy_coord.triggered.connect(self.copy_coord) self.pop_menu_point.addAction(self.action_point_context_copy_coord) self.action_point_context_clone = QAction("C&lone", self) self.action_point_context_clone.triggered.connect(self.clone_point) self.pop_menu_point.addAction(self.action_point_context_clone) self.pop_menu_point.addSeparator() self.action_point_context_delete = QAction("&Delete", self) self.action_point_context_delete.triggered.connect( self.delete_selected_points) self.pop_menu_point.addAction(self.action_point_context_delete) def __link_context_menu(self): """EntitiesLink context menu + Add + Edit + Merge links - Link0 - Link1 - ... + Copy table data + Release + Constrain ------- + Delete """ self.entities_link_widget.customContextMenuRequested.connect( self.link_context_menu) self.pop_menu_link = QMenu(self) self.pop_menu_link.setSeparatorsCollapsible(True) self.action_link_context_add = QAction("&Add", self) self.action_link_context_add.triggered.connect(self.new_link) self.pop_menu_link.addAction(self.action_link_context_add) self.action_link_context_edit = QAction("&Edit", self) self.action_link_context_edit.triggered.connect(self.edit_link) self.pop_menu_link.addAction(self.action_link_context_edit) self.pop_menu_link_merge = QMenu(self) self.pop_menu_link_merge.setTitle("Merge links") self.pop_menu_link.addMenu(self.pop_menu_link_merge) self.action_link_context_copydata = QAction("&Copy table data", self) self.action_link_context_copydata.triggered.connect( self.copy_links_table) self.pop_menu_link.addAction(self.action_link_context_copydata) self.action_link_context_release = QAction("&Release", self) self.action_link_context_release.triggered.connect(self.release_ground) self.pop_menu_link.addAction(self.action_link_context_release) self.action_link_context_constrain = QAction("C&onstrain", self) self.action_link_context_constrain.triggered.connect( self.constrain_link) self.pop_menu_link.addAction(self.action_link_context_constrain) self.pop_menu_link.addSeparator() self.action_link_context_delete = QAction("&Delete", self) self.action_link_context_delete.triggered.connect( self.delete_selected_links) self.pop_menu_link.addAction(self.action_link_context_delete) def __canvas_context_menu(self): """MainCanvas context menus, switch the actions when selection mode changed. + Actions set of points. + Actions set of links. """ self.main_canvas.setContextMenuPolicy(Qt.CustomContextMenu) self.main_canvas.customContextMenuRequested.connect( self.canvas_context_menu) """ Actions set of points: + Add /////// + New Link + Add [fixed] + Add [target path] /////// + Edit + Grounded + Multiple joint - Point0 - Point1 - ... + Clone + Copy coordinate ------- + Delete """ self.pop_menu_canvas_p = QMenu(self) self.pop_menu_canvas_p.setSeparatorsCollapsible(True) self.action_canvas_context_add = QAction("&Add", self) self.action_canvas_context_add.triggered.connect(self.add_normal_point) self.pop_menu_canvas_p.addAction(self.action_canvas_context_add) # New Link self.pop_menu_canvas_p.addAction(self.action_new_link) self.action_canvas_context_grounded_add = QAction( "Add [grounded]", self) self.action_canvas_context_grounded_add.triggered.connect( self.add_fixed_point) self.pop_menu_canvas_p.addAction( self.action_canvas_context_grounded_add) self.action_canvas_context_path = QAction("Add [target path]", self) self.action_canvas_context_path.triggered.connect( self.add_target_point) self.pop_menu_canvas_p.addAction(self.action_canvas_context_path) # The following actions will be shown when points selected. self.pop_menu_canvas_p.addAction(self.action_point_context_edit) self.pop_menu_canvas_p.addAction(self.action_point_context_lock) self.pop_menu_canvas_p.addMenu(self.pop_menu_point_merge) self.pop_menu_canvas_p.addAction(self.action_point_context_copy_coord) self.pop_menu_canvas_p.addAction(self.action_point_context_clone) self.pop_menu_canvas_p.addSeparator() self.pop_menu_canvas_p.addAction(self.action_point_context_delete) """ Actions set of links: + Add /////// + Add [target path] /////// + Edit + Merge links - Link0 - Link1 - ... + Release / Constrain ------- + Delete """ self.pop_menu_canvas_l = QMenu(self) self.pop_menu_canvas_l.setSeparatorsCollapsible(True) self.pop_menu_canvas_l.addAction(self.action_link_context_add) self.pop_menu_canvas_l.addAction(self.action_link_context_edit) self.pop_menu_canvas_l.addMenu(self.pop_menu_link_merge) self.pop_menu_canvas_l.addAction(self.action_link_context_constrain) self.pop_menu_canvas_l.addSeparator() self.pop_menu_canvas_l.addAction(self.action_link_context_delete) @Slot(int, name='on_entities_tab_currentChanged') def __set_selection_mode(self, index: int): """Connect selection signal for main canvas.""" # Set selection from click table items. tables: List[BaseTableWidget] = [ self.entities_point, self.entities_link, self.entities_expr, ] try: for table in tables: table.row_selection_changed.disconnect() except TypeError: pass tables[index].row_selection_changed.connect( self.main_canvas.set_selection) # Double click signal. try: self.main_canvas.doubleclick_edit.disconnect() except TypeError: pass if index == 0: self.main_canvas.doubleclick_edit.connect(self.edit_point) elif index == 1: self.main_canvas.doubleclick_edit.connect(self.edit_link) # Clear all selections. for table in tables: table.clearSelection() self.inputs_widget.clear_selection() @abstractmethod def command_reload(self, index: int) -> None: ... @abstractmethod def new_point(self) -> None: ... @abstractmethod def add_normal_point(self) -> None: ... @abstractmethod def add_fixed_point(self) -> None: ... @abstractmethod def edit_point(self) -> None: ... @abstractmethod def delete_selected_points(self) -> None: ... @abstractmethod def lock_points(self) -> None: ... @abstractmethod def new_link(self) -> None: ... @abstractmethod def edit_link(self) -> None: ... @abstractmethod def delete_selected_links(self) -> None: ... @abstractmethod def constrain_link(self) -> None: ... @abstractmethod def release_ground(self) -> None: ... @abstractmethod def add_target_point(self) -> None: ... @abstractmethod def set_free_move( self, args: Sequence[Tuple[int, Tuple[float, float, float]]]) -> None: ... @abstractmethod def q_add_normal_point(self, x: float, y: float) -> None: ... @abstractmethod def set_mouse_pos(self, x: float, y: float) -> None: ... @abstractmethod def solve(self) -> None: ... @abstractmethod def resolve(self) -> None: ... @abstractmethod def commit(self, is_branch: bool = False) -> None: ... @abstractmethod def commit_branch(self) -> None: ... @abstractmethod def enable_mechanism_actions(self) -> None: ... @abstractmethod def clone_point(self) -> None: ... @abstractmethod def copy_coord(self) -> None: ... @abstractmethod def copy_points_table(self) -> None: ... @abstractmethod def copy_links_table(self) -> None: ... @abstractmethod def canvas_context_menu(self, point: QPoint) -> None: ... @abstractmethod def link_context_menu(self, point: QPoint) -> None: ... @abstractmethod def customize_zoom(self) -> None: ... @abstractmethod def reset_options(self) -> None: ... @abstractmethod def preview_path(self, auto_preview: List[List[_Coord]], slider_auto_preview: Dict[int, List[_Coord]], vpoints: Sequence[VPoint]) -> None: ... @abstractmethod def reload_canvas(self) -> None: ... @abstractmethod def output_to(self, format_name: str, format_choose: Sequence[str]) -> str: ... @abstractmethod def right_input(self) -> bool: ... @abstractmethod def set_coords_as_current(self) -> None: ... @abstractmethod def dof(self) -> int: ... @abstractmethod def save_reply_box(self, title: str, file_name: str) -> None: ... @abstractmethod def input_from(self, format_name: str, format_choose: Sequence[str], multiple: bool = False) -> str: ... @abstractmethod def get_graph( self ) -> Tuple[Graph, List[int], List[Tuple[int, int]], Dict[int, _Coord], Dict[int, int], Dict[int, int]]: ... @abstractmethod def get_configure(self) -> Dict[str, Any]: ... @abstractmethod def workbook_no_save(self) -> None: ... @abstractmethod def workbook_saved(self) -> bool: ... @abstractmethod def merge_result(self, expr: str, path: Sequence[Sequence[_Coord]]) -> None: ... @abstractmethod def check_file_changed(self) -> bool: ... @abstractmethod def get_storage(self) -> Dict[str, str]: ... @abstractmethod def add_empty_links(self, link_color: Dict[str, str]) -> None: ... @abstractmethod def parse_expression(self, expr: str) -> None: ... @abstractmethod def add_multiple_storage(self, exprs: Sequence[Tuple[str, str]]) -> None: ... @abstractmethod def clear(self) -> None: ... @abstractmethod def add_points( self, p_attr: Sequence[Tuple[float, float, str, str, int, float]]) -> None: ...
def __canvas_context_menu(self): """MainCanvas context menus, switch the actions when selection mode changed. + Actions set of points. + Actions set of links. """ self.main_canvas.setContextMenuPolicy(Qt.CustomContextMenu) self.main_canvas.customContextMenuRequested.connect( self.canvas_context_menu) """ Actions set of points: + Add /////// + New Link + Add [fixed] + Add [target path] /////// + Edit + Grounded + Multiple joint - Point0 - Point1 - ... + Clone + Copy coordinate ------- + Delete """ self.pop_menu_canvas_p = QMenu(self) self.pop_menu_canvas_p.setSeparatorsCollapsible(True) self.action_canvas_context_add = QAction("&Add", self) self.action_canvas_context_add.triggered.connect(self.add_normal_point) self.pop_menu_canvas_p.addAction(self.action_canvas_context_add) # New Link self.pop_menu_canvas_p.addAction(self.action_new_link) self.action_canvas_context_grounded_add = QAction( "Add [grounded]", self) self.action_canvas_context_grounded_add.triggered.connect( self.add_fixed_point) self.pop_menu_canvas_p.addAction( self.action_canvas_context_grounded_add) self.action_canvas_context_path = QAction("Add [target path]", self) self.action_canvas_context_path.triggered.connect( self.add_target_point) self.pop_menu_canvas_p.addAction(self.action_canvas_context_path) # The following actions will be shown when points selected. self.pop_menu_canvas_p.addAction(self.action_point_context_edit) self.pop_menu_canvas_p.addAction(self.action_point_context_lock) self.pop_menu_canvas_p.addMenu(self.pop_menu_point_merge) self.pop_menu_canvas_p.addAction(self.action_point_context_copy_coord) self.pop_menu_canvas_p.addAction(self.action_point_context_clone) self.pop_menu_canvas_p.addSeparator() self.pop_menu_canvas_p.addAction(self.action_point_context_delete) """ Actions set of links: + Add /////// + Add [target path] /////// + Edit + Merge links - Link0 - Link1 - ... + Release / Constrain ------- + Delete """ self.pop_menu_canvas_l = QMenu(self) self.pop_menu_canvas_l.setSeparatorsCollapsible(True) self.pop_menu_canvas_l.addAction(self.action_link_context_add) self.pop_menu_canvas_l.addAction(self.action_link_context_edit) self.pop_menu_canvas_l.addMenu(self.pop_menu_link_merge) self.pop_menu_canvas_l.addAction(self.action_link_context_constrain) self.pop_menu_canvas_l.addSeparator() self.pop_menu_canvas_l.addAction(self.action_link_context_delete)
class MainWindowUiInterface(QMainWindow, Ui_MainWindow, metaclass=QAbcMeta): """External UI settings.""" def __init__(self): super(MainWindowUiInterface, self).__init__() self.setupUi(self) self.setAttribute(Qt.WA_DeleteOnClose) self.env = "" self.setLocate( QFileInfo(ARGUMENTS.c).canonicalFilePath() if ARGUMENTS.c else QStandardPaths.writableLocation(QStandardPaths.DesktopLocation)) # Undo stack stream. self.CommandStack = QUndoStack(self) # Initialize custom UI. self.__undo_redo() self.__appearance() self.__free_move() self.__options() self.__zoom() self.__point_context_menu() self.__link_context_menu() self.__canvas_context_menu() def show(self): """Overridden function to zoom the canvas's size after startup.""" super(MainWindowUiInterface, self).show() self.MainCanvas.zoomToFit() def setLocate(self, locate: str): """Set environment variables.""" if locate == self.env: # If no changed. return self.env = locate print(f"~Set workplace to: [\"{self.env}\"]") def __undo_redo(self): """Undo list settings. + Undo stack. + Undo view widget. + Hot keys. """ self.CommandStack.setUndoLimit(self.undolimit_option.value()) self.undolimit_option.valueChanged.connect( self.CommandStack.setUndoLimit) self.CommandStack.indexChanged.connect(self.commandReload) self.undoView = QUndoView(self.CommandStack) self.undoView.setEmptyLabel("~ Start Pyslvs") self.UndoRedoLayout.addWidget(self.undoView) self.action_Redo = self.CommandStack.createRedoAction(self, "Redo") self.action_Undo = self.CommandStack.createUndoAction(self, "Undo") self.action_Redo.setShortcuts([ QKeySequence("Ctrl+Shift+Z"), QKeySequence("Ctrl+Y"), ]) self.action_Redo.setStatusTip("Backtracking undo action.") self.action_Redo.setIcon(QIcon(QPixmap(":/icons/redo.png"))) self.action_Undo.setShortcut("Ctrl+Z") self.action_Undo.setStatusTip("Recover last action.") self.action_Undo.setIcon(QIcon(QPixmap(":/icons/undo.png"))) self.menu_Edit.addAction(self.action_Undo) self.menu_Edit.addAction(self.action_Redo) def __appearance(self): """Start up and initialize custom widgets.""" # Version label self.version_label.setText(f"v{_major}.{_minor}.{_build} ({_label})") # Entities tables. self.EntitiesTab.tabBar().setStatusTip( "Switch the tabs to change to another view mode.") self.EntitiesPoint = PointTableWidget(self.EntitiesPoint_widget) self.EntitiesPoint.cellDoubleClicked.connect(self.editPoint) self.EntitiesPoint.deleteRequest.connect(self.deletePoints) self.EntitiesPoint_layout.addWidget(self.EntitiesPoint) self.EntitiesLink = LinkTableWidget(self.EntitiesLink_widget) self.EntitiesLink.cellDoubleClicked.connect(self.editLink) self.EntitiesLink.deleteRequest.connect(self.deleteLinks) self.EntitiesLink_layout.addWidget(self.EntitiesLink) self.EntitiesExpr = ExprTableWidget(self.EntitiesExpr_widget) self.EntitiesExpr.reset.connect(self.link_free_move_widget.setEnabled) self.EntitiesExpr.free_move_request.connect(self.setLinkFreeMove) self.EntitiesExpr_layout.insertWidget(0, self.EntitiesExpr) # Link free mode slide bar. self.link_free_move_slider.valueChanged.connect( self.link_free_move_spinbox.setValue) self.link_free_move_spinbox.valueChanged.connect( self.link_free_move_slider.setValue) self.link_free_move_slider.rangeChanged.connect( self.link_free_move_spinbox.setRange) # Select all button on the Point and Link tab as corner widget. select_all_button = QPushButton() select_all_button.setIcon(QIcon(QPixmap(":/icons/select_all.png"))) select_all_button.setToolTip("Select all") select_all_button.setStatusTip("Select all item of point table.") @pyqtSlot() def table_select_all(): """Distinguish table by tab index.""" tables = (self.EntitiesPoint, self.EntitiesLink, self.EntitiesExpr) tables[self.EntitiesTab.currentIndex()].selectAll() select_all_button.clicked.connect(table_select_all) self.EntitiesTab.setCornerWidget(select_all_button) select_all_action = QAction("Select all point", self) select_all_action.triggered.connect(table_select_all) select_all_action.setShortcut("Ctrl+A") select_all_action.setShortcutContext(Qt.WindowShortcut) self.addAction(select_all_action) # QPainter canvas window self.MainCanvas = DynamicCanvas(self) self.EntitiesTab.currentChanged.connect( self.MainCanvas.setSelectionMode) @pyqtSlot(tuple, bool) def table_set_selection(selections: Tuple[int], key_detect: bool): """Distinguish table by tab index.""" tables = (self.EntitiesPoint, self.EntitiesLink, self.EntitiesExpr) tables[self.EntitiesTab.currentIndex()].setSelections( selections, key_detect) self.MainCanvas.selected.connect(table_set_selection) self.EntitiesPoint.rowSelectionChanged.connect( self.MainCanvas.setSelection) @pyqtSlot() def table_clear_selection(): """Distinguish table by tab index.""" tables = (self.EntitiesPoint, self.EntitiesLink, self.EntitiesExpr) tables[self.EntitiesTab.currentIndex()].clearSelection() self.MainCanvas.noselected.connect(table_clear_selection) clean_selection_action = QAction("Clean selection", self) clean_selection_action.triggered.connect(table_clear_selection) clean_selection_action.setShortcut("Esc") clean_selection_action.setShortcutContext(Qt.WindowShortcut) self.addAction(clean_selection_action) self.MainCanvas.free_moved.connect(self.setFreeMove) self.MainCanvas.alt_add.connect(self.qAddNormalPoint) self.MainCanvas.doubleclick_edit.connect(self.editPoint) self.MainCanvas.zoom_changed.connect(self.ZoomBar.setValue) self.MainCanvas.tracking.connect(self.setMousePos) self.canvasSplitter.insertWidget(0, self.MainCanvas) self.canvasSplitter.setSizes([600, 10, 30]) # Selection label on status bar right side. selection_label = SelectionLabel(self) self.EntitiesPoint.selectionLabelUpdate.connect( selection_label.updateSelectPoint) self.MainCanvas.browse_tracking.connect( selection_label.updateMousePosition) self.status_bar.addPermanentWidget(selection_label) # FPS label on status bar right side. fps_label = FPSLabel(self) self.MainCanvas.fps_updated.connect(fps_label.updateText) self.status_bar.addPermanentWidget(fps_label) # Inputs widget. self.InputsWidget = InputsWidget(self) self.inputs_tab_layout.addWidget(self.InputsWidget) self.free_move_button.toggled.connect( self.InputsWidget.variableValueReset) self.InputsWidget.aboutToResolve.connect(self.resolve) @pyqtSlot(tuple, bool) def inputs_set_selection(selections: Tuple[int], _: bool): """Distinguish table by tab index.""" self.InputsWidget.clearSelection() if self.EntitiesTab.currentIndex() == 0: self.InputsWidget.setSelection(selections) self.MainCanvas.selected.connect(inputs_set_selection) self.MainCanvas.noselected.connect(self.InputsWidget.clearSelection) self.InputsWidget.update_preview_button.clicked.connect( self.MainCanvas.updatePreviewPath) # Number and type synthesis. self.StructureSynthesis = StructureSynthesis(self) self.SynthesisTab.addTab(self.StructureSynthesis, self.StructureSynthesis.windowIcon(), "Structural") # Synthesis collections self.CollectionTabPage = Collections(self) self.SynthesisTab.addTab(self.CollectionTabPage, self.CollectionTabPage.windowIcon(), "Collections") self.StructureSynthesis.addCollection = ( self.CollectionTabPage.StructureWidget.addCollection) # Dimensional synthesis self.DimensionalSynthesis = DimensionalSynthesis(self) self.MainCanvas.set_target_point.connect( self.DimensionalSynthesis.setPoint) self.SynthesisTab.addTab(self.DimensionalSynthesis, self.DimensionalSynthesis.windowIcon(), "Dimensional") # File widget settings. self.DatabaseWidget = DatabaseWidget(self) self.SCMLayout.addWidget(self.DatabaseWidget) self.DatabaseWidget.commit_add.clicked.connect(self.commit) self.DatabaseWidget.branch_add.clicked.connect(self.commit_branch) self.action_stash.triggered.connect(self.DatabaseWidget.stash) # YAML editor. self.YamlEditor = YamlEditor(self) # Console dock will hide when startup. self.ConsoleWidget.hide() # Connect to GUI button switching. self.console_disconnect_button.setEnabled(not ARGUMENTS.debug_mode) self.console_connect_button.setEnabled(ARGUMENTS.debug_mode) # Splitter stretch factor. self.MainSplitter.setStretchFactor(0, 4) self.MainSplitter.setStretchFactor(1, 15) self.MechanismPanelSplitter.setSizes([500, 200]) self.synthesis_splitter.setSizes([100, 500]) # Enable mechanism menu actions when shows. self.menu_Mechanism.aboutToShow.connect(self.enableMechanismActions) # Start a new window. @pyqtSlot() def new_main_window(): run = self.__class__() run.show() self.action_new_window.triggered.connect(new_main_window) def __free_move(self): """Menu of free move mode.""" free_move_mode_menu = QMenu(self) def free_move_mode_func(j: int, icon_qt: QIcon): @pyqtSlot() def func(): self.free_move_button.setIcon(icon_qt) self.MainCanvas.setFreeMove(j) self.EntitiesTab.setCurrentIndex(0) self.InputsWidget.variable_stop.click() return func for i, (text, icon, tip) in enumerate(( ("View mode", "free_move_off", "Disable free move mode."), ("Translate mode", "translate", "Edit by 2 DOF moving."), ("Rotate mode", "rotate", "Edit by 1 DOF moving."), ("Reflect mode", "reflect", "Edit by flip axis."), )): action = QAction(QIcon(QPixmap(f":/icons/{icon}.png")), text, self) action.triggered.connect(free_move_mode_func(i, action.icon())) action.setShortcut(QKeySequence(f"Ctrl+{i + 1}")) action.setShortcutContext(Qt.WindowShortcut) action.setStatusTip(tip) free_move_mode_menu.addAction(action) if i == 0: self.free_move_disable = action self.free_move_button.setMenu(free_move_mode_menu) # Link free move by expression table. self.link_free_move_slider.sliderReleased.connect( self.MainCanvas.emit_free_move_all) def __options(self): """Signal connection for option widgets. + Spin boxes + Combo boxes + Check boxes """ # While value change, update the canvas widget. self.settings = QSettings('Kmol', 'Pyslvs') self.ZoomBar.valueChanged.connect(self.MainCanvas.setZoom) self.linewidth_option.valueChanged.connect( self.MainCanvas.setLinkWidth) self.pathwidth_option.valueChanged.connect( self.MainCanvas.setPathWidth) self.fontsize_option.valueChanged.connect(self.MainCanvas.setFontSize) self.action_show_point_mark.toggled.connect( self.MainCanvas.setPointMark) self.action_show_dimensions.toggled.connect( self.MainCanvas.setShowDimension) self.selectionradius_option.valueChanged.connect( self.MainCanvas.setSelectionRadius) self.linktrans_option.valueChanged.connect( self.MainCanvas.setTransparency) self.marginfactor_option.valueChanged.connect( self.MainCanvas.setMarginFactor) self.jointsize_option.valueChanged.connect( self.MainCanvas.setJointSize) self.zoomby_option.currentIndexChanged.connect( self.MainCanvas.setZoomBy) self.snap_option.valueChanged.connect(self.MainCanvas.setSnap) self.background_option.textChanged.connect( self.MainCanvas.setBackground) self.background_opacity_option.valueChanged.connect( self.MainCanvas.setBackgroundOpacity) self.background_scale_option.valueChanged.connect( self.MainCanvas.setBackgroundScale) self.background_offset_x_option.valueChanged.connect( self.MainCanvas.setBackgroundOffsetX) self.background_offset_y_option.valueChanged.connect( self.MainCanvas.setBackgroundOffsetY) # Resolve after change current kernel. self.planarsolver_option.addItems(kernel_list) self.pathpreview_option.addItems(kernel_list + ("Same as solver kernel", )) self.planarsolver_option.currentIndexChanged.connect(self.solve) self.pathpreview_option.currentIndexChanged.connect(self.solve) self.settings_reset.clicked.connect(self.resetOptions) def __zoom(self): """Zoom functions. + 'zoom to fit' function connections. + Zoom text buttons """ self.action_zoom_to_fit.triggered.connect(self.MainCanvas.zoomToFit) self.ResetCanvas.clicked.connect(self.MainCanvas.zoomToFit) zoom_menu = QMenu(self) def zoom_level(value: int): """Return a function that set the specified zoom value.""" @pyqtSlot() def func(): return self.ZoomBar.setValue(value) return func for level in range( self.ZoomBar.minimum() - self.ZoomBar.minimum() % 100 + 100, 500 + 1, 100): action = QAction(f'{level}%', self) action.triggered.connect(zoom_level(level)) zoom_menu.addAction(action) action = QAction("customize", self) action.triggered.connect(self.customizeZoom) zoom_menu.addAction(action) self.zoom_button.setMenu(zoom_menu) def __point_context_menu(self): """EntitiesPoint context menu + Add /////// + New Link + Edit + Grounded + Multiple joint - Point0 - Point1 - ... + Copy table data + Clone ------- + Delete """ self.EntitiesPoint_widget.customContextMenuRequested.connect( self.point_context_menu) self.pop_menu_point = QMenu(self) self.pop_menu_point.setSeparatorsCollapsible(True) self.action_point_context_add = QAction("&Add", self) self.action_point_context_add.triggered.connect(self.newPoint) self.pop_menu_point.addAction(self.action_point_context_add) # New Link self.pop_menu_point.addAction(self.action_new_link) self.action_point_context_edit = QAction("&Edit", self) self.action_point_context_edit.triggered.connect(self.editPoint) self.pop_menu_point.addAction(self.action_point_context_edit) self.action_point_context_lock = QAction("&Grounded", self) self.action_point_context_lock.setCheckable(True) self.action_point_context_lock.triggered.connect(self.lockPoints) self.pop_menu_point.addAction(self.action_point_context_lock) self.pop_menu_point_merge = QMenu(self) self.pop_menu_point_merge.setTitle("Multiple joint") self.pop_menu_point.addMenu(self.pop_menu_point_merge) self.action_point_context_copydata = QAction("&Copy table data", self) self.action_point_context_copydata.triggered.connect( self.copyPointsTable) self.pop_menu_point.addAction(self.action_point_context_copydata) self.action_point_context_copyCoord = QAction("&Copy coordinate", self) self.action_point_context_copyCoord.triggered.connect(self.copyCoord) self.pop_menu_point.addAction(self.action_point_context_copyCoord) self.action_point_context_copyPoint = QAction("C&lone", self) self.action_point_context_copyPoint.triggered.connect(self.clonePoint) self.pop_menu_point.addAction(self.action_point_context_copyPoint) self.pop_menu_point.addSeparator() self.action_point_context_delete = QAction("&Delete", self) self.action_point_context_delete.triggered.connect(self.deletePoints) self.pop_menu_point.addAction(self.action_point_context_delete) def __link_context_menu(self): """EntitiesLink context menu + Add + Edit + Merge links - Link0 - Link1 - ... + Copy table data + Release / Constrain ------- + Delete """ self.EntitiesLink_widget.customContextMenuRequested.connect( self.link_context_menu) self.pop_menu_link = QMenu(self) self.pop_menu_link.setSeparatorsCollapsible(True) self.action_link_context_add = QAction("&Add", self) self.action_link_context_add.triggered.connect(self.newLink) self.pop_menu_link.addAction(self.action_link_context_add) self.action_link_context_edit = QAction("&Edit", self) self.action_link_context_edit.triggered.connect(self.editLink) self.pop_menu_link.addAction(self.action_link_context_edit) self.pop_menu_link_merge = QMenu(self) self.pop_menu_link_merge.setTitle("Merge links") self.pop_menu_link.addMenu(self.pop_menu_link_merge) self.action_link_context_copydata = QAction("&Copy table data", self) self.action_link_context_copydata.triggered.connect( self.copyLinksTable) self.pop_menu_link.addAction(self.action_link_context_copydata) self.action_link_context_release = QAction("&Release", self) self.action_link_context_release.triggered.connect(self.releaseGround) self.pop_menu_link.addAction(self.action_link_context_release) self.action_link_context_constrain = QAction("C&onstrain", self) self.action_link_context_constrain.triggered.connect( self.constrainLink) self.pop_menu_link.addAction(self.action_link_context_constrain) self.pop_menu_link.addSeparator() self.action_link_context_delete = QAction("&Delete", self) self.action_link_context_delete.triggered.connect(self.deleteLinks) self.pop_menu_link.addAction(self.action_link_context_delete) def __canvas_context_menu(self): """MainCanvas context menus, switch the actions when selection mode changed. + Actions set of points. + Actions set of links. """ self.MainCanvas.setContextMenuPolicy(Qt.CustomContextMenu) self.MainCanvas.customContextMenuRequested.connect( self.canvas_context_menu) """ Actions set of points: + Add /////// + New Link + Add [fixed] + Add [target path] /////// + Edit + Grounded + Multiple joint - Point0 - Point1 - ... + Clone + Copy coordinate ------- + Delete """ self.pop_menu_canvas_p = QMenu(self) self.pop_menu_canvas_p.setSeparatorsCollapsible(True) self.action_canvas_context_add = QAction("&Add", self) self.action_canvas_context_add.triggered.connect(self.addNormalPoint) self.pop_menu_canvas_p.addAction(self.action_canvas_context_add) # New Link self.pop_menu_canvas_p.addAction(self.action_new_link) self.action_canvas_context_grounded_add = QAction( "Add [grounded]", self) self.action_canvas_context_grounded_add.triggered.connect( self.addFixedPoint) self.pop_menu_canvas_p.addAction( self.action_canvas_context_grounded_add) self.action_canvas_context_path = QAction("Add [target path]", self) self.action_canvas_context_path.triggered.connect(self.addTargetPoint) self.pop_menu_canvas_p.addAction(self.action_canvas_context_path) # The following actions will be shown when points selected. self.pop_menu_canvas_p.addAction(self.action_point_context_edit) self.pop_menu_canvas_p.addAction(self.action_point_context_lock) self.pop_menu_canvas_p.addMenu(self.pop_menu_point_merge) self.pop_menu_canvas_p.addAction(self.action_point_context_copyCoord) self.pop_menu_canvas_p.addAction(self.action_point_context_copyPoint) self.pop_menu_canvas_p.addSeparator() self.pop_menu_canvas_p.addAction(self.action_point_context_delete) """ Actions set of links: + Add /////// + Add [target path] /////// + Edit + Merge links - Link0 - Link1 - ... + Release / Constrain ------- + Delete """ self.pop_menu_canvas_l = QMenu(self) self.pop_menu_canvas_l.setSeparatorsCollapsible(True) self.pop_menu_canvas_l.addAction(self.action_link_context_add) self.pop_menu_canvas_l.addAction(self.action_link_context_edit) self.pop_menu_canvas_l.addMenu(self.pop_menu_link_merge) self.pop_menu_canvas_l.addAction(self.action_link_context_constrain) self.pop_menu_canvas_l.addSeparator() self.pop_menu_canvas_l.addAction(self.action_link_context_delete) @abstractmethod def commandReload(self, index: int) -> None: ... @abstractmethod def newPoint(self) -> None: ... @abstractmethod def addNormalPoint(self) -> None: ... @abstractmethod def addFixedPoint(self) -> None: ... @abstractmethod def editPoint(self) -> None: ... @abstractmethod def deletePoints(self) -> None: ... @abstractmethod def lockPoints(self) -> None: ... @abstractmethod def newLink(self) -> None: ... @abstractmethod def editLink(self) -> None: ... @abstractmethod def deleteLinks(self) -> None: ... @abstractmethod def constrainLink(self) -> None: ... @abstractmethod def releaseGround(self) -> None: ... @abstractmethod def addTargetPoint(self) -> None: ... @abstractmethod def setLinkFreeMove(self, enable: bool) -> None: ... @abstractmethod def setFreeMove( self, args: Sequence[Tuple[int, Tuple[float, float, float]]]) -> None: ... @abstractmethod def qAddNormalPoint(self, x: float, y: float) -> None: ... @abstractmethod def setMousePos(self, x: float, y: float) -> None: ... @abstractmethod def solve(self) -> None: ... @abstractmethod def resolve(self) -> None: ... @abstractmethod def commit(self, is_branch: bool = False) -> None: ... @abstractmethod def commit_branch(self) -> None: ... @abstractmethod def enableMechanismActions(self) -> None: ... @abstractmethod def clonePoint(self) -> None: ... @abstractmethod def copyCoord(self) -> None: ... @abstractmethod def copyPointsTable(self) -> None: ... @abstractmethod def copyLinksTable(self) -> None: ... @abstractmethod def canvas_context_menu(self, point: QPoint) -> None: ... @abstractmethod def link_context_menu(self, point: QPoint) -> None: ... @abstractmethod def customizeZoom(self) -> None: ... @abstractmethod def resetOptions(self) -> None: ...
def __appearance(self): """Start up and initialize custom widgets.""" # Version label self.version_label.setText(__version__) # Entities tables self.entities_tab.tabBar().setStatusTip( "Switch the tabs to change to another view mode.") self.entities_point = PointTableWidget(self.entities_point_widget) self.entities_point.cellDoubleClicked.connect(self.edit_point) self.entities_point.delete_request.connect(self.delete_selected_points) self.entities_point_layout.addWidget(self.entities_point) self.entities_link = LinkTableWidget(self.entities_link_widget) self.entities_link.cellDoubleClicked.connect(self.edit_link) self.entities_link.delete_request.connect(self.delete_selected_links) self.entities_link_layout.addWidget(self.entities_link) self.entities_expr = ExprTableWidget(self.EntitiesExpr_widget) self.entities_expr_layout.insertWidget(0, self.entities_expr) # Select all button on the Point and Link tab as corner widget. select_all_button = QPushButton() select_all_button.setIcon(QIcon(QPixmap(":/icons/select_all.png"))) select_all_button.setToolTip("Select all") select_all_button.setStatusTip("Select all item of point table.") @Slot() def table_select_all(): """Distinguish table by tab index.""" tables: List[BaseTableWidget] = [ self.entities_point, self.entities_link, self.entities_expr, ] tables[self.entities_tab.currentIndex()].selectAll() select_all_button.clicked.connect(table_select_all) self.entities_tab.setCornerWidget(select_all_button) select_all_action = QAction("Select all point", self) select_all_action.triggered.connect(table_select_all) select_all_action.setShortcut("Ctrl+A") select_all_action.setShortcutContext(Qt.WindowShortcut) self.addAction(select_all_action) # QPainter canvas window self.main_canvas = DynamicCanvas(self) select_tips = QLabel(self, Qt.ToolTip) self.entities_tab.currentChanged.connect( self.main_canvas.set_selection_mode) @Slot(QPoint, str) def show_select_tips(pos: QPoint, text: str): select_tips.setText(text) select_tips.move(pos - QPoint(0, select_tips.height())) select_tips.show() self.main_canvas.selected_tips.connect(show_select_tips) self.main_canvas.selected_tips_hide.connect(select_tips.hide) @Slot(tuple, bool) def table_set_selection(selections: Sequence[int], key_detect: bool): """Distinguish table by tab index.""" tables: List[BaseTableWidget] = [ self.entities_point, self.entities_link, self.entities_expr, ] tables[self.entities_tab.currentIndex()].set_selections( selections, key_detect) self.main_canvas.selected.connect(table_set_selection) self.entities_point.row_selection_changed.connect( self.main_canvas.set_selection) @Slot() def table_clear_selection(): """Distinguish table by tab index.""" tables: List[BaseTableWidget] = [ self.entities_point, self.entities_link, self.entities_expr, ] tables[self.entities_tab.currentIndex()].clearSelection() self.main_canvas.noselected.connect(table_clear_selection) clean_selection_action = QAction("Clean selection", self) clean_selection_action.triggered.connect(table_clear_selection) clean_selection_action.setShortcut("Esc") clean_selection_action.setShortcutContext(Qt.WindowShortcut) self.addAction(clean_selection_action) self.main_canvas.free_moved.connect(self.set_free_move) self.main_canvas.alt_add.connect(self.q_add_normal_point) self.main_canvas.doubleclick_edit.connect(self.edit_point) self.main_canvas.zoom_changed.connect(self.zoom_bar.setValue) self.main_canvas.tracking.connect(self.set_mouse_pos) self.canvas_splitter.insertWidget(0, self.main_canvas) self.canvas_splitter.setSizes([600, 10, 30]) # Selection label on status bar right side selection_label = SelectionLabel(self) self.entities_point.selectionLabelUpdate.connect( selection_label.update_select_point) self.main_canvas.browse_tracking.connect( selection_label.update_mouse_position) self.status_bar.addPermanentWidget(selection_label) # FPS label on status bar right side fps_label = FPSLabel(self) self.main_canvas.fps_updated.connect(fps_label.update_text) self.status_bar.addPermanentWidget(fps_label) # Inputs widget self.inputs_widget = InputsWidget(self) self.inputs_tab_layout.addWidget(self.inputs_widget) self.free_move_button.toggled.connect( self.inputs_widget.variable_value_reset) self.inputs_widget.about_to_resolve.connect(self.resolve) @Slot(tuple, bool) def inputs_set_selection(selections: Sequence[int], _: bool): """Distinguish table by tab index.""" self.inputs_widget.clear_selection() if self.entities_tab.currentIndex() == 0: self.inputs_widget.set_selection(selections) self.main_canvas.selected.connect(inputs_set_selection) self.main_canvas.noselected.connect(self.inputs_widget.clear_selection) self.inputs_widget.update_preview_button.clicked.connect( self.main_canvas.update_preview_path) # Number and type synthesis self.structure_synthesis = StructureSynthesis(self) self.synthesis_tab_widget.addTab(self.structure_synthesis, self.structure_synthesis.windowIcon(), "Structural") # Synthesis collections self.collection_tab_page = Collections(self) self.synthesis_tab_widget.addTab(self.collection_tab_page, self.collection_tab_page.windowIcon(), "Collections") self.structure_synthesis.addCollection = ( self.collection_tab_page.structure_widget.add_collection) # Dimensional synthesis self.dimensional_synthesis = DimensionalSynthesis(self) self.main_canvas.set_target_point.connect( self.dimensional_synthesis.set_point) self.synthesis_tab_widget.addTab( self.dimensional_synthesis, self.dimensional_synthesis.windowIcon(), "Dimensional") @Slot() def set_design_progress(): """Synthesis progress bar.""" pos = self.synthesis_tab_widget.currentIndex() if pos == 1: pos += self.collection_tab_page.tab_widget.currentIndex() elif pos == 2: pos += 1 self.synthesis_progress.setValue(pos) self.synthesis_tab_widget.currentChanged.connect(set_design_progress) self.collection_tab_page.tab_widget.currentChanged.connect( set_design_progress) # File widget settings self.database_widget = DatabaseWidget(self) self.vc_layout.addWidget(self.database_widget) self.database_widget.commit_add.clicked.connect(self.commit) self.database_widget.branch_add.clicked.connect(self.commit_branch) self.action_stash.triggered.connect(self.database_widget.stash) # YAML editor self.yaml_editor = YamlEditor(self) # Console dock will hide when startup self.console_widget.hide() # Connect to GUI button self.console_disconnect_button.setEnabled(not ARGUMENTS.debug_mode) self.console_connect_button.setEnabled(ARGUMENTS.debug_mode) # Splitter stretch factor self.main_splitter.setStretchFactor(0, 4) self.main_splitter.setStretchFactor(1, 15) self.mechanism_panel_splitter.setSizes([500, 200]) # Enable mechanism menu actions when shows. self.menu_mechanism.aboutToShow.connect(self.enable_mechanism_actions) @Slot() def new_main_window(): """Start a new window.""" run = self.__class__() run.show() self.action_new_window.triggered.connect(new_main_window)
class NumberAndTypeSynthesis(QWidget, Ui_Form): """Number and type synthesis widget.""" def __init__(self, parent): super(NumberAndTypeSynthesis, self).__init__(parent) self.setupUi(self) self.outputTo = parent.outputTo self.saveReplyBox = parent.saveReplyBox self.inputFrom = parent.inputFrom self.splitter.setStretchFactor(0, 2) self.splitter.setStretchFactor(1, 15) self.answer = [] self.save_edges_auto_label.setStatusTip(self.save_edges_auto.statusTip()) self.NL_input.valueChanged.connect(self.adjust_NJ_NL_dof) self.NJ_input.valueChanged.connect(self.adjust_NJ_NL_dof) self.graph_engine.addItems(EngineList) self.graph_engine.setCurrentIndex(2) self.graph_link_as_node.clicked.connect(self.on_reload_atlas_clicked) self.graph_engine.currentIndexChanged.connect( self.on_reload_atlas_clicked ) self.Topologic_result.customContextMenuRequested.connect( self.Topologic_result_context_menu ) """Context menu + Add to collections + Copy edges + Copy image """ self.popMenu_topo = QMenu(self) self.add_collection = QAction( QIcon(QPixmap(":/icons/collections.png")), "Add to collections", self ) self.copy_edges = QAction("Copy edges", self) self.copy_image = QAction("Copy image", self) self.popMenu_topo.addActions([ self.add_collection, self.copy_edges, self.copy_image ]) self.jointDataFunc = parent.Entities_Point.data self.linkDataFunc = parent.Entities_Link.data self.clear() def clear(self): """Clear all sub-widgets.""" self.answer.clear() self.Expression_edges.clear() self.Expression_number.clear() self.Topologic_result.clear() self.time_label.setText("") self.NL_input.setValue(0) self.NJ_input.setValue(0) self.NL_input_old_value = 0 self.NJ_input_old_value = 0 self.DOF.setValue(1) @pyqtSlot() def on_ReloadMechanism_clicked(self): """Reload button: Auto-combine the mechanism from the workbook.""" jointData = self.jointDataFunc() linkData = self.linkDataFunc() if jointData and linkData: self.Expression_edges.setText(str(v_to_graph(jointData, linkData))) else: self.Expression_edges.setText("") keep_dof_checked = self.keep_dof.isChecked() self.keep_dof.setChecked(False) self.NL_input.setValue( sum(len(vlink.points)>1 for vlink in linkData)+ sum( len(vpoint.links)-1 for vpoint in jointData if (vpoint.type == 2) and (len(vpoint.links) > 1) ) ) self.NJ_input.setValue(sum( (len(vpoint.links)-1 + int(vpoint.type == 2)) for vpoint in jointData if len(vpoint.links)>1 )) self.keep_dof.setChecked(keep_dof_checked) def adjust_NJ_NL_dof(self): """Update NJ and NL values. If user don't want to keep the DOF: Change the DOF then exit. """ if not self.keep_dof.isChecked(): self.DOF.setValue( 3 * (self.NL_input.value() - 1) - 2 * self.NJ_input.value() ) return """Prepare the input value. + N2: Get the user's adjusted value. + NL_func: Get the another value of parameters (N1) by degrees of freedom formula. + is_above: Is value increase or decrease? """ if self.sender() == self.NJ_input: N2 = self.NJ_input.value() NL_func = lambda: float(((self.DOF.value() + 2*N2) / 3) + 1) is_above = N2 > self.NJ_input_old_value else: N2 = self.NL_input.value() NL_func = lambda: float((3*(N2-1) - self.DOF.value()) / 2) is_above = N2 > self.NL_input_old_value N1 = NL_func() while not N1.is_integer(): N2 += 1 if is_above else -1 N1 = NL_func() if (N1 == 0) or (N2 == 0): break """Return the result values. + Value of widgets. + Setting old value record. """ if self.sender() == self.NL_input: self.NJ_input.setValue(N1) self.NL_input.setValue(N2) self.NJ_input_old_value = N1 self.NL_input_old_value = N2 else: self.NJ_input.setValue(N2) self.NL_input.setValue(N1) self.NJ_input_old_value = N2 self.NL_input_old_value = N1 @pyqtSlot() def on_Combine_number_clicked(self): """Show number of links with different number of joints.""" self.Expression_number.clear() NS_result = NumberSynthesis(self.NL_input.value(), self.NJ_input.value()) if type(NS_result) == str: item = QListWidgetItem(NS_result) item.links = None self.Expression_number.addItem(item) else: for result in NS_result: item = QListWidgetItem(", ".join( "NL{} = {}".format(i+2, result[i]) for i in range(len(result)) )) item.links = result self.Expression_number.addItem(item) self.Expression_number.setCurrentRow(0) @pyqtSlot() def on_Combine_type_clicked(self): """Type synthesis. If there has no data of number synthesis, execute number synthesis first. """ row = self.Expression_number.currentRow() if not row>-1: self.on_Combine_number_clicked() row = self.Expression_number.currentRow() if self.Expression_number.currentItem() is None: return answer = self.combineType(row) if answer: self.answer = answer self.on_reload_atlas_clicked() @pyqtSlot() def on_Combine_type_all_clicked(self): """Type synthesis - find all. If the data of number synthesis has multiple results, execute type synthesis one by one. """ if not self.Expression_number.currentRow()>-1: self.on_Combine_number_clicked() if self.Expression_number.currentItem().links is None: return answers = [] break_point = False for row in range(self.Expression_number.count()): answer = self.combineType(row) if answer: answers += answer else: break_point = True break if not answers: return if break_point: reply = QMessageBox.question(self, "Type synthesis - abort", "Do you want to keep the results?" ) if reply != QMessageBox.Yes: return self.answer = answers self.on_reload_atlas_clicked() def combineType(self, row: int): """Combine and show progress dialog.""" item = self.Expression_number.item(row) progdlg = QProgressDialog( "Analysis of the topology...", "Cancel", 0, 100, self ) progdlg.setWindowTitle("Type synthesis - ({})".format(item.text())) progdlg.setMinimumSize(QSize(500, 120)) progdlg.setModal(True) progdlg.show() #Call in every loop. def stopFunc(): QCoreApplication.processEvents() progdlg.setValue(progdlg.value() + 1) return progdlg.wasCanceled() def setjobFunc(job, maximum): progdlg.setLabelText(job) progdlg.setValue(0) progdlg.setMaximum(maximum+1) answer, time = topo( item.links, not self.graph_degenerate.isChecked(), setjobFunc, stopFunc ) self.time_label.setText("{}[min] {:.2f}[s]".format( int(time // 60), time % 60 )) progdlg.setValue(progdlg.maximum()) if answer: return [Graph(G.edges) for G in answer] @pyqtSlot() @pyqtSlot(str) def on_reload_atlas_clicked(self, p0=None): """Reload the atlas. Regardless there has any old data.""" self.engine = self.graph_engine.currentText().split(" - ")[1] self.Topologic_result.clear() if self.answer: progdlg = QProgressDialog( "Drawing atlas...", "Cancel", 0, len(self.answer), self ) progdlg.setWindowTitle("Type synthesis") progdlg.resize(400, progdlg.height()) progdlg.setModal(True) progdlg.show() for i, G in enumerate(self.answer): QCoreApplication.processEvents() if progdlg.wasCanceled(): return if self.drawAtlas(i, G): progdlg.setValue(i+1) else: break progdlg.setValue(progdlg.maximum()) def drawAtlas(self, i: int, G: Graph) -> bool: """Draw atlas and return True if done.""" item = QListWidgetItem("No. {}".format(i + 1)) try: item.setIcon(graph( G, self.Topologic_result.iconSize().width(), self.engine, self.graph_link_as_node.isChecked() )) except EngineError as e: QMessageBox.warning(self, str(e), "Please install and make sure Graphviz is working." ) return False else: item.setToolTip(str(G.edges)) self.Topologic_result.addItem(item) return True def atlas_image(self, row: int =None) -> QImage: """Capture a result item icon to image.""" w = self.Topologic_result if row is None: item = w.currentItem() else: item = w.item(row) return item.icon().pixmap(w.iconSize()).toImage() @pyqtSlot(QPoint) def Topologic_result_context_menu(self, point): """Context menu for the type synthesis results.""" index = self.Topologic_result.currentIndex().row() self.add_collection.setEnabled(index>-1) self.copy_edges.setEnabled(index>-1) self.copy_image.setEnabled(index>-1) action = self.popMenu_topo.exec_(self.Topologic_result.mapToGlobal(point)) if not action: return clipboard = QApplication.clipboard() if action==self.add_collection: self.addCollection(self.answer[index].edges) elif action==self.copy_edges: clipboard.setText(str(self.answer[index].edges)) elif action==self.copy_image: #Turn the transparent background to white. image1 = self.atlas_image() image2 = QImage(image1.size(), image1.format()) image2.fill(QColor(Qt.white).rgb()) painter = QPainter(image2) painter.drawImage(QPointF(0, 0), image1) painter.end() pixmap = QPixmap() pixmap.convertFromImage(image2) clipboard.setPixmap(pixmap) @pyqtSlot() def on_Expression_copy_clicked(self): """Copy expression button.""" string = self.Expression_edges.text() if string: QApplication.clipboard().setText(string) self.Expression_edges.selectAll() @pyqtSlot() def on_Expression_add_collection_clicked(self): """Add this expression to collections widget.""" string = self.Expression_edges.text() if string: self.addCollection(eval(string)) @pyqtSlot() def on_save_atlas_clicked(self): """Saving all the atlas to image file. We should turn transparent background to white first. Then using QImage class to merge into one image. """ fileName = "" lateral = 0 if self.save_edges_auto.isChecked(): lateral, ok = QInputDialog.getInt(self, "Atlas", "The number of lateral:", 5, 1, 10 ) if not ok: return fileName = self.outputTo("Atlas image", Qt_images) if fileName: reply = QMessageBox.question(self, "Type synthesis", "Do you want to Re-synthesis?", (QMessageBox.Yes | QMessageBox.YesToAll | QMessageBox.Cancel), QMessageBox.YesToAll ) if reply == QMessageBox.Yes: self.on_Combine_type_clicked() elif reply == QMessageBox.YesToAll: self.on_Combine_type_all_clicked() count = self.Topologic_result.count() if not count: return if not lateral: lateral, ok = QInputDialog.getInt(self, "Atlas", "The number of lateral:", 5, 1, 10 ) if not ok: return if not fileName: fileName = self.outputTo("Atlas image", Qt_images) if not fileName: return width = self.Topologic_result.iconSize().width() image_main = QImage( QSize( lateral * width if count>lateral else count * width, ((count // lateral) + bool(count % lateral)) * width ), self.atlas_image(0).format() ) image_main.fill(QColor(Qt.white).rgb()) painter = QPainter(image_main) for row in range(count): image = self.atlas_image(row) painter.drawImage(QPointF( row % lateral * width, row // lateral * width ), image) painter.end() pixmap = QPixmap() pixmap.convertFromImage(image_main) pixmap.save(fileName, format=QFileInfo(fileName).suffix()) self.saveReplyBox("Atlas", fileName) @pyqtSlot() def on_save_edges_clicked(self): """Saving all the atlas to text file.""" fileName = "" if self.save_edges_auto.isChecked(): fileName = self.outputTo( "Atlas edges expression", ["Text file (*.txt)"] ) if not fileName: return reply = QMessageBox.question(self, "Type synthesis", "Do you want to Re-synthesis?", (QMessageBox.Yes | QMessageBox.YesToAll | QMessageBox.Cancel), QMessageBox.YesToAll ) if reply == QMessageBox.Yes: self.on_Combine_type_clicked() elif reply == QMessageBox.YesToAll: self.on_Combine_type_all_clicked() count = self.Topologic_result.count() if not count: return if not fileName: fileName = self.outputTo( "Atlas edges expression", ["Text file (*.txt)"] ) if not fileName: return with open(fileName, 'w') as f: f.write('\n'.join(str(G.edges) for G in self.answer)) self.saveReplyBox("edges expression", fileName) @pyqtSlot() def on_Edges_to_altas_clicked(self): """Turn the text files into a atlas image. This opreation will load all edges to list widget first. """ fileNames = self.inputFrom( "Edges data", ["Text File (*.txt)"], multiple=True ) if not fileNames: return read_data = [] for fileName in fileNames: with open(fileName, 'r') as f: read_data += f.read().split('\n') answer = [] for edges in read_data: try: answer.append(Graph(eval(edges))) except: QMessageBox.warning(self, "Wrong format", "Please check the edges text format." ) return if not answer: return self.answer = answer self.on_reload_atlas_clicked() check_status = self.save_edges_auto.isChecked() self.save_edges_auto.setChecked(False) self.on_save_atlas_clicked() self.save_edges_auto.setChecked(check_status)
def __init__(self): super(MainWindow, self).__init__(None) self.setupUi(self) # Start new window. @pyqtSlot() def new_main_window(): XStream.back() run = self.__class__() run.show() self.action_New_Window.triggered.connect(new_main_window) # Text editor self.text_editor = TextEditor(self) self.h_splitter.addWidget(self.text_editor) self.text_editor.word_changed.connect(self.__set_not_saved_title) self.edge_line_option.toggled.connect(self.text_editor.setEdgeMode) self.trailing_blanks_option.toggled.connect(self.text_editor.set_remove_trailing_blanks) # Highlighters self.highlighter_option.addItems(sorted(QSCIHIGHLIGHTERS)) self.highlighter_option.setCurrentText("Markdown") self.highlighter_option.currentTextChanged.connect( self.text_editor.set_highlighter ) # Tree widget context menu. self.tree_widget.customContextMenuRequested.connect( self.on_tree_widget_context_menu ) self.popMenu_tree = QMenu(self) self.popMenu_tree.setSeparatorsCollapsible(True) self.popMenu_tree.addAction(self.action_new_project) self.popMenu_tree.addAction(self.action_open) self.tree_add = QAction("&Add Node", self) self.tree_add.triggered.connect(self.add_node) self.tree_add.setShortcut("Ctrl+I") self.tree_add.setShortcutContext(Qt.WindowShortcut) self.popMenu_tree.addAction(self.tree_add) self.popMenu_tree.addSeparator() self.tree_path = QAction("Set Path", self) self.tree_path.triggered.connect(self.set_path) self.popMenu_tree.addAction(self.tree_path) self.tree_refresh = QAction("&Refresh from Path", self) self.tree_refresh.triggered.connect(self.refresh_proj) self.popMenu_tree.addAction(self.tree_refresh) self.tree_openurl = QAction("&Open from Path", self) self.tree_openurl.triggered.connect(self.open_path) self.popMenu_tree.addAction(self.tree_openurl) self.action_save.triggered.connect(self.save_proj) self.popMenu_tree.addAction(self.action_save) self.tree_copy = QAction("Co&py", self) self.tree_copy.triggered.connect(self.copy_node) self.popMenu_tree.addAction(self.tree_copy) self.tree_clone = QAction("C&lone", self) self.tree_clone.triggered.connect(self.clone_node) self.popMenu_tree.addAction(self.tree_clone) self.tree_copy_tree = QAction("Recursive Copy", self) self.tree_copy_tree.triggered.connect(self.copy_node_recursive) self.popMenu_tree.addAction(self.tree_copy_tree) self.tree_clone_tree = QAction("Recursive Clone", self) self.tree_clone_tree.triggered.connect(self.clone_node_recursive) self.popMenu_tree.addAction(self.tree_clone_tree) self.popMenu_tree.addSeparator() self.tree_delete = QAction("&Delete", self) self.tree_delete.triggered.connect(self.delete_node) self.popMenu_tree.addAction(self.tree_delete) self.tree_close = QAction("&Close", self) self.tree_close.triggered.connect(self.close_file) self.popMenu_tree.addAction(self.tree_close) self.tree_main.header().setSectionResizeMode(QHeaderView.ResizeToContents) # Console self.console.setFont(self.text_editor.font) if not ARGUMENTS.debug_mode: XStream.stdout().messageWritten.connect(self.__append_to_console) XStream.stderr().messageWritten.connect(self.__append_to_console) for info in INFO: print(info) print('-' * 7) # Searching function. find_next = QShortcut(QKeySequence("F3"), self) find_next.activated.connect(self.find_next_button.click) find_previous = QShortcut(QKeySequence("F4"), self) find_previous.activated.connect(self.find_previous_button.click) find_tab = QShortcut(QKeySequence("Ctrl+F"), self) find_tab.activated.connect(lambda: self.panel_widget.setCurrentIndex(1)) find_project = QShortcut(QKeySequence("Ctrl+Shift+F"), self) find_project.activated.connect(self.find_project_button.click) # Replacing function. replace = QShortcut(QKeySequence("Ctrl+R"), self) replace.activated.connect(self.replace_node_button.click) replace_project = QShortcut(QKeySequence("Ctrl+Shift+R"), self) replace_project.activated.connect(self.replace_project_button.click) # Translator. self.panel_widget.addTab(TranslatorWidget(self), "Translator") # Node edit function. (Ctrl + ArrowKey) move_up_node = QShortcut(QKeySequence("Ctrl+Up"), self) move_up_node.activated.connect(self.__move_up_node) move_down_node = QShortcut(QKeySequence("Ctrl+Down"), self) move_down_node.activated.connect(self.__move_down_node) move_right_node = QShortcut(QKeySequence("Ctrl+Right"), self) move_right_node.activated.connect(self.__move_right_node) move_left_node = QShortcut(QKeySequence("Ctrl+Left"), self) move_left_node.activated.connect(self.__move_left_node) # Run script button. run_sript = QShortcut(QKeySequence("F5"), self) run_sript.activated.connect(self.exec_button.click) self.macros_toolbar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) # Splitter self.h_splitter.setStretchFactor(0, 10) self.h_splitter.setStretchFactor(1, 60) self.v_splitter.setStretchFactor(0, 30) self.v_splitter.setStretchFactor(1, 10) # Data self.data = DataDict() self.data.not_saved.connect(self.__set_not_saved_title) self.data.all_saved.connect(self.__set_saved_title) self.env = QStandardPaths.writableLocation(QStandardPaths.DesktopLocation) for filename in ARGUMENTS.r: filename = QFileInfo(filename).canonicalFilePath() if not filename: return root_node = QTreeRoot(QFileInfo(filename).baseName(), filename, '') self.tree_main.addTopLevelItem(root_node) parse(root_node, self.data) self.__add_macros()
class StructureSynthesis(QWidget, Ui_Form): """Number and type synthesis widget. Calculate the combinations of mechanism family and show the atlas. """ def __init__(self, parent: 'mw.MainWindow'): """Reference names: + IO functions from main window. + Table data from PMKS expression. + Graph data function from main window. """ super(StructureSynthesis, self).__init__(parent) self.setupUi(self) self.save_edges_auto_label.setStatusTip( self.save_edges_auto.statusTip()) # Function references self.outputTo = parent.outputTo self.saveReplyBox = parent.saveReplyBox self.inputFrom = parent.inputFrom self.jointDataFunc = parent.EntitiesPoint.dataTuple self.linkDataFunc = parent.EntitiesLink.dataTuple self.getGraph = parent.getGraph # Splitters self.splitter.setStretchFactor(0, 2) self.splitter.setStretchFactor(1, 15) # Answer list. self.answer: List[Graph] = [] # Signals self.NL_input.valueChanged.connect(self.__adjust_structure_data) self.NJ_input.valueChanged.connect(self.__adjust_structure_data) self.graph_engine.addItems(engines) self.structure_list.customContextMenuRequested.connect( self.__topologic_result_context_menu) """Context menu + Add to collections + Copy edges + Copy image """ self.pop_menu_topo = QMenu(self) self.add_collection = QAction( QIcon(QPixmap(":/icons/collections.png")), "Add to collections", self) self.copy_edges = QAction("Copy edges", self) self.copy_image = QAction("Copy image", self) self.pop_menu_topo.addActions( [self.add_collection, self.copy_edges, self.copy_image]) self.NL_input_old_value = 0 self.NJ_input_old_value = 0 self.clear() def clear(self): """Clear all sub-widgets.""" self.answer.clear() self.edges_text.clear() self.l_a_list.clear() self.__clear_structure_list() self.NL_input.setValue(0) self.NJ_input.setValue(0) self.NL_input_old_value = 0 self.NJ_input_old_value = 0 self.DOF.setValue(1) @pyqtSlot(name='on_structure_list_clear_button_clicked') def __clear_structure_list(self): """Clear the structure list.""" self.structure_list.clear() self.time_label.setText("") @pyqtSlot(name='on_from_mechanism_button_clicked') def __from_mechanism(self): """Reload button: Auto-combine the mechanism from the workbook.""" joint_data = self.jointDataFunc() link_data = self.linkDataFunc() if joint_data and link_data: graph = Graph(self.getGraph()) self.edges_text.setText(str(graph.edges)) else: graph = Graph([]) self.edges_text.setText("") keep_dof_checked = self.keep_dof.isChecked() self.keep_dof.setChecked(False) self.NL_input.setValue( sum(len(vlink.points) > 1 for vlink in link_data) + sum( len(vpoint.links) - 2 for vpoint in joint_data if (vpoint.type == VPoint.RP) and (len(vpoint.links) > 1))) self.NJ_input.setValue( sum((len(vpoint.links) - 1 + int(vpoint.type == VPoint.RP)) for vpoint in joint_data if (len(vpoint.links) > 1))) self.keep_dof.setChecked(keep_dof_checked) # Auto synthesis. if not graph.edges: return self.l_a_list.setCurrentRow( compare_assortment(tuple(l_a(graph)), self.__l_a_synthesis())) self.c_l_a_list.setCurrentRow( compare_assortment(tuple(c_l_a(graph)), self.__c_l_a_synthesis())) def __adjust_structure_data(self): """Update NJ and NL values. If user don't want to keep the DOF: Change the DOF then exit. """ if not self.keep_dof.isChecked(): self.DOF.setValue(3 * (self.NL_input.value() - 1) - 2 * self.NJ_input.value()) return """Prepare the input value. + N2: Get the user's adjusted value. + NL_func: Get the another value of parameters (N1) by degrees of freedom formula. + is_above: Is value increase or decrease? """ if self.sender() == self.NJ_input: n2 = self.NJ_input.value() def nl_func() -> float: return ((self.DOF.value() + 2 * n2) / 3) + 1 is_above = n2 > self.NJ_input_old_value else: n2 = self.NL_input.value() def nl_func() -> float: return (3 * (n2 - 1) - self.DOF.value()) / 2 is_above = n2 > self.NL_input_old_value n1 = nl_func() while not n1.is_integer(): n2 += 1 if is_above else -1 n1 = nl_func() if (n1 == 0) or (n2 == 0): break """Return the result values. + Value of widgets. + Setting old value record. """ if self.sender() == self.NL_input: self.NJ_input.setValue(n1) self.NL_input.setValue(n2) self.NJ_input_old_value = n1 self.NL_input_old_value = n2 else: self.NJ_input.setValue(n2) self.NL_input.setValue(n1) self.NJ_input_old_value = n2 self.NL_input_old_value = n1 @pyqtSlot(name='on_number_synthesis_button_clicked') def __l_a_synthesis(self) -> List[Tuple[int, ...]]: """Synthesis of link assortments.""" self.l_a_list.clear() self.c_l_a_list.clear() try: results = number_synthesis(self.NL_input.value(), self.NJ_input.value()) except Exception as e: item = QListWidgetItem(str(e)) self.l_a_list.addItem(item) return [] else: for result in results: self.l_a_list.addItem( QListWidgetItem(", ".join(f"NL{i + 2} = {result[i]}" for i in range(len(result))))) self.l_a_list.setCurrentRow(0) return results @pyqtSlot(int, name='on_l_a_list_currentRowChanged') def __c_l_a_synthesis(self, index: int = 0) -> List[Tuple[int, ...]]: """Synthesis of contracted link assortments.""" self.c_l_a_list.clear() item = self.l_a_list.item(index) if item is None: return [] results = contracted_link(_link_assortments(item.text())) for c_j in results: self.c_l_a_list.addItem( QListWidgetItem(", ".join(f"Nc{i + 1} = {c_j[i]}" for i in range(len(c_j))))) self.c_l_a_list.setCurrentRow(0) return results def __set_time_count(self, t: float, count: int): """Set time and count digit to label.""" self.time_label.setText(f"{t:.04f} s ({count})") @pyqtSlot(name='on_structure_synthesis_button_clicked') def __structure_synthesis(self): """Structural synthesis - find by contracted links.""" self.__clear_structure_list() row = self.l_a_list.currentRow() if row == -1: self.__l_a_synthesis() self.__c_l_a_synthesis() item_l_a: QListWidgetItem = self.l_a_list.currentItem() item_c_l_a: QListWidgetItem = self.c_l_a_list.currentItem() try: job_l_a = _link_assortments(item_l_a.text()) job_c_l_a = _link_assortments(item_c_l_a.text()) except ValueError: return self.__structural_combine([(job_l_a, job_c_l_a)], 1) @pyqtSlot(name='on_structure_synthesis_links_button_clicked') def __structure_synthesis_links(self): """Structural synthesis - find by links.""" self.__clear_structure_list() row = self.l_a_list.currentRow() if row == -1: self.__l_a_synthesis() self.__c_l_a_synthesis() item_l_a: QListWidgetItem = self.l_a_list.currentItem() try: job_l_a = _link_assortments(item_l_a.text()) except ValueError: return jobs = contracted_link(job_l_a) def jobs_iterator( _l_a: Sequence[int], _jobs: Sequence[Sequence[int]] ) -> Iterator[Tuple[Sequence[int], Sequence[int]]]: for _c_l_a in _jobs: yield _l_a, _c_l_a self.__structural_combine(jobs_iterator(job_l_a, jobs), len(jobs)) @pyqtSlot(name='on_structure_synthesis_all_button_clicked') def __structure_synthesis_all(self): """Structural synthesis - find all.""" self.__clear_structure_list() if self.l_a_list.currentRow() == -1: self.__l_a_synthesis() item: QListWidgetItem = self.c_l_a_list.currentItem() try: _link_assortments(item.text()) except ValueError: return job_count = 0 jobs = [] for row in range(self.l_a_list.count()): item: QListWidgetItem = self.l_a_list.item(row) job_l_a = _link_assortments(item.text()) job_c_l_as = contracted_link(job_l_a) job_count += len(job_c_l_as) jobs.append((job_l_a, job_c_l_as)) def jobs_iterator( _jobs: Sequence[Tuple[Sequence[int], Sequence[Sequence[int]]]] ) -> Iterator[Tuple[Sequence[int], Sequence[int]]]: for _l_a, _c_l_as in _jobs: for _c_l_a in _c_l_as: yield _l_a, _c_l_a self.__structural_combine(jobs_iterator(jobs), job_count) def __structural_combine(self, jobs: Iterable[Tuple[Sequence[int], Sequence[int]]], job_count: int): """Structural combine by iterator.""" dlg = SynthesisProgressDialog("Structural Synthesis", "", job_count, self) dlg.show() answers = [] break_point = False t0 = 0. c0 = 0 for job_l_a, job_c_l_a in jobs: answer, t1 = topo(job_l_a, job_c_l_a, self.graph_degenerate.currentIndex(), dlg.stop_func) dlg.next() if answer is not None: answers.extend(answer) t0 += t1 c0 += len(answer) else: break_point = True break if not answers: return if break_point: reply = QMessageBox.question(self, "Type synthesis - abort", "Do you want to keep the results?") if reply != QMessageBox.Yes: return # Save the answer list. self.answer = answers self.__set_time_count(t0, c0) self.__reload_atlas() @pyqtSlot(name='on_graph_link_as_node_clicked') @pyqtSlot(name='on_reload_atlas_clicked') @pyqtSlot(int, name='on_graph_engine_currentIndexChanged') def __reload_atlas(self, *_: int): """Reload the atlas.""" scroll_bar: QScrollBar = self.structure_list.verticalScrollBar() scroll_pos = scroll_bar.sliderPosition() self.structure_list.clear() if not self.answer: return dlg = SynthesisProgressDialog("Type synthesis", "Drawing atlas...", len(self.answer), self) dlg.show() for i, G in enumerate(self.answer): QCoreApplication.processEvents() if dlg.wasCanceled(): return if self.__draw_atlas(i, G): dlg.setValue(i + 1) else: break dlg.setValue(dlg.maximum()) scroll_bar.setSliderPosition(scroll_pos) def __draw_atlas(self, i: int, g: Graph) -> bool: """Draw atlas and return True if done.""" item = QListWidgetItem(f"No. {i + 1}") item.setIcon( to_graph(g, self.structure_list.iconSize().width(), self.graph_engine.currentText(), self.graph_link_as_node.isChecked())) item.setToolTip(f"Edge Set: {list(g.edges)}\n" f"Link Assortments: {l_a(g)}\n" f"Contracted Link Assortments: {c_l_a(g)}") self.structure_list.addItem(item) return True def __atlas_image(self, row: int = None) -> QImage: """Capture a result item icon to image.""" w = self.structure_list if row is None: item = w.currentItem() else: item = w.item(row) return item.icon().pixmap(w.iconSize()).toImage() @pyqtSlot(QPoint) def __topologic_result_context_menu(self, point): """Context menu for the type synthesis results.""" index = self.structure_list.currentIndex().row() self.add_collection.setEnabled(index > -1) self.copy_edges.setEnabled(index > -1) self.copy_image.setEnabled(index > -1) action = self.pop_menu_topo.exec_( self.structure_list.mapToGlobal(point)) if not action: return clipboard = QApplication.clipboard() if action == self.add_collection: self.addCollection(self.answer[index].edges) elif action == self.copy_edges: clipboard.setText(str(self.answer[index].edges)) elif action == self.copy_image: # Turn the transparent background to white. image1 = self.__atlas_image() image2 = QImage(image1.size(), image1.format()) image2.fill(QColor(Qt.white).rgb()) painter = QPainter(image2) painter.drawImage(QPointF(0, 0), image1) painter.end() pixmap = QPixmap() pixmap.convertFromImage(image2) clipboard.setPixmap(pixmap) @pyqtSlot(name='on_expr_copy_clicked') def __copy_expr(self): """Copy expression button.""" string = self.edges_text.text() if string: QApplication.clipboard().setText(string) self.edges_text.selectAll() @pyqtSlot(name='on_expr_add_collection_clicked') def __add_collection(self): """Add this expression to collections widget.""" string = self.edges_text.text() if string: self.addCollection(eval(string)) @pyqtSlot(name='on_save_atlas_clicked') def __save_atlas(self): """Saving all the atlas to image file. We should turn transparent background to white first. Then using QImage class to merge into one image. """ file_name = "" lateral = 0 if self.save_edges_auto.isChecked(): lateral, ok = QInputDialog.getInt(self, "Atlas", "The number of lateral:", 5, 1, 10) if not ok: return file_name = self.outputTo("Atlas image", qt_image_format) if file_name: reply = QMessageBox.question( self, "Type synthesis", "Do you want to Re-synthesis?", (QMessageBox.Yes | QMessageBox.YesToAll | QMessageBox.Cancel), QMessageBox.Yes) if reply == QMessageBox.Yes: self.__structure_synthesis() elif reply == QMessageBox.YesToAll: self.__structure_synthesis_all() count = self.structure_list.count() if not count: return if not lateral: lateral, ok = QInputDialog.getInt(self, "Atlas", "The number of lateral:", 5, 1, 10) if not ok: return if not file_name: file_name = self.outputTo("Atlas image", qt_image_format) if not file_name: return width = self.structure_list.iconSize().width() image_main = QImage( QSize(lateral * width if count > lateral else count * width, ((count // lateral) + bool(count % lateral)) * width), self.__atlas_image(0).format()) image_main.fill(QColor(Qt.white).rgb()) painter = QPainter(image_main) for row in range(count): image = self.__atlas_image(row) painter.drawImage( QPointF(row % lateral * width, row // lateral * width), image) painter.end() pixmap = QPixmap() pixmap.convertFromImage(image_main) pixmap.save(file_name, format=QFileInfo(file_name).suffix()) self.saveReplyBox("Atlas", file_name) @pyqtSlot(name='on_save_edges_clicked') def __save_edges(self): """Saving all the atlas to text file.""" file_name = "" if self.save_edges_auto.isChecked(): file_name = self.outputTo("Atlas edges expression", ["Text file (*.txt)"]) if not file_name: return reply = QMessageBox.question( self, "Type synthesis", "Do you want to Re-synthesis?", (QMessageBox.Yes | QMessageBox.YesToAll | QMessageBox.Cancel), QMessageBox.Yes) if reply == QMessageBox.Yes: self.__structure_synthesis() elif reply == QMessageBox.YesToAll: self.__structure_synthesis_all() count = self.structure_list.count() if not count: return if not file_name: file_name = self.outputTo("Atlas edges expression", ["Text file (*.txt)"]) if not file_name: return with open(file_name, 'w') as f: f.write('\n'.join(str(G.edges) for G in self.answer)) self.saveReplyBox("edges expression", file_name) @pyqtSlot(name='on_edges2atlas_button_clicked') def __edges2atlas(self): """Turn the text files into a atlas image. This operation will load all edges to list widget first. """ file_names = self.inputFrom("Edges data", ["Text file (*.txt)"], multiple=True) if not file_names: return read_data = [] for file_name in file_names: with open(file_name) as f: for line in f: read_data.append(line) answer = [] for edges in read_data: try: g = Graph(eval(edges)) except (SyntaxError, TypeError): QMessageBox.warning(self, "Wrong format", "Please check text format.") else: answer.append(g) if not answer: QMessageBox.information(self, "No data", "The graph data is empty.") return self.answer = answer self.__reload_atlas() self.save_edges_auto.setChecked(False) self.__save_atlas() self.save_edges_auto.setChecked(self.save_edges_auto.isChecked())
def __appearance(self): """Start up and initialize custom widgets.""" # Version label self.version_label.setText(f"v{_major}.{_minor}.{_build} ({_label})") # Entities tables. self.EntitiesTab.tabBar().setStatusTip( "Switch the tabs to change to another view mode.") self.EntitiesPoint = PointTableWidget(self.EntitiesPoint_widget) self.EntitiesPoint.cellDoubleClicked.connect(self.editPoint) self.EntitiesPoint.deleteRequest.connect(self.deletePoints) self.EntitiesPoint_layout.addWidget(self.EntitiesPoint) self.EntitiesLink = LinkTableWidget(self.EntitiesLink_widget) self.EntitiesLink.cellDoubleClicked.connect(self.editLink) self.EntitiesLink.deleteRequest.connect(self.deleteLinks) self.EntitiesLink_layout.addWidget(self.EntitiesLink) self.EntitiesExpr = ExprTableWidget(self.EntitiesExpr_widget) self.EntitiesExpr.reset.connect(self.link_free_move_widget.setEnabled) self.EntitiesExpr.free_move_request.connect(self.setLinkFreeMove) self.EntitiesExpr_layout.insertWidget(0, self.EntitiesExpr) # Link free mode slide bar. self.link_free_move_slider.valueChanged.connect( self.link_free_move_spinbox.setValue) self.link_free_move_spinbox.valueChanged.connect( self.link_free_move_slider.setValue) self.link_free_move_slider.rangeChanged.connect( self.link_free_move_spinbox.setRange) # Select all button on the Point and Link tab as corner widget. select_all_button = QPushButton() select_all_button.setIcon(QIcon(QPixmap(":/icons/select_all.png"))) select_all_button.setToolTip("Select all") select_all_button.setStatusTip("Select all item of point table.") @pyqtSlot() def table_select_all(): """Distinguish table by tab index.""" tables = (self.EntitiesPoint, self.EntitiesLink, self.EntitiesExpr) tables[self.EntitiesTab.currentIndex()].selectAll() select_all_button.clicked.connect(table_select_all) self.EntitiesTab.setCornerWidget(select_all_button) select_all_action = QAction("Select all point", self) select_all_action.triggered.connect(table_select_all) select_all_action.setShortcut("Ctrl+A") select_all_action.setShortcutContext(Qt.WindowShortcut) self.addAction(select_all_action) # QPainter canvas window self.MainCanvas = DynamicCanvas(self) self.EntitiesTab.currentChanged.connect( self.MainCanvas.setSelectionMode) @pyqtSlot(tuple, bool) def table_set_selection(selections: Tuple[int], key_detect: bool): """Distinguish table by tab index.""" tables = (self.EntitiesPoint, self.EntitiesLink, self.EntitiesExpr) tables[self.EntitiesTab.currentIndex()].setSelections( selections, key_detect) self.MainCanvas.selected.connect(table_set_selection) self.EntitiesPoint.rowSelectionChanged.connect( self.MainCanvas.setSelection) @pyqtSlot() def table_clear_selection(): """Distinguish table by tab index.""" tables = (self.EntitiesPoint, self.EntitiesLink, self.EntitiesExpr) tables[self.EntitiesTab.currentIndex()].clearSelection() self.MainCanvas.noselected.connect(table_clear_selection) clean_selection_action = QAction("Clean selection", self) clean_selection_action.triggered.connect(table_clear_selection) clean_selection_action.setShortcut("Esc") clean_selection_action.setShortcutContext(Qt.WindowShortcut) self.addAction(clean_selection_action) self.MainCanvas.free_moved.connect(self.setFreeMove) self.MainCanvas.alt_add.connect(self.qAddNormalPoint) self.MainCanvas.doubleclick_edit.connect(self.editPoint) self.MainCanvas.zoom_changed.connect(self.ZoomBar.setValue) self.MainCanvas.tracking.connect(self.setMousePos) self.canvasSplitter.insertWidget(0, self.MainCanvas) self.canvasSplitter.setSizes([600, 10, 30]) # Selection label on status bar right side. selection_label = SelectionLabel(self) self.EntitiesPoint.selectionLabelUpdate.connect( selection_label.updateSelectPoint) self.MainCanvas.browse_tracking.connect( selection_label.updateMousePosition) self.status_bar.addPermanentWidget(selection_label) # FPS label on status bar right side. fps_label = FPSLabel(self) self.MainCanvas.fps_updated.connect(fps_label.updateText) self.status_bar.addPermanentWidget(fps_label) # Inputs widget. self.InputsWidget = InputsWidget(self) self.inputs_tab_layout.addWidget(self.InputsWidget) self.free_move_button.toggled.connect( self.InputsWidget.variableValueReset) self.InputsWidget.aboutToResolve.connect(self.resolve) @pyqtSlot(tuple, bool) def inputs_set_selection(selections: Tuple[int], _: bool): """Distinguish table by tab index.""" self.InputsWidget.clearSelection() if self.EntitiesTab.currentIndex() == 0: self.InputsWidget.setSelection(selections) self.MainCanvas.selected.connect(inputs_set_selection) self.MainCanvas.noselected.connect(self.InputsWidget.clearSelection) self.InputsWidget.update_preview_button.clicked.connect( self.MainCanvas.updatePreviewPath) # Number and type synthesis. self.StructureSynthesis = StructureSynthesis(self) self.SynthesisTab.addTab(self.StructureSynthesis, self.StructureSynthesis.windowIcon(), "Structural") # Synthesis collections self.CollectionTabPage = Collections(self) self.SynthesisTab.addTab(self.CollectionTabPage, self.CollectionTabPage.windowIcon(), "Collections") self.StructureSynthesis.addCollection = ( self.CollectionTabPage.StructureWidget.addCollection) # Dimensional synthesis self.DimensionalSynthesis = DimensionalSynthesis(self) self.MainCanvas.set_target_point.connect( self.DimensionalSynthesis.setPoint) self.SynthesisTab.addTab(self.DimensionalSynthesis, self.DimensionalSynthesis.windowIcon(), "Dimensional") # File widget settings. self.DatabaseWidget = DatabaseWidget(self) self.SCMLayout.addWidget(self.DatabaseWidget) self.DatabaseWidget.commit_add.clicked.connect(self.commit) self.DatabaseWidget.branch_add.clicked.connect(self.commit_branch) self.action_stash.triggered.connect(self.DatabaseWidget.stash) # YAML editor. self.YamlEditor = YamlEditor(self) # Console dock will hide when startup. self.ConsoleWidget.hide() # Connect to GUI button switching. self.console_disconnect_button.setEnabled(not ARGUMENTS.debug_mode) self.console_connect_button.setEnabled(ARGUMENTS.debug_mode) # Splitter stretch factor. self.MainSplitter.setStretchFactor(0, 4) self.MainSplitter.setStretchFactor(1, 15) self.MechanismPanelSplitter.setSizes([500, 200]) self.synthesis_splitter.setSizes([100, 500]) # Enable mechanism menu actions when shows. self.menu_Mechanism.aboutToShow.connect(self.enableMechanismActions) # Start a new window. @pyqtSlot() def new_main_window(): run = self.__class__() run.show() self.action_new_window.triggered.connect(new_main_window)