Beispiel #1
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:
        ...
Beispiel #2
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:
        ...