Exemplo n.º 1
0
    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)
Exemplo n.º 2
0
    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)
Exemplo n.º 3
0
    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)
Exemplo n.º 4
0
    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)
Exemplo n.º 5
0
    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])
Exemplo n.º 6
0
 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()))
Exemplo n.º 7
0
    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)
Exemplo n.º 8
0
    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)
Exemplo n.º 9
0
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)
Exemplo n.º 10
0
    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()
Exemplo n.º 11
0
    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)
Exemplo n.º 12
0
 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()
Exemplo n.º 13
0
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."""
Exemplo n.º 14
0
    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)
Exemplo n.º 15
0
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:
        ...
Exemplo n.º 16
0
    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)
Exemplo n.º 17
0
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:
        ...
Exemplo n.º 18
0
    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)
Exemplo n.º 19
0
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)
Exemplo n.º 20
0
    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()
Exemplo n.º 21
0
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())
Exemplo n.º 22
0
    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)