Beispiel #1
0
 def setUp(self):
     self.polygons = [
         [1, 2, 1, '1,2'],
         [2, 2, 1, '3,4'],
         [4, 1, 1, '5,6'],
         [5, 1, 1, '5,6'],
         [6, 0, 1, '5,6'],
     ]
     self.layers = [
         [[1, 6, 'desc'], ],
         [[1, 4, 0, 6], [2, 5, 0, 6]],
         [[1, 1, 0, 4], [2, 2, 0, 5]],
         [],
     ]
     self.polygon_table = create_dao_polygon_table(self.polygons, self.layers)
     self.helper = DbHelper()
     self.helper.polygon_table = self.polygon_table
Beispiel #2
0
 def __init__(self, parent=None):
     super().__init__()
     config_loader.load_all()
     # ui
     self.ui = Ui_MainWindow()
     self.ui.setupUi(self)
     self.ui.graphics_view.setScene(QGraphicsScene())
     self.view = self.ui.graphics_view
     self.scene = self.ui.graphics_view.scene()
     self.ui.polygon_table_widget.setColumnCount(2)
     self.ui.polygon_table_widget.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection)
     self.ui.polygon_table_widget.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
     self.ui.polygon_table_widget.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
     self.ui.second_table_widget.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection)
     self.ui.second_table_widget.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
     self.ui.second_table_widget.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
     self.ui.insert_layer_combo_box.addItems(config_loader.get_layer_names())
     self.ui.graphics_view.scale(1, -1)  # invert y
     # data
     self.db = DbHelper()
     self.command_handler = MapCommand(self.db)
     self.path = None
     # fsm
     self.__init_fsm()
     # other signals/slots
     self.command_handler.gotoPolygon.connect(self.goto_polygon)
     self.ui.polygon_table_widget.itemSelectionChanged.connect(self.polygon_selection_changed)
     self.ui.polygon_table_widget.itemClicked.connect(self.polygon_selection_clicked)
     self.ui.polygon_table_widget.polygonActivated.connect(self.goto_polygon)
     self.ui.second_table_widget.itemSelectionChanged.connect(self.second_selection_changed)
     self.ui.second_table_widget.polygonActivated.connect(self.goto_polygon)
     self.ui.scale_slider.valueChanged.connect(self.scale_slider_changed)
     self.ui.graphics_view.polygonCreated.connect(self.add_polygon)
     self.ui.graphics_view.polygonUpdated.connect(self.update_polygon)
     self.ui.graphics_view.pointsUpdated.connect(self.update_points)
     log.logger.onLog.connect(self.print_to_output)
     # open default database
     self.setAcceptDrops(True)
     self.open("default.sqlite", True)
Beispiel #3
0
class TestDbHelper(unittest.TestCase):
    def setUp(self):
        self.polygons = [
            [1, 2, 1, '1,2'],
            [2, 2, 1, '3,4'],
            [4, 1, 1, '5,6'],
            [5, 1, 1, '5,6'],
            [6, 0, 1, '5,6'],
        ]
        self.layers = [
            [[1, 6, 'desc'], ],
            [[1, 4, 0, 6], [2, 5, 0, 6]],
            [[1, 1, 0, 4], [2, 2, 0, 5]],
            [],
        ]
        self.polygon_table = create_dao_polygon_table(self.polygons, self.layers)
        self.helper = DbHelper()
        self.helper.polygon_table = self.polygon_table

    @patch('dao.db_loader.load_from_sqlite')
    def test_load(self, mock_load):
        mock_load.return_value = {1: DaoPolygon(self.polygons[0])}
        self.helper.load_tables('')
        self.assertEqual(1, mock_load.call_count)

    @patch('dao.db_loader.write_to_sqlite')
    def test_save(self, mock_save):
        self.helper.write_to_file('')
        self.assertEqual(1, mock_save.call_count)

    def test_create_dao_polygon_table(self):
        # 测试能否从来自数据库的数据 list 创建 Polygon 表
        self.assertEqual(5, len(self.polygon_table))
        self.assertEqual([1, 4, 2, 5, 6], self.polygon_table[6].traversal_post_order())
        self.assertEqual(1, self.polygon_table[1].polygon_id)
        self.assertEqual(2, self.polygon_table[2].polygon_id)
        self.assertEqual(4, self.polygon_table[4].polygon_id)
        self.assertEqual(5, self.polygon_table[5].polygon_id)
        self.assertEqual(6, self.polygon_table[6].polygon_id)

    def test_get_polygon_by_id(self):
        self.assertEqual(4, self.helper.get_polygon_by_id(4).polygon_id)
        self.assertIsNone(self.helper.get_polygon_by_id(3))

    def test_get_children_table_by_id(self):
        self.assertEqual([4, 5], list(self.helper.get_children_table_by_id(6).keys()))
        self.assertEqual([2], list(self.helper.get_children_table_by_id(5).keys()))
        self.assertDictEqual({}, self.helper.get_children_table_by_id(2))
        self.assertIsNone(self.helper.get_children_table_by_id(8))

    def test_clear(self):
        self.helper.clear()
        self.assertDictEqual({}, self.helper.polygon_table)

    def test_delete_by_id(self):
        self.helper.delete_by_id(1)
        self.assertEqual([4, 2, 5, 6], self.polygon_table[6].traversal_post_order())
        self.helper.delete_by_id(5)
        self.assertEqual([4, 6], self.polygon_table[6].traversal_post_order())

    def test_add_polygon(self):
        self.assertEqual(5, len(self.helper.polygon_table))
        # L0
        self.helper.add_l0_polygon(9, 'new')
        self.assertEqual(6, len(self.helper.polygon_table))
        self.assertEqual(9, self.polygon_table[9].polygon_id)
        # Other
        self.helper.add_lp_polygon(10, 2, 0, 9)
        self.assertEqual(7, len(self.helper.polygon_table))
        self.assertEqual(10, self.polygon_table[10].polygon_id)
        self.assertEqual([10, 9], self.polygon_table[9].traversal_post_order())
Beispiel #4
0
class MainWindow(QMainWindow):
    def __init__(self, parent=None):
        super().__init__()
        config_loader.load_all()
        # ui
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        self.ui.graphics_view.setScene(QGraphicsScene())
        self.view = self.ui.graphics_view
        self.scene = self.ui.graphics_view.scene()
        self.ui.polygon_table_widget.setColumnCount(2)
        self.ui.polygon_table_widget.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection)
        self.ui.polygon_table_widget.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
        self.ui.polygon_table_widget.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
        self.ui.second_table_widget.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection)
        self.ui.second_table_widget.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
        self.ui.second_table_widget.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
        self.ui.insert_layer_combo_box.addItems(config_loader.get_layer_names())
        self.ui.graphics_view.scale(1, -1)  # invert y
        # data
        self.db = DbHelper()
        self.command_handler = MapCommand(self.db)
        self.path = None
        # fsm
        self.__init_fsm()
        # other signals/slots
        self.command_handler.gotoPolygon.connect(self.goto_polygon)
        self.ui.polygon_table_widget.itemSelectionChanged.connect(self.polygon_selection_changed)
        self.ui.polygon_table_widget.itemClicked.connect(self.polygon_selection_clicked)
        self.ui.polygon_table_widget.polygonActivated.connect(self.goto_polygon)
        self.ui.second_table_widget.itemSelectionChanged.connect(self.second_selection_changed)
        self.ui.second_table_widget.polygonActivated.connect(self.goto_polygon)
        self.ui.scale_slider.valueChanged.connect(self.scale_slider_changed)
        self.ui.graphics_view.polygonCreated.connect(self.add_polygon)
        self.ui.graphics_view.polygonUpdated.connect(self.update_polygon)
        self.ui.graphics_view.pointsUpdated.connect(self.update_points)
        log.logger.onLog.connect(self.print_to_output)
        # open default database
        self.setAcceptDrops(True)
        self.open("default.sqlite", True)

    def __init_fsm(self):
        """初始化 UI 状态机"""
        self.fsm_mgr = FsmMgr()
        self.fsm_mgr.changeState.connect(self.change_state)
        self.fsm_mgr.get_fsm("insert").enterState.connect(self.ui.graphics_view.begin_insert)
        self.fsm_mgr.get_fsm("insert").exitState.connect(self.ui.graphics_view.end_insert)
        self.fsm_mgr.get_fsm("normal").transferToState.connect(
            lambda name: self.ui.graphics_view.begin_move() if (name == "move") else None
        )
        self.fsm_mgr.get_fsm("normal").transferToState.connect(
            lambda name: self.ui.graphics_view.begin_move() if (name == "move_point") else None
        )
        self.fsm_mgr.get_fsm("move").transferToState.connect(
            lambda name: self.ui.graphics_view.end_move() if (name == "normal") else None
        )
        self.fsm_mgr.get_fsm("move_point").transferToState.connect(
            lambda name: self.ui.graphics_view.end_move() if (name == "normal") else None
        )
        self.change_state(self.fsm_mgr.get_current_state())

    # slots
    @pyqtSlot(QObject)
    def change_state(self, new_state):
        """UI 状态转移"""
        if new_state == self.fsm_mgr.get_fsm("empty"):
            self.ui.save_action.setEnabled(False)
            self.ui.undo_action.setEnabled(False)
            self.ui.redo_action.setEnabled(False)
            self.ui.insert_action.setEnabled(False)
            self.ui.delete_action.setEnabled(False)
            self.ui.move_action.setEnabled(False)
            self.ui.graphics_view.setCursor(QCursor(Qt.ForbiddenCursor))
            self.ui.polygon_table_widget.setEnabled(False)
            self.ui.list2_type_label.setText("")
        if new_state == self.fsm_mgr.get_fsm("normal"):
            self.ui.save_action.setEnabled(True)
            self.ui.undo_action.setEnabled(True)
            self.ui.redo_action.setEnabled(True)
            self.ui.insert_action.setEnabled(True)
            self.ui.delete_action.setEnabled(True)
            self.ui.move_action.setEnabled(True)
            self.ui.graphics_view.setCursor(QCursor(Qt.ArrowCursor))
            self.ui.polygon_table_widget.setEnabled(True)
            self.ui.list2_type_label.setText("children")
        elif new_state == self.fsm_mgr.get_fsm("insert"):
            self.ui.undo_action.setEnabled(False)
            self.ui.redo_action.setEnabled(False)
            self.ui.insert_action.setEnabled(True)
            self.ui.delete_action.setEnabled(False)
            self.ui.move_action.setEnabled(False)
            self.ui.graphics_view.setCursor(QCursor(Qt.CrossCursor))
            self.ui.polygon_table_widget.setEnabled(True)
            self.ui.list2_type_label.setText("children")
        elif new_state == self.fsm_mgr.get_fsm("move"):
            self.ui.undo_action.setEnabled(False)
            self.ui.redo_action.setEnabled(False)
            self.ui.insert_action.setEnabled(False)
            self.ui.delete_action.setEnabled(False)
            self.ui.move_action.setEnabled(True)
            self.ui.graphics_view.setCursor(QCursor(Qt.DragMoveCursor))
            self.ui.polygon_table_widget.setEnabled(False)
            self.ui.list2_type_label.setText("points")
        elif new_state == self.fsm_mgr.get_fsm("move_point"):
            self.ui.undo_action.setEnabled(False)
            self.ui.redo_action.setEnabled(False)
            self.ui.insert_action.setEnabled(False)
            self.ui.delete_action.setEnabled(False)
            self.ui.move_action.setEnabled(True)
            self.ui.graphics_view.setCursor(QCursor(Qt.DragMoveCursor))
            self.ui.polygon_table_widget.setEnabled(False)
            self.ui.list2_type_label.setText("points")

    @pyqtSlot()
    def on_open_action_triggered(self):
        """点击“打开”按钮"""
        path = QFileDialog.getOpenFileName(self, "载入数据", ".", "数据库文档(*.sqlite)")[0]
        if path:
            self.open(path)

    @pyqtSlot()
    def on_save_action_triggered(self):
        """点击“保存”按钮"""
        if self.path is not None:
            self.save(self.path)

    @pyqtSlot()
    def on_undo_action_triggered(self):
        """点击“撤销”按钮"""
        try:
            self.command_handler.undo()
            self.update_polygon_list()
        except Exception as e:
            log.error("撤销操作出错: %s" % repr(e))
            return False
        else:
            return True

    @pyqtSlot()
    def on_redo_action_triggered(self):
        """点击“重做”按钮"""
        try:
            self.command_handler.redo()
            self.update_polygon_list()
        except Exception as e:
            log.error("重做操作出错: %s" % repr(e))
            return False
        else:
            return True

    @pyqtSlot()
    def on_insert_action_triggered(self):
        """点击“插入”按钮"""
        if self.ui.insert_action.isChecked():
            if not self.fsm_mgr.change_fsm("normal", "insert"):
                self.ui.insert_action.setChecked(False)
        else:
            if not self.fsm_mgr.change_fsm("insert", "normal"):
                self.ui.insert_action.setChecked(True)

    @pyqtSlot()
    def on_delete_action_triggered(self):
        """点击“删除”按钮"""
        _id = self.selected_id()
        if _id >= 0:
            row = self.ui.polygon_table_widget.currentRow()
            self.execute("del shape %d" % _id)
            self.ui.polygon_table_widget.setCurrentCell(row, 0)

    @pyqtSlot()
    def on_about_action_triggered(self):
        """点击“关于”按钮"""
        info = "L5MapEditor by bssthu\n\n" "https://github.com/bssthu/L5MapEditor"
        QMessageBox.about(self, "关于", info)

    @pyqtSlot()
    def on_exit_action_triggered(self):
        """点击“退出”按钮"""
        exit()

    @pyqtSlot()
    def on_move_action_triggered(self):
        """点击“移动”按钮"""
        state_name = "move" if not self.ui.move_point_action.isChecked() else "move_point"
        if self.ui.move_action.isChecked():
            if not self.fsm_mgr.change_fsm("normal", state_name):
                self.ui.move_action.setChecked(False)
        else:
            if not self.fsm_mgr.change_fsm(state_name, "normal"):
                self.ui.move_action.setChecked(True)

    @pyqtSlot()
    def on_move_point_action_triggered(self):
        """点击“拾取点”按钮"""
        self.ui.graphics_view.move_point(self.ui.move_point_action.isChecked())
        if self.ui.move_point_action.isChecked():
            self.fsm_mgr.change_fsm("move", "move_point")
        else:
            self.fsm_mgr.change_fsm("move_point", "move")

    @pyqtSlot()
    def on_closed_polygon_action_triggered(self):
        """点击“绘制封闭多边形”按钮"""
        self.ui.graphics_view.draw_closed_polygon(self.ui.closed_polygon_action.isChecked())

    @pyqtSlot()
    def on_highlight_action_triggered(self):
        """点击“突出显示图形”按钮"""
        self.ui.graphics_view.highlight_selection(self.ui.highlight_action.isChecked())

    @pyqtSlot()
    def on_grid_action_triggered(self):
        """点击“显示网格”按钮"""
        pass

    @pyqtSlot()
    def on_mark_points_action_triggered(self):
        """点击“标出点”按钮"""
        self.ui.graphics_view.mark_points(self.ui.mark_points_action.isChecked())

    @pyqtSlot()
    def on_command_edit_returnPressed(self):
        """输入命令后按下回车"""
        commands = self.ui.command_edit.text().strip()
        if commands != "":
            # 执行命令
            self.execute(commands)
        self.ui.command_edit.setText("")

    def lock_ui(self):
        """锁定 UI"""
        self.ui.tool_bar.setEnabled(False)

    @pyqtSlot()
    def unlock_ui(self):
        """解锁 UI"""
        self.ui.tool_bar.setEnabled(True)
        self.ui.graphics_view.scene().update()

    @pyqtSlot(list)
    def update_child_list(self, polygon_table):
        """更新 children 列表

        Args:
            polygon_table: 多边形表
        """
        self.ui.second_table_widget.fill_with_polygons(polygon_table)

    @pyqtSlot(QTableWidgetItem)
    def polygon_selection_clicked(self, item):
        self.polygon_selection_changed()

    @pyqtSlot()
    def polygon_selection_changed(self):
        """在多边形列表中选择了多边形"""
        _id = self.selected_id()
        if _id >= 0:
            # draw polygon
            polygon = self.db.get_polygon_by_id(_id)
            # list children
            child_list = self.db.get_children_table_by_id(_id)
        else:
            # 选中了非法的多边形
            polygon = None
            child_list = {}
        self.ui.graphics_view.set_selected_polygon(polygon)
        self.update_child_list(child_list)
        return

    @pyqtSlot()
    def second_selection_changed(self):
        """在第二列选中"""
        if self.ui.move_action.isChecked():
            self.ui.graphics_view.select_point(self.ui.second_table_widget.currentRow())
        if self.ui.second_table_widget.is_polygon:
            _id = self.ui.second_table_widget.get_selected_id()
            polygon = self.db.get_polygon_by_id(_id)
            if polygon is not None:
                self.ui.graphics_view.set_selected_polygon(polygon)

    @pyqtSlot()
    def scale_slider_changed(self):
        """修改地图缩放比例"""
        scale = math.exp(self.ui.scale_slider.value() / 10)
        self.ui.graphics_view.resetTransform()
        self.ui.graphics_view.scale(scale, -scale)

    @pyqtSlot(list)
    def add_polygon(self, vertices):
        """插入多边形

        Args:
            vertices (list[list[float]]): 多边形顶点 list, [[x1,y1], [x2,y2], ..., [xn,yn]]
        """
        parent_id = self.selected_id()
        _id = self.command_handler.get_spare_id(parent_id)
        layer = self.ui.insert_layer_combo_box.currentIndex()
        additional = 0
        if layer == 0:
            commands = ["add shape %d %d %s" % (_id, layer, str(additional))]
        else:
            commands = ["add shape %d %d %s %d" % (_id, layer, str(additional), parent_id)]
        for vertex in vertices:
            commands.append("add pt %d %f %f" % (_id, vertex[0], vertex[1]))
        self.execute(commands)

    @pyqtSlot("PyQt_PyObject")
    def goto_polygon(self, _id):
        """视角聚焦到多边形中心

        Args:
            _id (int): 目标多边形 id
        """
        self.ui.graphics_view.center_on_polygon(self.db.get_polygon_by_id(_id))

    @pyqtSlot(list)
    def update_polygon(self, vertices):
        """修改当前选中的多边形的顶点坐标

        Args:
            vertices (list[list[float]]): 多边形顶点 list, [[x1,y1], [x2,y2], ..., [xn,yn]]
        """
        _id = self.selected_id()
        commands = []
        for pt_id in range(0, len(vertices)):
            x = vertices[pt_id][0]
            y = vertices[pt_id][1]
            commands.append("set pt %d %d %f %f" % (_id, pt_id, x, y))
        self.execute(commands)

    @pyqtSlot(list)
    def update_points(self, points):
        """更新第二列显示的点

        编辑模式下,第二列显示当前图形的点的坐标。本方法用于更新第二列的显示。

        Args:
            points (list[QPointF]): 多边形顶点 list, [qpoint1, qpoint2, ..., qpointn]
        """
        row = self.ui.second_table_widget.currentRow()
        self.ui.second_table_widget.fill_with_points(points)
        row_count = self.ui.second_table_widget.rowCount()
        if row_count > 0:
            row = min(row_count - 1, max(0, row))
            self.ui.second_table_widget.setCurrentCell(row, 0)

    @pyqtSlot(str, QColor)
    def print_to_output(self, msg, color):
        """打印到输出窗口

        Args:
            msg (str): 输出内容
            color (QColor): 输出颜色
        """
        print(msg)
        self.ui.output_browser.setTextColor(color)
        self.ui.output_browser.append(msg)

    def execute(self, commands):
        """执行命令

        Args:
            commands (str): 待执行命令
        """
        log.debug(commands)
        try:
            self.command_handler.execute(commands)
            self.update_polygon_list()
        except Exception as e:
            log.error("执行命令出错: %s" % repr(e))
            return False
        else:
            return True

    def update_polygon_list(self):
        """更新多边形列表"""
        polygon_table = self.db.polygon_table
        _id = self.selected_id()
        self.ui.polygon_table_widget.fill_with_polygons(polygon_table)
        self.ui.graphics_view.set_polygons(polygon_table, len(config_loader.get_layer_names()))
        if len(polygon_table) > 0:
            if not self.select_row_by_id(_id):
                self.ui.polygon_table_widget.setCurrentCell(0, 0)

    def open(self, path, quiet=False):
        """打开 sqlite 数据库文件

        Args:
            path (str): 文件路径
            quiet (bool): 报错不弹框
        """
        if os.path.exists(path):
            try:
                self.db.load_tables(path)
                self.command_handler.reset_backup_data()
                self.update_polygon_list()
                self.path = path
                self.fsm_mgr.change_fsm("empty", "normal")
                log.debug('Open "%s".' % path)
            except sqlite3.Error as error:
                log.error('Failed to open "%s".' % path)
                log.error(repr(error))
                if not quiet:
                    self.show_message(repr(error))
        else:
            log.error("File %s not exists." % path)
            if not quiet:
                self.show_message("File %s not exists." % path)

    def save(self, path):
        """保存 sqlite 数据库文件

        Args:
            path (str): 文件路径
        """
        try:
            self.db.write_to_file(path)
            self.command_handler.reset_backup_data()
            log.debug('Save "%s".' % path)
        except sqlite3.Error as error:
            log.error('Failed to save "%s".' % path)
            log.error(repr(error))
            self.show_message(repr(error))

    def selected_id(self):
        """选中的多边形的 id"""
        return self.ui.polygon_table_widget.get_selected_id()

    def select_row_by_id(self, polygon_id):
        """根据多边形的 id 选中行

        Args:
            polygon_id (int): 多边形 id
        """
        return self.ui.polygon_table_widget.select_id(polygon_id)

    def show_message(self, msg, title="L5MapEditor"):
        QMessageBox.information(self, title, msg)

    def dragEnterEvent(self, event):
        if event.mimeData().hasUrls():
            event.accept()
        else:
            event.ignore()

    def dropEvent(self, event):
        url = event.mimeData().urls()[0]
        if url.isLocalFile():
            path = url.toLocalFile()
            if os.path.isfile(path):
                self.open(path)