コード例 #1
0
ファイル: mainwindow.py プロジェクト: light5551/dt-gui-tools
class duck_window(QtWidgets.QMainWindow):
    map = None
    mapviewer = None
    info_json = None
    editor = None
    drawState = ''
    copyBuffer = [[]]

    def __init__(self, args, elem_info="doc/info.json"):
        super().__init__()
        # active items in editor
        self.active_items = []

        #  additional windows for displaying information
        self.author_window = info_window()
        self.param_window = info_window()
        self.mater_window = info_window()

        #  The brush button / override the closeEvent
        self.brush_button = QtWidgets.QToolButton()
        self.closeEvent = functools.partial(self.quit_program_event)

        # Set locale
        self.locale = args.locale

        # Set debug mode
        self.debug_mode = args.debug

        # Load element's info
        self.info_json = json.load(codecs.open(elem_info, "r", "utf-8"))

        # Loads info about types from duckietown
        self.duckietown_types_apriltags = get_duckietown_types()
        self.new_tag_class = NewTagForm(self.duckietown_types_apriltags)

        self.map = map.DuckietownMap()
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        viewer = mapviewer.MapViewer()
        self.editor = MapEditor(self.map, self.mapviewer)
        viewer.setMap(self.map)
        self.mapviewer = viewer
        viewer.setMinimumSize(540, 540)
        self.ui.horizontalLayout.addWidget(viewer)
        viewer.repaint()
        self.initUi()

        init_map(self)
        self.update_layer_tree()

    def get_translation(self, elem):
        """Gets info about the element based on self.locale
        If local doesn't exist, return locale='en'

        :param self
        :param elem: dict, information about elements (or category), that contains translation
        :return: dict(). Dict with translation based on the self.locale
        """
        if self.locale in elem['lang']:
            return elem['lang'][self.locale]
        else:
            logger.debug(
                "duck_window.get_translation. No such locale: {}".format(
                    self.locale))
            return elem['lang']['en']

    def initUi(self):
        self.center()
        self.show()

        #  Initialize button objects
        create_map = self.ui.create_new
        open_map = self.ui.open_map
        save_map = self.ui.save_map
        save_map_as = self.ui.save_map_as
        export_png = self.ui.export_png
        calc_param = self.ui.calc_param
        calc_materials = self.ui.calc_materials
        about_author = self.ui.about_author
        exit = self.ui.exit
        change_blocks = self.ui.change_blocks
        change_info = self.ui.change_info
        change_map = self.ui.change_map
        change_layer = self.ui.change_layer

        #  Initialize floating blocks
        block_widget = self.ui.block_widget
        info_widget = self.ui.info_widget
        map_info_widget = self.ui.map_info_widget
        layer_info_widget = self.ui.layer_info_widget

        #  Signal from viewer
        self.mapviewer.selectionChanged.connect(self.selectionUpdate)
        self.mapviewer.editObjectChanged.connect(self.create_form)
        self.new_tag_class.apriltag_added.connect(self.add_apriltag)

        #  Assign actions to buttons
        create_map.triggered.connect(self.create_map_triggered)
        open_map.triggered.connect(self.open_map_triggered)
        save_map.triggered.connect(self.save_map_triggered)
        save_map_as.triggered.connect(self.save_map_as_triggered)
        export_png.triggered.connect(self.export_png_triggered)
        calc_param.triggered.connect(self.calc_param_triggered)
        calc_materials.triggered.connect(self.calc_materials_triggered)
        about_author.triggered.connect(self.about_author_triggered)
        exit.triggered.connect(self.exit_triggered)

        change_blocks.toggled.connect(self.change_blocks_toggled)
        change_info.toggled.connect(self.change_info_toggled)
        change_map.toggled.connect(self.change_map_toggled)
        change_layer.toggled.connect(self.toggle_layer_window)

        block_widget.closeEvent = functools.partial(self.blocks_event)
        info_widget.closeEvent = functools.partial(self.info_event)
        map_info_widget.closeEvent = functools.partial(self.map_event)
        layer_info_widget.closeEvent = functools.partial(
            self.close_layer_window_event)

        #  QToolBar setting
        tool_bar = self.ui.tool_bar

        a1 = QtWidgets.QAction(QtGui.QIcon("img/icons/new.png"),
                               _translate("MainWindow", "New map"), self)
        a2 = QtWidgets.QAction(QtGui.QIcon("img/icons/open.png"),
                               _translate("MainWindow", "Open map"), self)
        a3 = QtWidgets.QAction(QtGui.QIcon("img/icons/save.png"),
                               _translate("MainWindow", "Save map"), self)
        a4 = QtWidgets.QAction(QtGui.QIcon("img/icons/save_as.png"),
                               _translate("MainWindow", "Save map as"), self)
        a5 = QtWidgets.QAction(QtGui.QIcon("img/icons/png.png"),
                               _translate("MainWindow", "Export to PNG"), self)

        b1 = QtWidgets.QAction(QtGui.QIcon("img/icons/copy.png"),
                               _translate("MainWindow", "Copy"), self)
        b2 = QtWidgets.QAction(QtGui.QIcon("img/icons/cut.png"),
                               _translate("MainWindow", "Cut"), self)
        b3 = QtWidgets.QAction(QtGui.QIcon("img/icons/insert.png"),
                               _translate("MainWindow", "Paste"), self)
        b4 = QtWidgets.QAction(QtGui.QIcon("img/icons/delete.png"),
                               _translate("MainWindow", "Delete"), self)
        b5 = QtWidgets.QAction(QtGui.QIcon("img/icons/undo.png"),
                               _translate("MainWindow", "Undo"), self)
        b1.setShortcut("Ctrl+C")
        b2.setShortcut("Ctrl+X")
        b3.setShortcut("Ctrl+V")
        b4.setShortcut("Delete")
        b5.setShortcut("Ctrl+Z")

        c1 = QtWidgets.QAction(QtGui.QIcon("img/icons/rotate.png"),
                               _translate("MainWindow", "Rotate"), self)
        c2 = QtWidgets.QAction(
            QtGui.QIcon("img/icons/trim.png"),
            _translate("MainWindow", "Delete extreme empty blocks"), self)
        c1.setShortcut("Ctrl+R")
        c2.setShortcut("Ctrl+F")

        self.brush_button.setIcon(QtGui.QIcon("img/icons/brush.png"))
        self.brush_button.setCheckable(True)
        self.brush_button.setToolTip("Brush tool")
        self.brush_button.setShortcut("Ctrl+B")

        a1.triggered.connect(self.create_map_triggered)
        a2.triggered.connect(self.open_map_triggered)
        a3.triggered.connect(self.save_map_triggered)
        a4.triggered.connect(self.save_map_as_triggered)
        a5.triggered.connect(self.export_png_triggered)

        b1.triggered.connect(self.copy_button_clicked)
        b2.triggered.connect(self.cut_button_clicked)
        b3.triggered.connect(self.insert_button_clicked)
        b4.triggered.connect(self.delete_button_clicked)
        b5.triggered.connect(self.undo_button_clicked)

        c1.triggered.connect(self.rotateSelectedTiles)
        c2.triggered.connect(self.trimClicked)

        self.brush_button.clicked.connect(self.brush_mode)

        for elem in [[a1, a2, a3, a4, a5], [b1, b2, b3, b4, b5]]:
            for act in elem:
                tool_bar.addAction(act)
            tool_bar.addSeparator()
        tool_bar.addWidget(self.brush_button)
        tool_bar.addAction(c1)
        tool_bar.addAction(c2)

        # Setup Layer Tree menu
        self.ui.layer_tree.setModel(
            QtGui.QStandardItemModel())  # set item model for tree

        #  Customize the Blocks menu
        block_list_widget = self.ui.block_list
        block_list_widget.itemClicked.connect(self.item_list_clicked)
        block_list_widget.itemDoubleClicked.connect(
            self.item_list_double_clicked)

        #  Customize the Map Editor menu
        default_fill = self.ui.default_fill
        delete_fill = self.ui.delete_fill

        #  Fill out the list
        categories = self.info_json['categories']
        information = self.info_json['info']
        for group in categories:
            # add separator
            icon = "img/icons/galka.png"
            widget = QtWidgets.QListWidgetItem(
                QtGui.QIcon(icon),
                self.get_translation(group)['name'])
            widget.setData(0x0100, "separator")
            widget.setData(0x0101, group['id'])
            widget.setBackground(QtGui.QColor(169, 169, 169))
            block_list_widget.addItem(widget)
            # add elements
            for elem_id in group['elements']:
                widget = QtWidgets.QListWidgetItem(
                    QtGui.QIcon(information[elem_id]['icon']),
                    self.get_translation(information[elem_id])['name'])
                widget.setData(0x0100, elem_id)
                widget.setData(0x0101, group['id'])
                block_list_widget.addItem(widget)
                # add tiles to fill menu
                if group['id'] in ("road", "block"):
                    default_fill.addItem(
                        QtGui.QIcon(information[elem_id]['icon']),
                        self.get_translation(information[elem_id])['name'],
                        elem_id)
                    delete_fill.addItem(
                        QtGui.QIcon(information[elem_id]['icon']),
                        self.get_translation(information[elem_id])['name'],
                        elem_id)

        default_fill.setCurrentText(
            self.get_translation(information["grass"])['name'])
        delete_fill.setCurrentText(
            self.get_translation(information["empty"])['name'])

        set_fill = self.ui.set_fill
        set_fill.clicked.connect(self.set_default_fill)

    def center(self):
        qr = self.frameGeometry()
        cp = QDesktopWidget().availableGeometry().center()
        qr.moveCenter(cp)
        self.move(qr.topLeft())

    #  Create a new map
    def create_map_triggered(self):
        new_map(self)
        logger.debug("Length - {}".format(len(self.map.get_tile_layer().data)))
        self.mapviewer.offsetX = self.mapviewer.offsetY = 0
        self.mapviewer.scene().update()
        logger.debug("Creating a new map")
        self.update_layer_tree()

    #  Open map
    def open_map_triggered(self):
        self.editor.save(self.map)
        open_map(self)
        self.mapviewer.offsetX = self.mapviewer.offsetY = 0
        self.mapviewer.scene().update()
        self.update_layer_tree()

    #  Save map
    def save_map_triggered(self):
        save_map(self)
        logger.debug("Save")

    #  Save map as
    def save_map_as_triggered(self):
        save_map_as(self)

    #  Export to png
    def export_png_triggered(self):
        export_png(self)

    #  Calculate map characteristics
    def calc_param_triggered(self):

        text = get_map_specifications(self)
        self.show_info(self.param_window,
                       _translate("MainWindow", "Map characteristics"), text)

    #  Calculate map materials
    def calc_materials_triggered(self):
        text = get_map_materials(self)
        self.show_info(self.mater_window,
                       _translate("MainWindow", "Map material"), text)

    #  Help: About
    def about_author_triggered(self):
        text = '''
        - Select an object using the left mouse button\n
        - when object is selected you can change pos, using WASD: W(UP), A(LEFT), D(RIGHT), S(DOWN)\n
        - reset object tracking using `Q`\n
        - add apriltag using key `R`\n
        - Edit an object, click on it using the right mouse button\n
        - Authors:\n alskaa;\n dihindee;\n ovc-serega;\n HadronCollider;\n light5551;\n snush.\n\n Contact us on github!
        '''
        self.show_info(self.author_window, "About", text)

    #  Exit
    def exit_triggered(self):
        self.save_before_exit()
        QtCore.QCoreApplication.instance().quit()

    # Save map before exit
    def save_before_exit(self):
        if not self.debug_mode:
            ret = self.quit_MessageBox()
            if ret == QMessageBox.Cancel:
                return
            if ret == QMessageBox.Save:
                save_map(self)

    #  Hide Block menu
    def change_blocks_toggled(self):
        block = self.ui.block_widget
        if self.ui.change_blocks.isChecked():
            block.show()
            block.setFloating(False)
        else:
            block.close()

    #  Change button state
    def blocks_event(self, event):
        self.ui.change_blocks.setChecked(False)
        event.accept()

    #  Hide information menu
    def change_info_toggled(self):
        block = self.ui.info_widget
        if self.ui.change_info.isChecked():
            block.show()
            block.setFloating(False)
        else:
            block.close()

    #  Change button state
    def info_event(self, event):
        self.ui.change_info.setChecked(False)
        event.accept()

    #  Hide the menu about map properties
    def change_map_toggled(self):
        block = self.ui.map_info_widget
        if self.ui.change_map.isChecked():
            block.show()
            block.setFloating(False)
        else:
            block.close()

    #  Change button state
    def map_event(self, event):
        self.ui.change_map.setChecked(False)
        event.accept()

    # Layer window

    def toggle_layer_window(self):
        """
        Toggle layers window by `View -> Layers`
        :return: -
        """
        block = self.ui.layer_info_widget
        if self.ui.change_layer.isChecked():
            block.show()
            block.setFloating(False)
        else:
            block.close()

    def close_layer_window_event(self, event):
        """
        Reset flag `View -> Layers` when closing layers window
        :param event: closeEvent
        :return: -
        """
        self.ui.change_layer.setChecked(False)
        event.accept()

    def layer_tree_clicked(self):
        pass

    def layer_tree_double_clicked(self):
        pass

    def update_layer_tree(self):
        """
        Update layer tree.
        Show layer's elements as children in hierarchy (except tile layer)
        :return: -
        """
        def signal_check_state(item):
            """
            update visible state of layer.
            :return: -
            """
            layer = self.map.get_layer_by_type_name(item.text())
            if not layer:
                logger.debug("Not found layer: {}".format(item.text()))
                return

            layer.visible = not layer.visible
            logger.debug('Layer: {}; visible: {}'.format(
                item.text(), layer.visible))
            self.map.set_layer(layer)
            self.mapviewer.scene().update()

        layer_tree_view = self.ui.layer_tree
        item_model = layer_tree_view.model()
        item_model.clear()
        try:
            item_model.itemChanged.disconnect()
        except TypeError:
            pass  # only 1st time in update_layer_tree
        item_model.itemChanged.connect(signal_check_state)
        item_model.setHorizontalHeaderLabels(['Name'])
        root_item = layer_tree_view.model().invisibleRootItem()
        for layer in self.map.layers:
            layer_item = QtGui.QStandardItem(str(layer.type))
            layer_item.setCheckable(True)
            layer_item.setCheckState(
                QtCore.Qt.Checked if layer.visible else QtCore.Qt.Unchecked)
            root_item.appendRow(layer_item)
            if layer.type == LayerType.TILES:
                tile_elements = []
                for row in layer.data:
                    for tile in row:
                        tile_elements.append(tile.kind)
                layer_elements = utils.count_elements(tile_elements)
            elif layer.type in (LayerType.TRAFFIC_SIGNS,
                                LayerType.GROUND_APRILTAG):
                layer_elements = utils.count_elements([
                    '{}{}'.format(elem.kind, elem.tag_id)
                    for elem in layer.data
                ])
            else:
                layer_elements = utils.count_elements(
                    [elem.kind for elem in layer.data])
            for kind, counter in layer_elements.most_common():
                item = QtGui.QStandardItem("{} ({})".format(kind, counter))
                layer_item.appendRow(item)
            layer_item.sortChildren(0)
        layer_tree_view.expandAll()

    #  MessageBox to exit
    def quit_MessageBox(self):
        reply = QMessageBox(self)
        reply.setIcon(QMessageBox.Question)
        reply.setWindowTitle(_translate("MainWindow", "Exit"))
        reply.setText(_translate("MainWindow", "Exit"))
        reply.setInformativeText(_translate("MainWindow", "Save and exit?"))
        reply.setStandardButtons(QMessageBox.Save | QMessageBox.Discard
                                 | QMessageBox.Cancel)
        reply.setDefaultButton(QMessageBox.Save)
        ret = reply.exec()
        return ret

    #  Program exit event
    def quit_program_event(self, event):
        self.save_before_exit()

        #  Close additional dialog boxes
        self.author_window.exit()
        self.param_window.exit()
        self.mater_window.exit()

        event.accept()

    #  Handle a click on an item from a list to a list
    def item_list_clicked(self):
        list = self.ui.block_list
        name = list.currentItem().data(0x0100)
        type = list.currentItem().data(0x0101)

        if name == "separator":
            list.currentItem().setSelected(False)

            for i in range(list.count()):
                elem = list.item(i)
                if type == elem.data(0x0101):
                    if name == elem.data(0x0100):
                        icon = QtGui.QIcon("img/icons/galka.png") if list.item(i + 1).isHidden() else \
                            QtGui.QIcon("img/icons/galka_r.png")
                        elem.setIcon(icon)
                    else:
                        elem.setHidden(not elem.isHidden())
        else:
            elem = self.info_json['info'][name]
            info_browser = self.ui.info_browser
            info_browser.clear()
            text = "{}:\n {}\n{}:\n{}".format(
                _translate("MainWindow", "Name"),
                list.currentItem().text(),
                _translate("MainWindow", "Description"),
                self.get_translation(elem)['info'])
            if elem["type"] == "block":
                text += "\n\n{}: {} {}".format(
                    _translate("MainWindow", "Road len"), elem["length"],
                    _translate("MainWindow", "sm"))
                text += " Tape:\n"
                text += " {}: {} {}\n".format(_translate("MainWindow", "Red"),
                                              elem["red"],
                                              _translate("MainWindow", "sm"))
                text += " {}: {} {}\n".format(
                    _translate("MainWindow", "Yellow"), elem["yellow"],
                    _translate("MainWindow", "sm"))
                text += " {}: {} {}\n".format(
                    _translate("MainWindow", "White"), elem["white"],
                    _translate("MainWindow", "sm"))
            info_browser.setText(text)

    #  Double click initiates as single click action
    def item_list_double_clicked(self):
        item_ui_list = self.ui.block_list
        item_name = item_ui_list.currentItem().data(0x0100)
        item_type = item_ui_list.currentItem().data(0x0101)

        if item_name == "separator":
            item_ui_list.currentItem().setSelected(False)
        else:
            if item_type in TILE_TYPES:
                self.ui.default_fill.setCurrentText(
                    self.get_translation(
                        self.info_json['info'][item_name])['name'])
                logger.debug("Set {} for brush".format(item_name))
            else:
                # save map before adding object
                self.editor.save(self.map)
                # adding object
                self.map.add_objects_to_map([
                    dict(kind=item_name,
                         pos=(.0, .0),
                         rotate=0,
                         height=1,
                         optional=False,
                         static=True)
                ], self.info_json['info'])
                # TODO: need to understand what's the type and create desired class, not general
                # also https://github.com/moevm/mse_visual_map_editor_for_duckietown/issues/122
                # (for args, that can be edited and be different between classes)
                self.mapviewer.scene().update()
                logger.debug("Add {} to map".format(item_name))
            self.update_layer_tree()

    #  Reset to default values
    def set_default_fill(self):
        default_fill = self.ui.default_fill.currentData()
        delete_fill = self.ui.delete_fill.currentData()
        # TODO установка занчений по умолчанию
        logger.debug("{}; {}".format(default_fill, delete_fill))

    #  Copy
    def copy_button_clicked(self):
        if self.brush_button.isChecked():
            self.brush_button.click()
        self.drawState = 'copy'
        self.copyBuffer = copy.copy(self.mapviewer.tileSelection)
        logger.debug("Copy")

    #  Cut
    def cut_button_clicked(self):
        if self.brush_button.isChecked():
            self.brush_button.click()
        self.drawState = 'cut'
        self.copyBuffer = copy.copy(self.mapviewer.tileSelection)
        logger.debug("Cut")

    #  Paste
    def insert_button_clicked(self):
        if len(self.copyBuffer) == 0:
            return
        self.editor.save(self.map)
        if self.drawState == 'copy':
            self.editor.copySelection(
                self.copyBuffer, self.mapviewer.tileSelection[0],
                self.mapviewer.tileSelection[1],
                MapTile(self.ui.delete_fill.currentData()))
        elif self.drawState == 'cut':
            self.editor.moveSelection(
                self.copyBuffer, self.mapviewer.tileSelection[0],
                self.mapviewer.tileSelection[1],
                MapTile(self.ui.delete_fill.currentData()))
        self.mapviewer.scene().update()
        self.update_layer_tree()

    #  Delete
    def delete_button_clicked(self):
        if not self.map.get_tile_layer().visible:
            return
        self.editor.save(self.map)
        self.editor.deleteSelection(self.mapviewer.tileSelection,
                                    MapTile(self.ui.delete_fill.currentData()))
        self.mapviewer.scene().update()
        self.update_layer_tree()

    #  Undo
    def undo_button_clicked(self):
        self.editor.undo()
        self.mapviewer.scene().update()
        self.update_layer_tree()

    #  Brush mode
    def brush_mode(self):
        if self.brush_button.isChecked():
            self.drawState = 'brush'
        else:
            self.drawState = ''

    def keyPressEvent(self, e):
        selection = self.mapviewer.raw_selection
        item_layer = self.map.get_objects_from_layers(
        )  # TODO: add self.current_layer for editing only it's objects?
        new_selected_obj = False
        for item in item_layer:
            x, y = item.position
            if x > selection[0] and x < selection[2] and y > selection[
                    1] and y < selection[3]:
                if item not in self.active_items:
                    self.active_items.append(item)
                    new_selected_obj = True
        if new_selected_obj:
            # save map if new objects are selected
            self.editor.save(self.map)
        key = e.key()
        if key == QtCore.Qt.Key_Q:
            # clear object buffer
            self.active_items = []
            self.mapviewer.raw_selection = [0] * 4
        elif key == QtCore.Qt.Key_R:
            self.new_tag_class.create_form()
        if self.active_items:
            if key == QtCore.Qt.Key_Backspace:
                # delete object
                if question_form_yes_no(
                        self, "Deleting objects",
                        "Delete objects from map?") == QMessageBox.Yes:
                    # save map before deleting objects
                    self.editor.save(self.map)
                    for item in self.active_items:
                        object_type = self.info_json['info'][item.kind]['type']
                        layer = self.map.get_layer_by_type(
                            get_layer_type_by_object_type(object_type))
                        layer.remove_object_from_layer(item)
                    self.active_items = []
                    self.mapviewer.scene().update()
                    self.update_layer_tree()
                return
            for item in self.active_items:
                logger.debug("Name of item: {}; X - {}; Y - {};".format(
                    item.kind, item.position[0], item.position[1]))
                if key == QtCore.Qt.Key_W:
                    item.position[1] -= EPS
                elif key == QtCore.Qt.Key_S:
                    item.position[1] += EPS
                elif key == QtCore.Qt.Key_A:
                    item.position[0] -= EPS
                elif key == QtCore.Qt.Key_D:
                    item.position[0] += EPS
                elif key == QtCore.Qt.Key_E:
                    if len(self.active_items) == 1:
                        self.create_form(self.active_items[0])
                    else:
                        logger.debug("I can't edit more than one object!")
        self.mapviewer.scene().update()

    def create_form(self, active_object: MapObject):
        def accept():
            if 'tag_type' in edit_obj:
                tag_type = edit_obj['tag_type'].text()
                if 'tag_id' in edit_obj and (
                        not edit_obj['tag_id'].text().isdigit()
                        or int(edit_obj['tag_id'].text())
                        not in self.duckietown_types_apriltags[tag_type]):
                    msgBox = QMessageBox()
                    msgBox.setText("tag id or tag type is uncorrect!")
                    msgBox.exec()
                    return
                if tag_type not in self.duckietown_types_apriltags.keys(
                ) or int(edit_obj['tag_id'].text()
                         ) not in self.duckietown_types_apriltags[tag_type]:
                    msgBox = QMessageBox()
                    msgBox.setText("tag id or tag type is uncorrect!")
                    msgBox.exec()
                    return
            elif 'tag_id' in edit_obj:
                if not edit_obj['tag_id'].text().isdigit() or not int(
                        edit_obj['tag_id'].text(
                        )) in self.duckietown_types_apriltags['TrafficSign']:
                    msgBox = QMessageBox()
                    msgBox.setText("tag id is uncorrect!")
                    msgBox.exec()
                    return
            for attr_name, attr in editable_attrs.items():
                if attr_name == 'pos':
                    active_object.position[0] = float(edit_obj['x'].text())
                    active_object.position[1] = float(edit_obj['y'].text())
                    continue
                if type(attr) == bool:
                    active_object.__setattr__(attr_name,
                                              edit_obj[attr_name].isChecked())
                    continue
                if type(attr) == float:
                    active_object.__setattr__(
                        attr_name, float(edit_obj[attr_name].text()))
                if type(attr) == int:
                    active_object.__setattr__(attr_name,
                                              int(edit_obj[attr_name].text()))
                else:
                    active_object.__setattr__(attr_name,
                                              edit_obj[attr_name].text())
            dialog.close()
            self.mapviewer.scene().update()
            self.update_layer_tree()

        def reject():
            dialog.close()

        dialog = QtWidgets.QDialog(self)
        dialog.setWindowTitle('Change attribute of object')
        # buttonbox
        buttonBox = QtWidgets.QDialogButtonBox(
            QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel)
        buttonBox.accepted.connect(accept)
        buttonBox.rejected.connect(reject)
        # form
        formGroupBox = QGroupBox("Change attribute's object: {}".format(
            active_object.kind))

        layout = QFormLayout()
        editable_attrs = active_object.get_editable_attrs()
        edit_obj = {}
        combo_id = QComboBox(self)

        def change_combo_id(value):
            combo_id.clear()
            combo_id.addItems(
                [str(i) for i in self.duckietown_types_apriltags[value]])
            combo_id.setEditText(str(
                self.duckietown_types_apriltags[value][0]))

        for attr_name in sorted(editable_attrs):
            attr = editable_attrs[attr_name]
            if attr_name == 'pos':
                x_edit = QLineEdit(str(attr[0]))
                y_edit = QLineEdit(str(attr[1]))
                edit_obj['x'] = x_edit
                edit_obj['y'] = y_edit
                layout.addRow(QLabel("{}.X".format(attr_name)), x_edit)
                layout.addRow(QLabel("{}.Y".format(attr_name)), y_edit)
                continue
            elif attr_name == 'tag_id':
                edit = QLineEdit(str(attr))
                edit_obj[attr_name] = edit
                tag_id = int(attr)
                type_id = list(self.duckietown_types_apriltags.keys())[0]
                for type_sign in self.duckietown_types_apriltags.keys():
                    if tag_id in self.duckietown_types_apriltags[type_sign]:
                        type_id = type_sign
                        break

                combo_id.addItems(
                    [str(i) for i in self.duckietown_types_apriltags[type_id]])
                combo_id.setLineEdit(edit)
                combo_id.setEditText(str(attr))
                layout.addRow(QLabel(attr_name), combo_id)
                continue
            elif attr_name == 'tag_type':
                edit = QLineEdit(str(attr))
                edit_obj[attr_name] = edit
                combo_type = QComboBox(self)
                combo_type.addItems(
                    [str(i) for i in self.duckietown_types_apriltags.keys()])
                combo_type.activated[str].connect(change_combo_id)
                combo_type.setLineEdit(edit)
                combo_type.setEditText(attr)
                layout.addRow(QLabel(attr_name), combo_type)
                continue
            if type(attr) == bool:
                check = QCheckBox()
                check.setChecked(attr)
                edit_obj[attr_name] = check
                layout.addRow(QLabel(attr_name), check)
                continue
            edit = QLineEdit(str(attr))
            edit_obj[attr_name] = edit
            layout.addRow(QLabel(attr_name), edit)

        formGroupBox.setLayout(layout)
        # layout
        mainLayout = QVBoxLayout()
        mainLayout.addWidget(formGroupBox)
        mainLayout.addWidget(buttonBox)
        dialog.setLayout(mainLayout)
        dialog.exec_()

    def rotateSelectedTiles(self):
        self.editor.save(self.map)
        selection = self.mapviewer.tileSelection
        tile_layer = self.map.get_tile_layer().data
        if selection:
            for i in range(max(selection[1], 0),
                           min(selection[3], len(tile_layer))):
                for j in range(max(selection[0], 0),
                               min(selection[2], len(tile_layer[0]))):
                    tile_layer[i][j].rotation = (tile_layer[i][j].rotation +
                                                 90) % 360
            self.mapviewer.scene().update()

    def add_apriltag(self, apriltag: GroundAprilTagObject):
        layer = self.map.get_layer_by_type(LayerType.GROUND_APRILTAG)
        if layer is None:
            self.map.add_layer_from_data(LayerType.GROUND_APRILTAG, [apriltag])
        else:
            self.map.add_elem_to_layer_by_type(LayerType.GROUND_APRILTAG,
                                               apriltag)
        self.update_layer_tree()
        self.mapviewer.scene().update()

    def trimClicked(self):
        self.editor.save(self.map)
        self.editor.trimBorders(True, True, True, True,
                                MapTile(self.ui.delete_fill.currentData()))
        self.mapviewer.scene().update()
        self.update_layer_tree()

    def selectionUpdate(self):
        selection = self.mapviewer.tileSelection
        filler = MapTile(self.ui.default_fill.currentData())
        tile_layer = self.map.get_tile_layer().data
        if self.drawState == 'brush':
            self.editor.save(self.map)
            self.editor.extendToFit(selection, selection[0], selection[1],
                                    MapTile(self.ui.delete_fill.currentData()))
            if selection[0] < 0:
                delta = -selection[0]
                selection[0] = 0
                selection[2] += delta
            if selection[1] < 0:
                delta = -selection[1]
                selection[3] += delta
            for i in range(max(selection[0], 0),
                           min(selection[2], len(tile_layer[0]))):
                for j in range(max(selection[1], 0),
                               min(selection[3], len(tile_layer))):
                    tile_layer[j][i] = copy.copy(filler)
        self.update_layer_tree()
        self.mapviewer.scene().update()

    # функция создания доп. информационного окна
    def show_info(self, name, title, text):
        name.set_window_name(title)
        name.set_text(text)
        name.show()
コード例 #2
0
class duck_window(QtWidgets.QMainWindow):
    map = None
    mapviewer = None
    info_json = None
    editor = None
    drawState = ''
    copyBuffer = [[]]

    def __init__(self):
        super().__init__()
        # доп. окна для вывода информации
        self.author_window = info_window()
        self.param_window = info_window()
        self.mater_window = info_window()

        # кнопка для кисти и переопределение события закрытия
        self.brush_button = QtWidgets.QToolButton()
        self.closeEvent = functools.partial(self.quit_program_event)

        self.map = map.DuckietownMap()
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        viewer = mapviewer.MapViewer()
        self.editor = MapEditor(self.map, self.mapviewer)
        viewer.setMap(self.map)
        self.mapviewer = viewer
        viewer.setMinimumSize(540, 540)
        self.ui.horizontalLayout.addWidget(viewer)
        viewer.repaint()
        self.initUi()

        read_file = codecs.open("doc/info.json", "r", "utf-8")
        self.info_json = json.load(read_file)

        init_map(self)

    def initUi(self):
        self.center()
        self.show()

        # инициализация объектов кнопок
        create_map = self.ui.create_new
        open_map = self.ui.open_map
        save_map = self.ui.save_map
        save_map_as = self.ui.save_map_as
        export_png = self.ui.export_png
        calc_param = self.ui.calc_param
        calc_materials = self.ui.calc_materials
        about_author = self.ui.about_author
        exit = self.ui.exit
        change_blocks = self.ui.change_blocks
        change_info = self.ui.change_info
        change_map = self.ui.change_map

        # инициализация плавающих блоков
        block_widget = self.ui.block_widget
        info_widget = self.ui.info_widget
        map_info_widget = self.ui.map_info_widget

        # сигнал от viewer'а
        self.mapviewer.selectionChanged.connect(self.selectionUpdate)

        # подключение действий к кнопкам
        create_map.triggered.connect(self.create_map_triggered)
        open_map.triggered.connect(self.open_map_triggered)
        save_map.triggered.connect(self.save_map_triggered)
        save_map_as.triggered.connect(self.save_map_as_triggered)
        export_png.triggered.connect(self.export_png_triggered)
        calc_param.triggered.connect(self.calc_param_triggered)
        calc_materials.triggered.connect(self.calc_materials_triggered)
        about_author.triggered.connect(self.about_author_triggered)
        exit.triggered.connect(self.exit_triggered)

        change_blocks.toggled.connect(self.change_blocks_toggled)
        change_info.toggled.connect(self.change_info_toggled)
        change_map.toggled.connect(self.change_map_toggled)

        block_widget.closeEvent = functools.partial(self.blocks_event)
        info_widget.closeEvent = functools.partial(self.info_event)
        map_info_widget.closeEvent = functools.partial(self.map_event)

        # настройка QToolBar
        tool_bar = self.ui.tool_bar

        a1 = QtWidgets.QAction(QtGui.QIcon("img/icons/new.png"), 'Новая карта',
                               self)
        a2 = QtWidgets.QAction(QtGui.QIcon("img/icons/open.png"),
                               'Открыть карту', self)
        a3 = QtWidgets.QAction(QtGui.QIcon("img/icons/save.png"),
                               'Сохранить карту', self)
        a4 = QtWidgets.QAction(QtGui.QIcon("img/icons/save_as.png"),
                               'Сохранить карту как', self)
        a5 = QtWidgets.QAction(QtGui.QIcon("img/icons/png.png"),
                               'Экспортировать в png', self)

        b1 = QtWidgets.QAction(QtGui.QIcon("img/icons/copy.png"), 'Копировать',
                               self)
        b2 = QtWidgets.QAction(QtGui.QIcon("img/icons/cut.png"), 'Вырезать',
                               self)
        b3 = QtWidgets.QAction(QtGui.QIcon("img/icons/insert.png"), 'Вставить',
                               self)
        b4 = QtWidgets.QAction(QtGui.QIcon("img/icons/delete.png"), 'Удалить',
                               self)
        b5 = QtWidgets.QAction(QtGui.QIcon("img/icons/undo.png"),
                               'Откатить изменение', self)
        b1.setShortcut("Ctrl+C")
        b2.setShortcut("Ctrl+X")
        b3.setShortcut("Ctrl+V")
        b4.setShortcut("Delete")
        b5.setShortcut("Ctrl+Z")

        c1 = QtWidgets.QAction(QtGui.QIcon("img/icons/rotate.png"),
                               'Повернуть', self)
        c2 = QtWidgets.QAction(QtGui.QIcon("img/icons/trim.png"),
                               'Обрезать крайние блоки', self)
        c1.setShortcut("Ctrl+R")
        c2.setShortcut("Ctrl+F")

        self.brush_button.setIcon(QtGui.QIcon("img/icons/brush.png"))
        self.brush_button.setCheckable(True)
        self.brush_button.setToolTip("Режим кисти")
        self.brush_button.setShortcut("Ctrl+B")

        a1.triggered.connect(self.create_map_triggered)
        a2.triggered.connect(self.open_map_triggered)
        a3.triggered.connect(self.save_map_triggered)
        a4.triggered.connect(self.save_map_as_triggered)
        a5.triggered.connect(self.export_png_triggered)

        b1.triggered.connect(self.copy_button_clicked)
        b2.triggered.connect(self.cut_button_clicked)
        b3.triggered.connect(self.insert_button_clicked)
        b4.triggered.connect(self.delete_button_clicked)
        b5.triggered.connect(self.undo_button_clicked)

        c1.triggered.connect(self.rotateSelectedTiles)
        c2.triggered.connect(self.trimClicked)

        self.brush_button.clicked.connect(self.brush_mode)

        for elem in [[a1, a2, a3, a4, a5], [b1, b2, b3, b4, b5]]:
            for act in elem:
                tool_bar.addAction(act)
            tool_bar.addSeparator()
        tool_bar.addWidget(self.brush_button)
        tool_bar.addAction(c1)
        tool_bar.addAction(c2)

        # Настройка меню Блоки
        block_list_widget = self.ui.block_list
        block_list_widget.itemClicked.connect(self.item_list_clicked)
        block_list_widget.itemDoubleClicked.connect(
            self.item_list_double_clicked)

        # Заполнение списка
        blocks_list = [
            ("Куски дороги", "separator", "road", "img/icons/galka.png"),
            ("Дорога", "straight", "road", "img/tiles/straight.png"),
            ("Левый поворот", "curve_left", "road",
             "img/tiles/curve_left.png"),
            ("Правый поворот", "curve_right", "road",
             "img/tiles/curve_right.png"),
            ("T-образный левый перекрёсток", "3way_left", "road",
             "img/tiles/three_way_left.png"),
            ("T-образный правый перекрёсток", "3way_right", "road",
             "img/tiles/three_way_left.png"),
            ("Перекрёсток", "4way", "road", "img/tiles/four_way_center.png"),
            ("Блоки заполнения", "separator", "block", "img/icons/galka.png"),
            ("Пустой блок", "empty", "block", "img/tiles/empty.png"),
            ("Асфальт", "asphalt", "block", "img/tiles/asphalt.png"),
            ("Трава", "grass", "block", "img/tiles/grass.png"),
            ("Плитка", "floor", "block", "img/tiles/floor.png")
        ]

        signs_list = [
            ("Запрещающие знаки", "separator", "ban", "img/icons/galka.png"),
            ("Стоп", "sign_stop", "ban", "img/signs/sign_stop.png"),
            ("Уступи дорогу", "sign_yield", "ban", "img/signs/sign_yield.png"),
            ("Поворот направо запрещён", "sign_no_right_turn", "ban",
             "img/signs/sign_no_right_turn.png"),
            ("Поворот налево запрещён", "sign_no_left_turn", "ban",
             "img/signs/sign_no_left_turn.png"),
            ("Кирпич", "sign_do_not_enter", "ban",
             "img/signs/sign_do_not_enter.png"),
            ("Информационные знаки", "separator", "info",
             "img/icons/galka.png"),
            ("Односторонее движении направо", "sign_oneway_right", "info",
             "img/signs/sign_oneway_right.png"),
            ("Односторонее движении налево", "sign_oneway_left", "info",
             "img/signs/sign_oneway_left.png"),
            ("Перекрёсток", "sign_4_way_intersect", "info",
             "img/signs/sign_4_way_intersect.png"),
            ("T-образный правый перекрёсток", "sign_right_T_intersect", "info",
             "img/signs/sign_right_T_intersect.png"),
            ("T-образный левый перекрёсток", "sign_left_T_intersect", "info",
             "img/signs/sign_left_T_intersect.png"),
            ("T-образный перекрёсток", "sign_T_intersection", "info",
             "img/signs/sign_T_intersection.png"),
            ("Специальные знаки", "separator", "spec", "img/icons/galka.png"),
            ("Пешеход", "sign_pedestrian", "spec",
             "img/signs/sign_pedestrian.png"),
            ("Светофор", "sign_t_light_ahead", "spec",
             "img/signs/sign_t_light_ahead.png"),
            ("Уточки", "sign_duck_crossing", "spec",
             "img/signs/sign_duck_crossing.png"),
            ("Парковка", "sign_parking", "spec", "img/signs/sign_parking.png")
        ]

        object_list = [
            ("Городские объекты", "separator", "objects",
             "img/icons/galka.png"),
            ("Светофор", "trafficlight", "objects",
             "img/objects/trafficlight.png"),
            ("Барьер", "barrier", "objects", "img/objects/barrier.png"),
            ("Конус", "cone", "objects", "img/objects/cone.png"),
            ("Уточка", "duckie", "objects", "img/objects/duckie.png"),
            ("Уточка-бот", "duckiebot", "objects",
             "img/objects/duckiebot.png"),
            ("Дерево", "tree", "objects", "img/objects/tree.png"),
            ("Дом", "house", "objects", "img/objects/house.png"),
            ("Грузовик(в стиле доставки)", "truck", "objects",
             "img/objects/truck.png"),
            ("Автобус", "bus", "objects", "img/objects/bus.png"),
            ("Здание(многоэтажное)", "building", "objects",
             "img/objects/building.png"),
        ]

        for elem in [blocks_list, signs_list, object_list]:
            for name, data, categ, icon in elem:
                widget = QtWidgets.QListWidgetItem(QtGui.QIcon(icon), name)
                widget.setData(0x0100, data)
                widget.setData(0x0101, categ)
                if data == "separator":
                    widget.setBackground(QtGui.QColor(169, 169, 169))
                block_list_widget.addItem(widget)

        # Настройка меню Редактор карты
        default_fill = self.ui.default_fill
        delete_fill = self.ui.delete_fill
        for name, data, categ, icon in blocks_list:
            if data != "separator":
                default_fill.addItem(QtGui.QIcon(icon), name, data)
                delete_fill.addItem(QtGui.QIcon(icon), name, data)

        default_fill.setCurrentText("Трава")
        delete_fill.setCurrentText("Пустой блок")

        set_fill = self.ui.set_fill
        set_fill.clicked.connect(self.set_default_fill)

    def center(self):
        qr = self.frameGeometry()
        cp = QDesktopWidget().availableGeometry().center()
        qr.moveCenter(cp)
        self.move(qr.topLeft())

    # Действие по созданию новой карты
    def create_map_triggered(self):
        new_map(self)
        print(self.map.tiles, self.map.items)
        self.mapviewer.offsetX = self.mapviewer.offsetY = 0
        self.mapviewer.scene().update()
        print("создание новой карты")

    # Действия по открытию карты
    def open_map_triggered(self):
        self.editor.save(self.map)
        open_map(self)
        self.mapviewer.offsetX = self.mapviewer.offsetY = 0
        self.mapviewer.scene().update()

    # Сохранение карты
    def save_map_triggered(self):
        save_map(self)

    # Сохранение карт с новым именем
    def save_map_as_triggered(self):
        save_map_as(self)

    # Экспорт в png
    def export_png_triggered(self):
        export_png(self)

    # Подсчёт характеристик карт
    def calc_param_triggered(self):

        text = get_map_specifications(self)
        self.show_info(self.param_window, "Характеристики карты", text)

    # Расчёт требуемых материалов
    def calc_materials_triggered(self):
        text = get_map_materials(self)
        self.show_info(self.mater_window, "Необходимые материалы", text)

    # Вывод справки по работе с программой
    def about_author_triggered(self):
        text = "Авторы:\n alskaa;\n dihindee; \n ovc-serega.\n\n Ищите нас на github!"
        self.show_info(self.author_window, "Об авторах", text)

    # Выход из программы
    def exit_triggered(self):
        ret = self.quit_MessageBox()
        if ret == QMessageBox.Cancel:
            return
        if ret == QMessageBox.Save:
            save_map(self)
        self.info.close()
        QtCore.QCoreApplication.instance().quit()

    # скрытие меню о блоках
    def change_blocks_toggled(self):
        block = self.ui.block_widget
        if self.ui.change_blocks.isChecked():
            block.show()
            block.setFloating(False)
        else:
            block.close()

    # изменение состояния кнопки при закрытии
    def blocks_event(self, event):
        self.ui.change_blocks.setChecked(False)
        event.accept()

    # скрытие меню информации
    def change_info_toggled(self):
        block = self.ui.info_widget
        if self.ui.change_info.isChecked():
            block.show()
            block.setFloating(False)
        else:
            block.close()

    # изменение состояния кнопки при закрытии
    def info_event(self, event):
        self.ui.change_info.setChecked(False)
        event.accept()

    # скрытие меню о свойствах карты
    def change_map_toggled(self):
        block = self.ui.map_info_widget
        if self.ui.change_map.isChecked():
            block.show()
            block.setFloating(False)
        else:
            block.close()

    # изменение состояния кнопки при закрытии
    def map_event(self, event):
        self.ui.change_map.setChecked(False)
        event.accept()

    # MessageBox для выхода
    def quit_MessageBox(self):
        reply = QMessageBox(self)
        reply.setIcon(QMessageBox.Question)
        reply.setWindowTitle("Выход")
        reply.setText("Выход из программы")
        reply.setInformativeText("Выйти и сохранить?")
        reply.setStandardButtons(QMessageBox.Save | QMessageBox.Discard
                                 | QMessageBox.Cancel)
        reply.setDefaultButton(QMessageBox.Save)
        ret = reply.exec()
        return ret

    # Событие выхода из программы
    def quit_program_event(self, event):
        ret = self.quit_MessageBox()
        if ret == QMessageBox.Cancel:
            event.ignore()
            return
        if ret == QMessageBox.Save:
            save_map(self)

        # закрытие доп. диалоговых окон
        self.author_window.exit()
        self.param_window.exit()
        self.mater_window.exit()

        event.accept()

    # обработка клика по элементу из списка списку
    def item_list_clicked(self):
        list = self.ui.block_list
        name = list.currentItem().data(0x0100)
        type = list.currentItem().data(0x0101)

        if name == "separator":
            list.currentItem().setSelected(False)

            for i in range(list.count()):
                elem = list.item(i)
                if type == elem.data(0x0101):
                    if name == elem.data(0x0100):
                        icon = QtGui.QIcon("img/icons/galka.png") if list.item(i + 1).isHidden() else \
                            QtGui.QIcon("img/icons/galka_r.png")
                        elem.setIcon(icon)
                    else:
                        elem.setHidden(not elem.isHidden())
        else:
            elem = self.info_json[name]
            info_browser = self.ui.info_browser
            info_browser.clear()
            text = "Название:\n " + list.currentItem().text() \
                   + "\n\nОписание:\n " + elem["info"]
            if elem["type"] == "block":
                text += "\n\nДлина дороги: " + str(elem["length"]) + " см\n"
                text += "\nИзолента:\n"
                text += " Красная: " + str(elem["red"]) + " см\n"
                text += " Желтая: " + str(elem["yellow"]) + " см\n"
                text += " Белая: " + str(elem["white"]) + " см"
            info_browser.setText(text)

    # 2й клик также перехватывается одинарным
    def item_list_double_clicked(self):
        list = self.ui.block_list
        name = list.currentItem().data(0x0100)
        type = list.currentItem().data(0x0101)

        if name == "separator":
            list.currentItem().setSelected(False)
        else:
            # TODO Добавление блока на карту по 2 клику
            print(name)

    # Установка значений по умолчанию
    def set_default_fill(self):
        default_fill = self.ui.default_fill.currentData()
        delete_fill = self.ui.delete_fill.currentData()
        # TODO установка занчений по умолчанию
        print(default_fill, delete_fill)

    # Вызов функции копирования
    def copy_button_clicked(self):
        if self.brush_button.isChecked():
            self.brush_button.click()
        self.drawState = 'copy'
        self.copyBuffer = copy.copy(self.mapviewer.tileSelection)
        # print("copy")

    # Вызов функции вырезания
    def cut_button_clicked(self):
        if self.brush_button.isChecked():
            self.brush_button.click()
        self.drawState = 'cut'
        self.copyBuffer = copy.copy(self.mapviewer.tileSelection)
        # print("cut")

    # Вызов функции вставки
    def insert_button_clicked(self):
        if len(self.copyBuffer) == 0:
            return
        self.editor.save(self.map)
        if self.drawState == 'copy':
            self.editor.copySelection(
                self.copyBuffer, self.mapviewer.tileSelection[0],
                self.mapviewer.tileSelection[1],
                MapTile(self.ui.delete_fill.currentData()))
        elif self.drawState == 'cut':
            self.editor.moveSelection(
                self.copyBuffer, self.mapviewer.tileSelection[0],
                self.mapviewer.tileSelection[1],
                MapTile(self.ui.delete_fill.currentData()))
        self.mapviewer.scene().update()

    # Вызов функции удаления
    def delete_button_clicked(self):
        self.editor.save(self.map)
        self.editor.deleteSelection(self.mapviewer.tileSelection,
                                    MapTile(self.ui.delete_fill.currentData()))
        self.mapviewer.scene().update()

    # Вызов функции отката
    def undo_button_clicked(self):
        self.editor.undo()
        self.mapviewer.scene().update()

    # Включение режима кисти
    def brush_mode(self):
        if self.brush_button.isChecked():
            self.drawState = 'brush'
        else:
            self.drawState = ''

    def rotateSelectedTiles(self):
        self.editor.save(self.map)
        selection = self.mapviewer.tileSelection
        if selection:
            for i in range(selection[0], selection[2]):
                for j in range(selection[1], selection[3]):
                    self.map.tiles[j][i].rotation = (
                        self.map.tiles[j][i].rotation + 90) % 360
            self.mapviewer.scene().update()

    def trimClicked(self):
        self.editor.save(self.map)
        self.editor.trimBorders(True, True, True, True,
                                MapTile(self.ui.delete_fill.currentData()))
        self.mapviewer.scene().update()

    def selectionUpdate(self):
        selection = self.mapviewer.tileSelection
        filler = MapTile(self.ui.default_fill.currentData())
        if self.drawState == 'brush':
            self.editor.save(self.map)
            self.editor.extendToFit(selection, selection[0], selection[1],
                                    MapTile(self.ui.delete_fill.currentData()))
            if selection[0] < 0:
                delta = -selection[0]
                selection[0] = 0
                selection[2] += delta
            if selection[1] < 0:
                delta = -selection[1]
                selection[3] += delta
            for i in range(max(selection[0], 0),
                           min(selection[2], len(self.map.tiles[0]))):
                for j in range(max(selection[1], 0),
                               min(selection[3], len(self.map.tiles))):
                    self.map.tiles[j][i] = copy.copy(filler)
        self.mapviewer.scene().update()

    # функция создания доп. информационного окна
    def show_info(self, name, title, text):
        name.set_window_name(title)
        name.set_text(text)
        name.show()
コード例 #3
0
ファイル: mainwindow.py プロジェクト: OSLL/dt-gui-tools
class duck_window(QtWidgets.QMainWindow):
    map = None
    mapviewer = None
    info_json = None
    editor = None
    drawState = ''
    copyBuffer = [[]]

    def __init__(self, args, elem_info="doc/info.json"):
        super().__init__()
        # active items in editor
        self.distortion_view_one_string_mode = True
        self.region_create = False
        self.active_items = []
        self.active_group = None
        self.name_of_editable_obj = None
        self.dm = get_dt_world()
        self.tile_size = DEFAULT_TILE_SIZE
        self.duckie_manager = ManagerDuckietownMaps()
        self.duckie_manager.add_map("maps/empty", self.dm)

        #  additional windows for displaying information
        self.author_window = info_window()
        self.param_window = info_window()
        self.mater_window = info_window()

        #  The brush button / override the closeEvent
        self.brush_button = QtWidgets.QToolButton()
        self.closeEvent = functools.partial(self.quit_program_event)

        # Set locale
        self.locale = args.locale

        # Set debug mode
        self.debug_mode = args.debug

        # Load element's info
        self.info_json = json.load(codecs.open(elem_info, "r", "utf-8"))

        # Loads info about types from duckietown
        self.duckietown_types_apriltags = get_duckietown_types()
        #####  Forms   #############
        self.new_tag_class = NewTagForm(self.duckietown_types_apriltags)
        self.init_info_form = StartInfoForm()
        self.new_group_form = NewGroupForm()
        self.env_form = EnvForm()
        ############################
        map_name = "maps/empty"
        self.dm = get_dt_world(map_name)
        logger.debug(self.dm.get_context())
        self.map = map.DuckietownMap()
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        viewer = mapviewer.MapViewer()
        self.editor = MapEditor(self.map, self.mapviewer)
        viewer.setMap(self.map)
        self.mapviewer = viewer
        viewer.setMinimumSize(540, 540)
        self.ui.horizontalLayout.addWidget(viewer)
        viewer.repaint()
        self.initUi()

        self.update_layer_tree()

    def get_translation(self, elem):
        """Gets info about the element based on self.locale
        If local doesn't exist, return locale='en'

        :param self
        :param elem: dict, information about elements (or category), that contains translation
        :return: dict(). Dict with translation based on the self.locale
        """
        if self.locale in elem['lang']:
            return elem['lang'][self.locale]
        else:
            logger.debug(
                "duck_window.get_translation. No such locale: {}".format(
                    self.locale))
            return elem['lang']['en']

    def initUi(self):
        self.center()
        self.show()

        #  Initialize button objects
        create_map = self.ui.create_new
        open_map = self.ui.open_map
        save_map = self.ui.save_map
        save_map_as = self.ui.save_map_as
        calc_param = self.ui.calc_param
        about_author = self.ui.about_author
        exit = self.ui.exit
        change_blocks = self.ui.change_blocks
        change_info = self.ui.change_info
        change_map = self.ui.change_map
        change_layer = self.ui.change_layer
        distortion_view = self.ui.distortion_view
        create_region = self.ui.region_create
        import_old_format = self.ui.import_old_format
        environment = self.ui.env

        #  Initialize floating blocks
        block_widget = self.ui.block_widget
        info_widget = self.ui.info_widget
        map_info_widget = self.ui.map_info_widget
        layer_info_widget = self.ui.layer_info_widget

        #  Signal from viewer
        self.mapviewer.selectionChanged.connect(self.selectionUpdate)
        self.mapviewer.editObjectChanged.connect(self.create_form)
        self.new_tag_class.apriltag_added.connect(self.add_apriltag)

        #  Assign actions to buttons
        create_map.triggered.connect(self.create_map_triggered)
        open_map.triggered.connect(self.open_map_triggered)
        save_map.triggered.connect(self.save_map_triggered)
        save_map_as.triggered.connect(self.save_map_as_triggered)
        calc_param.triggered.connect(self.calc_param_triggered)
        about_author.triggered.connect(self.about_author_triggered)
        distortion_view.triggered.connect(
            self.change_distortion_view_triggered)
        create_region.triggered.connect(self.create_region)
        import_old_format.triggered.connect(self.import_old_format)
        environment.triggered.connect(self.change_env)
        exit.triggered.connect(self.exit_triggered)

        change_blocks.toggled.connect(self.change_blocks_toggled)
        change_info.toggled.connect(self.change_info_toggled)
        change_map.toggled.connect(self.change_map_toggled)
        change_layer.toggled.connect(self.toggle_layer_window)

        block_widget.closeEvent = functools.partial(self.blocks_event)
        info_widget.closeEvent = functools.partial(self.info_event)
        map_info_widget.closeEvent = functools.partial(self.map_event)
        layer_info_widget.closeEvent = functools.partial(
            self.close_layer_window_event)

        #  QToolBar setting
        tool_bar = self.ui.tool_bar

        a1 = QtWidgets.QAction(QtGui.QIcon("img/icons/new.png"),
                               _translate("MainWindow", "New map"), self)
        a2 = QtWidgets.QAction(QtGui.QIcon("img/icons/open.png"),
                               _translate("MainWindow", "Open map"), self)
        a3 = QtWidgets.QAction(QtGui.QIcon("img/icons/save.png"),
                               _translate("MainWindow", "Save map"), self)
        a4 = QtWidgets.QAction(QtGui.QIcon("img/icons/save_as.png"),
                               _translate("MainWindow", "Save map as"), self)
        a5 = QtWidgets.QAction(QtGui.QIcon("img/icons/png.png"),
                               _translate("MainWindow", "Export to PNG"), self)

        b1 = QtWidgets.QAction(QtGui.QIcon("img/icons/copy.png"),
                               _translate("MainWindow", "Copy"), self)
        b2 = QtWidgets.QAction(QtGui.QIcon("img/icons/cut.png"),
                               _translate("MainWindow", "Cut"), self)
        b3 = QtWidgets.QAction(QtGui.QIcon("img/icons/insert.png"),
                               _translate("MainWindow", "Paste"), self)
        b4 = QtWidgets.QAction(QtGui.QIcon("img/icons/delete.png"),
                               _translate("MainWindow", "Delete"), self)
        b5 = QtWidgets.QAction(QtGui.QIcon("img/icons/undo.png"),
                               _translate("MainWindow", "Undo"), self)
        b1.setShortcut("Ctrl+C")
        b2.setShortcut("Ctrl+X")
        b3.setShortcut("Ctrl+V")
        b4.setShortcut("Delete")
        b5.setShortcut("Ctrl+Z")

        c1 = QtWidgets.QAction(QtGui.QIcon("img/icons/rotate.png"),
                               _translate("MainWindow", "Rotate"), self)
        c2 = QtWidgets.QAction(
            QtGui.QIcon("img/icons/trim.png"),
            _translate("MainWindow", "Delete extreme empty blocks"), self)
        c1.setShortcut("Ctrl+R")
        c2.setShortcut("Ctrl+F")

        self.brush_button.setIcon(QtGui.QIcon("img/icons/brush.png"))
        self.brush_button.setCheckable(True)
        self.brush_button.setToolTip("Brush tool")
        self.brush_button.setShortcut("Ctrl+B")

        a1.triggered.connect(self.create_map_triggered)
        a2.triggered.connect(self.open_map_triggered)
        a3.triggered.connect(self.save_map_triggered)
        a4.triggered.connect(self.save_map_as_triggered)

        b1.triggered.connect(self.copy_button_clicked)
        b2.triggered.connect(self.cut_button_clicked)
        b3.triggered.connect(self.insert_button_clicked)
        b4.triggered.connect(self.delete_button_clicked)
        b5.triggered.connect(self.undo_button_clicked)

        c1.triggered.connect(self.rotateSelectedTiles)
        c2.triggered.connect(self.trimClicked)

        self.brush_button.clicked.connect(self.brush_mode)

        for elem in [[a1, a2, a3, a4, a5], [b1, b2, b3, b4, b5]]:
            for act in elem:
                tool_bar.addAction(act)
            tool_bar.addSeparator()
        tool_bar.addWidget(self.brush_button)
        tool_bar.addAction(c1)
        tool_bar.addAction(c2)

        # Setup Layer Tree menu
        self.ui.layer_tree.setModel(
            QtGui.QStandardItemModel())  # set item model for tree

        #  Customize the Blocks menu
        block_list_widget = self.ui.block_list
        block_list_widget.itemClicked.connect(self.item_list_clicked)
        block_list_widget.itemDoubleClicked.connect(
            self.item_list_double_clicked)

        #  Customize the Map Editor menu
        default_fill = self.ui.default_fill
        delete_fill = self.ui.delete_fill

        #  Fill out the list
        categories = self.info_json['categories']
        information = self.info_json['info']
        for group in categories:
            # add separator
            icon = "img/icons/galka.png"
            widget = QtWidgets.QListWidgetItem(
                QtGui.QIcon(icon),
                self.get_translation(group)['name'])
            widget.setData(0x0100, "separator")
            widget.setData(0x0101, group['id'])
            widget.setBackground(QtGui.QColor(169, 169, 169))
            block_list_widget.addItem(widget)
            # add elements
            for elem_id in group['elements']:
                widget = QtWidgets.QListWidgetItem(
                    QtGui.QIcon(information[elem_id]['icon']),
                    self.get_translation(information[elem_id])['name'])
                widget.setData(0x0100, elem_id)
                widget.setData(0x0101, group['id'])
                block_list_widget.addItem(widget)
                # add tiles to fill menu
                if group['id'] in ("road", "block"):
                    default_fill.addItem(
                        QtGui.QIcon(information[elem_id]['icon']),
                        self.get_translation(information[elem_id])['name'],
                        elem_id)
                    delete_fill.addItem(
                        QtGui.QIcon(information[elem_id]['icon']),
                        self.get_translation(information[elem_id])['name'],
                        elem_id)

        default_fill.setCurrentText(
            self.get_translation(information["grass"])['name'])
        delete_fill.setCurrentText(
            self.get_translation(information["empty"])['name'])

        set_fill = self.ui.set_fill
        set_fill.clicked.connect(self.set_default_fill)

    def change_env(self):
        self.env_form.show()

    def center(self):
        qr = self.frameGeometry()
        cp = QDesktopWidget().availableGeometry().center()
        qr.moveCenter(cp)
        self.move(qr.topLeft())

    #  Create a new map
    def open_map_triggered(self):
        logger.debug("Creating a new map")
        new_map_dir = QFileDialog.getExistingDirectory(
            self, 'Open new map', '.',
            QFileDialog.ShowDirsOnly | QFileDialog.DontResolveSymlinks)
        if new_map_dir:
            dm2_test = get_new_dt_world(new_map_dir)
            print(dm2_test)
            self.duckie_manager.add_map(new_map_dir.split('/')[-1], dm2_test)
            self.reset_duckietown_map(dm2_test)
            print(new_map_dir.split('/')[-1])
            self.mapviewer.offsetX = self.mapviewer.offsetY = 0
            self.mapviewer.scene().update()
            self.update_layer_tree()

    def import_old_format(self):
        old_format_map = QFileDialog.getOpenFileName(self,
                                                     'Open map(old format)',
                                                     '.')
        path, _ = old_format_map
        print(path)
        with open(path) as file:
            new_format_map = convert_new_format(file.read())
        print(new_format_map)
        map_path = os.getcwd() + "/output"
        try:
            os.makedirs(map_path)
        except:
            pass
        dump(new_format_map)
        dm = get_new_dt_world(map_path)
        self.duckie_manager.add_map(map_path.split('/')[-1], dm)
        self.reset_duckietown_map(dm)
        self.mapviewer.offsetX = self.mapviewer.offsetY = 0
        self.mapviewer.scene().update()
        self.update_layer_tree()

    #  Open map
    def create_map_triggered(self):
        logger.debug(2)
        self.editor.save(self.map)

        def init_info(info):
            i, j = int(info['x']), int(info['y'])
            self.tile_size = float(info['tile_size'])
            self.create_empty_map(i, j)
            self.dm.tile_maps['map_1'].x = self.tile_size
            self.dm.tile_maps['map_1'].y = self.tile_size
            self.mapviewer.tile_size = self.tile_size
            self.mapviewer.scene().update()
            self.update_layer_tree()

        self.init_info_form.send_info.connect(init_info)
        self.init_info_form.show()

        self.mapviewer.offsetX = self.mapviewer.offsetY = 0
        self.mapviewer.scene().update()
        self.update_layer_tree()

    def create_region(self):
        self.region_create = True
        self.new_group_form.show()
        print('Create REGION ', self.region_create)

    def change_distortion_view_triggered(self):
        self.distortion_view_one_string_mode = not self.distortion_view_one_string_mode

    #  Save map
    def save_map_triggered(self):
        self.save_map_as_triggered()

    #  Save map as
    def save_map_as_triggered(self):
        path_folder = save_map_as(self)
        if path_folder:
            map_final = self.dm.dump(self.dm)

            for layer_name in map_final:
                with open(path_folder + f'/{layer_name}.yaml', 'w+') as file:
                    file.write(map_final[layer_name])
            print('FINAL PATH, ', path_folder)

    #  Calculate map characteristics
    def calc_param_triggered(self):
        text = ""
        for info, obj in self.dm.tiles:
            i, j = obj.i, obj.j
            type = obj.type
            orientation = obj.type
            text += f"{i}-{j}: {type}/{orientation}\n"
        self.show_info(self.param_window,
                       _translate("MainWindow", "Map characteristics"), text)

    #  Help: About
    def about_author_triggered(self):
        text = '''
        - Select an object using the left mouse button\n
        - when object is selected you can change pos, using mouse\n
        - add apriltag using key `R`\n
        - Edit an object, click on it using the right mouse button\n
        - Authors:\n alskaa;\n dihindee;\n ovc-serega;\n HadronCollider;\n light5551;\n snush.\n\n Contact us on github!
        '''
        self.show_info(self.author_window, "About", text)

    #  Exit
    def exit_triggered(self):
        self.save_before_exit()
        QtCore.QCoreApplication.instance().quit()

    # Save map before exit
    def save_before_exit(self):
        if not self.debug_mode:
            ret = self.quit_MessageBox()
            if ret == QMessageBox.Cancel:
                return
            if ret == QMessageBox.Save:
                self.save_map_as_triggered()

    #  Hide Block menu
    def change_blocks_toggled(self):
        block = self.ui.block_widget
        if self.ui.change_blocks.isChecked():
            block.show()
            block.setFloating(False)
        else:
            block.close()

    #  Change button state
    def blocks_event(self, event):
        self.ui.change_blocks.setChecked(False)
        event.accept()

    #  Hide information menu
    def change_info_toggled(self):
        block = self.ui.info_widget
        if self.ui.change_info.isChecked():
            block.show()
            block.setFloating(False)
        else:
            block.close()

    #  Change button state
    def info_event(self, event):
        self.ui.change_info.setChecked(False)
        event.accept()

    #  Hide the menu about map properties
    def change_map_toggled(self):
        block = self.ui.map_info_widget
        if self.ui.change_map.isChecked():
            block.show()
            block.setFloating(False)
        else:
            block.close()

    #  Change button state
    def map_event(self, event):
        self.ui.change_map.setChecked(False)
        event.accept()

    # Layer window

    def toggle_layer_window(self):
        """
        Toggle layers window by `View -> Layers`
        :return: -
        """
        block = self.ui.layer_info_widget
        if self.ui.change_layer.isChecked():
            block.show()
            block.setFloating(False)
        else:
            block.close()

    def close_layer_window_event(self, event):
        """
        Reset flag `View -> Layers` when closing layers window
        :param event: closeEvent
        :return: -
        """
        self.ui.change_layer.setChecked(False)
        event.accept()

    def layer_tree_clicked(self):
        pass

    def layer_tree_double_clicked(self):
        pass

    def update_layer_tree(self):
        """
        Update layer tree.
        Show layer's elements as children in hierarchy (except tile layer)
        :return: -
        """
        def signal_check_state(item):
            """
            update visible state of layer.
            :return: -
            """

            dm = self.duckie_manager.get_map(item.text())
            self.reset_duckietown_map(dm)
            self.mapviewer.scene().update()
            layer_tree_view.clearSelection()
            item_model.clear()
            item_model.setHorizontalHeaderLabels(['Maps'])
            self.show_maps_menu(layer_tree_view.model().invisibleRootItem())

        layer_tree_view = self.ui.layer_tree
        item_model = layer_tree_view.model()
        item_model.clear()
        print('waws clear')
        try:
            item_model.itemChanged.disconnect()
        except TypeError:
            pass  # only 1st time in update_layer_tree
        item_model.itemChanged.connect(signal_check_state)
        item_model.setHorizontalHeaderLabels(['Maps'])
        root_item = layer_tree_view.model().invisibleRootItem()

        self.show_maps_menu(root_item)
        layer_tree_view.expandAll()

    def show_maps_menu(self, root_item):
        for map_name in self.duckie_manager.get_maps_name():
            layer_item = QtGui.QStandardItem(str(map_name))
            layer_item.setCheckable(True)
            layer_item.setCheckState(QtCore.Qt.Unchecked)
            layer_item.setCheckState(
                QtCore.Qt.Checked if self.duckie_manager.is_active ==
                map_name else QtCore.Qt.Unchecked)
            root_item.appendRow(layer_item)
            layer_item.sortChildren(0)

    #  MessageBox to exit
    def quit_MessageBox(self):
        reply = QMessageBox(self)
        reply.setIcon(QMessageBox.Question)
        reply.setWindowTitle(_translate("MainWindow", "Exit"))
        reply.setText(_translate("MainWindow", "Exit"))
        reply.setInformativeText(_translate("MainWindow", "Save and exit?"))
        reply.setStandardButtons(QMessageBox.Save | QMessageBox.Discard
                                 | QMessageBox.Cancel)
        reply.setDefaultButton(QMessageBox.Save)
        ret = reply.exec()
        return ret

    #  Program exit event
    def quit_program_event(self, event):
        self.save_before_exit()

        #  Close additional dialog boxes
        self.author_window.exit()
        self.param_window.exit()
        self.mater_window.exit()

        event.accept()

    def create_empty_map(self, i_size: int, j_size: int) -> None:
        self.mapviewer.i_tile, self.mapviewer.j_tile = i_size, j_size
        for i in range(i_size):
            for j in range(j_size):
                tile = Tile("{}/tile_{}_{}".format(self.dm.get_context(), i,
                                                   j))
                tile.obj.i = i
                tile.obj.j = j
                tile.frame.pose.x = int(i) * 0.585 + 0.585 / 2
                tile.frame.pose.y = int(j) * 0.585 + 0.585 / 2
                tile.obj.orientation = 'E'
                tile.frame.relative_to = self.dm.get_context()
                tile.frame.dm = self.dm
                self.dm.add(tile)

    #  Handle a click on an item from a list to a list
    def item_list_clicked(self):
        list = self.ui.block_list
        name = list.currentItem().data(0x0100)
        type = list.currentItem().data(0x0101)

        if name == "separator":
            list.currentItem().setSelected(False)

            for i in range(list.count()):
                elem = list.item(i)
                if type == elem.data(0x0101):
                    if name == elem.data(0x0100):
                        icon = QtGui.QIcon("img/icons/galka.png") if list.item(i + 1).isHidden() else \
                            QtGui.QIcon("img/icons/galka_r.png")
                        elem.setIcon(icon)
                    else:
                        elem.setHidden(not elem.isHidden())
        else:
            elem = self.info_json['info'][name]
            info_browser = self.ui.info_browser
            info_browser.clear()
            text = "{}:\n {}\n{}:\n{}".format(
                _translate("MainWindow", "Name"),
                list.currentItem().text(),
                _translate("MainWindow", "Description"),
                self.get_translation(elem)['info'])
            if elem["type"] == "block":
                text += "\n\n{}: {} {}".format(
                    _translate("MainWindow", "Road len"), elem["length"],
                    _translate("MainWindow", "sm"))
                text += " Tape:\n"
                text += " {}: {} {}\n".format(_translate("MainWindow", "Red"),
                                              elem["red"],
                                              _translate("MainWindow", "sm"))
                text += " {}: {} {}\n".format(
                    _translate("MainWindow", "Yellow"), elem["yellow"],
                    _translate("MainWindow", "sm"))
                text += " {}: {} {}\n".format(
                    _translate("MainWindow", "White"), elem["white"],
                    _translate("MainWindow", "sm"))
            info_browser.setText(text)

    #  Double click initiates as single click action
    def item_list_double_clicked(self):
        item_ui_list = self.ui.block_list
        item_name = item_ui_list.currentItem().data(0x0100)
        item_type = item_ui_list.currentItem().data(0x0101)

        if item_name == "separator":
            item_ui_list.currentItem().setSelected(False)
        else:
            if item_type in TILE_TYPES:
                self.ui.default_fill.setCurrentText(
                    self.get_translation(
                        self.info_json['info'][item_name])['name'])
                logger.debug("Set {} for brush".format(item_name))
            else:
                # save map before adding object
                self.editor.save(self.map)
                # adding object
                print(item_name)
                type_of_element = self.info_json['info'][item_name]['type']
                obj = None
                if item_name == "duckie":
                    obj = Citizen(
                        f"{self.dm.get_context()}/duckie_{len(self.dm.citizens.dict())}",
                        x=1,
                        y=1)
                elif item_name == "watchtower":
                    name = f"{self.dm.get_context()}/watchtower_{len(self.dm.watchtowers.dict())}"
                    obj = Watchtower(name, x=1, y=1)
                    self.dm.add(Camera(f"{name}/camera"))
                elif type_of_element == "sign":
                    name = f"{self.dm.get_context()}/{get_canonical_sign_name(item_name)}_{len(self.dm.traffic_signs.dict())}"
                    obj = TrafficSign(name, x=1, y=1)
                    obj.obj.type = get_canonical_sign_name(item_name)
                    obj.obj.id = utils.get_id_by_type(item_name)
                elif item_name == "apriltag":
                    name = f"{self.dm.get_context()}/groundtag_{len(self.dm.ground_tags.dict())}"
                    obj = GroundTag(name, x=1, y=1)
                elif item_name == "duckiebot":
                    name = f"{self.dm.get_context()}/vehicle_{len(self.dm.vehicles.dict())}"
                    obj = Vehicle(name, x=1, y=1)
                    self.dm.add(Camera(f"{name}/camera"))
                else:  # block for decorations
                    name = f"{self.dm.get_context()}/{item_name}_{len(self.dm.decorations.dict())}"
                    obj = Decoration(name, x=1, y=1)
                    obj.obj.type = item_name
                if obj:
                    obj.frame.relative_to = self.dm.get_context()
                    self.dm.add(obj)

                # TODO: need to understand what's the type and create desired class, not general
                # also https://github.com/moevm/mse_visual_map_editor_for_duckietown/issues/122
                # (for args, that can be edited and be different between classes)
                self.mapviewer.scene().update()
                logger.debug("Add {} to map".format(item_name))
            self.update_layer_tree()

    #  Reset to default values
    def set_default_fill(self):
        default_fill = self.ui.default_fill.currentData()
        delete_fill = self.ui.delete_fill.currentData()
        # TODO установка занчений по умолчанию
        logger.debug("{}; {}".format(default_fill, delete_fill))

    #  Copy
    def copy_button_clicked(self):
        if self.brush_button.isChecked():
            self.brush_button.click()
        self.drawState = 'copy'
        self.copyBuffer = copy.copy(self.mapviewer.tileSelection)
        logger.debug("Copy")

    #  Cut
    def cut_button_clicked(self):
        if self.brush_button.isChecked():
            self.brush_button.click()
        self.drawState = 'cut'
        self.copyBuffer = copy.copy(self.mapviewer.tileSelection)
        logger.debug("Cut")

    #  Paste
    def insert_button_clicked(self):
        if len(self.copyBuffer) == 0:
            return
        self.editor.save(self.map)
        if self.drawState == 'copy':
            self.editor.copySelection(
                self.copyBuffer, self.mapviewer.tileSelection[0],
                self.mapviewer.tileSelection[1],
                MapTile(self.ui.delete_fill.currentData()))
        elif self.drawState == 'cut':
            self.editor.moveSelection(
                self.copyBuffer, self.mapviewer.tileSelection[0],
                self.mapviewer.tileSelection[1],
                MapTile(self.ui.delete_fill.currentData()))
        self.mapviewer.scene().update()
        self.update_layer_tree()

    #  Delete
    def delete_button_clicked(self):
        if not self.map.get_tile_layer().visible:
            return
        self.mapviewer.remove_last_obj()
        self.mapviewer.scene().update()
        self.update_layer_tree()

    #  Undo
    def undo_button_clicked(self):
        self.editor.undo()
        self.mapviewer.scene().update()
        self.update_layer_tree()

    #  Brush mode
    def brush_mode(self):
        if self.brush_button.isChecked():
            self.drawState = 'brush'
        else:
            self.drawState = ''

    def keyPressEvent(self, e):
        selection = self.mapviewer.raw_selection
        item_layer = self.map.get_objects_from_layers(
        )  # TODO: add self.current_layer for editing only it's objects?
        new_selected_obj = False
        for layer in self.dm:
            print(layer)
        print(selection)
        if self.region_create:
            self.region_create = False

        for item in item_layer:
            x, y = item.position
            if x > selection[0] and x < selection[2] and y > selection[
                    1] and y < selection[3]:
                if item not in self.active_items:
                    self.active_items.append(item)
                    new_selected_obj = True
        if new_selected_obj:
            # save map if new objects are selected
            self.editor.save(self.map)
        key = e.key()
        print('KEY ', key, " ", QtCore.Qt.ALT, " ", e.modifiers())
        if key == QtCore.Qt.Key_Q:
            # clear object buffer
            self.active_items = []
            self.mapviewer.raw_selection = [0] * 4
        elif key == QtCore.Qt.Key_R:
            self.new_tag_class.create_form()
        elif key == QtCore.Qt.Key_H:
            # print(self.duckie_manager.get_maps_name())
            for map_name in self.duckie_manager.get_maps_name():
                if map_name == "maps/test":
                    self.reset_duckietown_map(
                        self.duckie_manager.get_map(map_name))

        if self.active_items:
            if key == QtCore.Qt.Key_Backspace:
                # delete object
                if question_form_yes_no(
                        self, "Deleting objects",
                        "Delete objects from map?") == QMessageBox.Yes:
                    # save map before deleting objects
                    self.editor.save(self.map)
                    for item in self.active_items:
                        object_type = self.info_json['info'][item.kind]['type']
                        layer = self.map.get_layer_by_type(
                            get_layer_type_by_object_type(object_type))
                        layer.remove_object_from_layer(item)
                    self.active_items = []
                    self.mapviewer.scene().update()
                    self.update_layer_tree()
                return
            for item in self.active_items:
                logger.debug("Name of item: {}; X - {}; Y - {};".format(
                    item.kind, item.position[0], item.position[1]))
                if key == QtCore.Qt.Key_W:
                    item.position[1] -= EPS
                elif key == QtCore.Qt.Key_S:
                    item.position[1] += EPS
                elif key == QtCore.Qt.Key_A:
                    item.position[0] -= EPS
                elif key == QtCore.Qt.Key_D:
                    item.position[0] += EPS
                elif key == QtCore.Qt.Key_E:
                    if len(self.active_items) == 1:
                        self.create_form(self.active_items[0])
                    else:
                        logger.debug("I can't edit more than one object!")
        self.mapviewer.scene().update()

    def create_form(self, active_object_data: tuple):

        active_object, (name, tp) = active_object_data
        self.name_of_editable_obj = name
        assert tp is _Frame

        def accept():
            try:
                active_object.pose.x = float(edit_obj['x'].text())
                active_object.pose.y = float(edit_obj['y'].text())
                active_object.pose.yaw = float(
                    np.deg2rad(float(edit_obj['yaw'].text())))
                new_type = None
                print(f"ACCEPT: {cam_obj}")
                for key in editable_values:
                    print("Key - ", key)
                    if key in [
                            "width", "height", "framerate",
                            "distortion_parameters", "camera_matrix"
                    ]:  # cam obj
                        if key in ["width", "height", "framerate"]:
                            cam_obj[key] = int(edit_obj[key].text().split()[0])
                        elif key == "distortion_parameters":
                            if not cam_obj.distortion_parameters:
                                cam_obj["distortion_parameters"] = []

                            if self.distortion_view_one_string_mode:
                                dk = edit_obj[f"distortion_parameters"].text(
                                ).replace(" ", "").split(",")
                                for idx, val in enumerate(dk):
                                    val = float(val)
                                    if len(cam_obj.distortion_parameters) < 5:
                                        cam_obj.distortion_parameters.append(
                                            val)
                                    else:
                                        cam_obj.distortion_parameters[
                                            idx] = val
                            else:
                                for idx in range(5):
                                    val = float(edit_obj[
                                        f"distortion_parameters_{idx}"].text().
                                                split()[0])
                                    if len(cam_obj.distortion_parameters) < 5:
                                        cam_obj.distortion_parameters.append(
                                            val)
                                    else:
                                        cam_obj.distortion_parameters[
                                            idx] = val
                        elif key == "camera_matrix":
                            if not cam_obj.camera_matrix:
                                cam_obj["camera_matrix"] = []
                            if not cam_obj.camera_matrix:
                                for _ in range(3):
                                    cam_obj.camera_matrix.append([])
                            for row in range(3):
                                for col in range(3):
                                    val = float(
                                        edit_obj[f"camera_matrix_{row}_{col}"].
                                        text().split()[0])
                                    if len(cam_obj.camera_matrix[row]) < 3:
                                        cam_obj.camera_matrix[row].append(val)
                                    else:
                                        cam_obj.camera_matrix[row][col] = val
                            print('MATRIX1   ', cam_obj.camera_matrix)
                    else:
                        new_value = edit_obj[key].text().split()[0]
                        if key == 'id' and len(
                                edit_obj[key].text().split()) > 1:
                            new_type = edit_obj[key].text().split()[1][1:-1]
                        if key == 'type' and new_type:
                            obj[key] = get_canonical_sign_name(
                                new_type)  # new_type
                            continue
                        if new_value.isdigit():
                            new_value = int(new_value)
                        try:
                            if isinstance(new_value, str):
                                new_value = float(new_value)
                        except ValueError:
                            pass
                        print(f"New value {new_value} for key {key}")
                        obj[key] = new_value
            except Exception as e:
                msg = QMessageBox()
                msg.setIcon(QMessageBox.Critical)
                msg.setText(str(e))
                msg.setWindowTitle("Error")
                msg.exec_()
            dialog.close()
            self.mapviewer.scene().update()
            self.update_layer_tree()

        def reject():
            dialog.close()

        # work version
        info_object = self.dm.get_objects_by_name(name)
        del info_object[(name, tp)]
        _, type_object = list(info_object.keys())[0]
        obj = info_object[(name, type_object)]
        cam_obj = None

        dialog = QtWidgets.QDialog(self)
        dialog.setWindowTitle('Change attribute of object')
        # buttonbox
        buttonBox = QtWidgets.QDialogButtonBox(
            QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel)
        buttonBox.accepted.connect(accept)
        buttonBox.rejected.connect(reject)
        # form
        formGroupBox = QGroupBox("Change attribute's object: {}".format(""))

        layout = QFormLayout()
        # editable_attrs = active_object.get_editable_attrs()
        edit_obj = {}
        combo_id = QComboBox(self)

        def change_combo_id(value):
            combo_id.clear()
            combo_id.addItems([
                "{}/{}".format(i.id, i.type)
                for i in self.duckietown_types_apriltags[value]
            ])
            combo_id.setEditText(str(
                self.duckietown_types_apriltags[value][0]))

        def change_type_from_combo(value: str):
            if 'type' in edit_obj:
                edit_obj['type'].setText(value.split()[1][1:-1])

        x_edit = QLineEdit(str(active_object.pose.x))
        y_edit = QLineEdit(str(active_object.pose.y))
        yaw_edit = QLineEdit(str(np.rad2deg(active_object.pose.yaw)))
        edit_obj['x'] = x_edit
        edit_obj['y'] = y_edit
        edit_obj['yaw'] = yaw_edit
        layout.addRow(QLabel("{}.X".format("pose")), x_edit)
        layout.addRow(QLabel("{}.Y".format("pose")), y_edit)
        layout.addRow(QLabel("{}.yaw".format("pose")), yaw_edit)

        editable_values = obj.dict()
        print('ATTR SHOW: ', editable_values, name)

        for attr_name in sorted(editable_values.keys()):
            attr = editable_values[attr_name]
            new_edit = QLineEdit(str(attr))

            edit_obj[attr_name] = new_edit

            if "vehicle" not in name and attr_name == 'id':
                type_id = list(self.duckietown_types_apriltags.keys())[0]
                for type_sign in self.duckietown_types_apriltags.keys():
                    try:
                        if int(attr
                               ) in self.duckietown_types_apriltags[type_sign]:
                            type_id = type_sign
                            break
                    except:
                        pass
                if "sign" not in name:
                    for type_tag in self.duckietown_types_apriltags:
                        combo_id.addItems([
                            "{}".format(i.id)
                            for i in self.duckietown_types_apriltags[type_tag]
                        ])
                else:
                    combo_id.addItems([
                        "{} ({})".format(i.id, i.type)
                        for i in self.duckietown_types_apriltags[type_id]
                    ])
                combo_id.setLineEdit(new_edit)
                new_edit.setReadOnly(True)
                combo_id.setEditText(str(attr))
                combo_id.currentTextChanged.connect(change_type_from_combo)
                layout.addRow(QLabel(attr_name), combo_id)
            elif attr_name == 'id':
                new_edit.setReadOnly(True)
                layout.addRow(QLabel("{}".format(attr_name)), new_edit)
            elif attr_name == "type":
                new_edit.setReadOnly(True)
                layout.addRow(QLabel("{}".format(attr_name)), new_edit)
            else:
                layout.addRow(QLabel("{}".format(attr_name)), new_edit)

        if "vehicle" in name:
            cam = self.dm.get_objects_by_name(name + "/camera")
            cam_obj: _Camera = cam[list(cam.keys())[0]]
            editable_values.update(cam_obj.dict())
            print("Camera info ", type(cam), cam.keys(),
                  list(cam.keys())[0], cam)
            print(cam_obj)
            layout.addRow(QHLine())

            width_camera_edit = QLineEdit(str(cam_obj.width))
            height_camera_edit = QLineEdit(str(cam_obj.height))
            framerate_camera_edit = QLineEdit(str(cam_obj.framerate))
            edit_obj.update({
                "width": width_camera_edit,
                "height": height_camera_edit,
                "framerate": framerate_camera_edit
            })
            layout.addRow(QLabel("{}.width".format("camera")),
                          width_camera_edit)
            layout.addRow(QLabel("{}.height".format("camera")),
                          height_camera_edit)
            layout.addRow(QLabel("{}.framerate".format("camera")),
                          framerate_camera_edit)

            layout.addRow(QLabel("Camera Matrix"))
            grid_matrix = QGridLayout()
            grid_matrix.setColumnStretch(1, 4)
            grid_matrix.setColumnStretch(2, 4)
            for row in range(3):
                for col in range(3):
                    if cam_obj.camera_matrix:
                        # TODO: NEED TO TEST
                        grid_line_edit = QLineEdit(
                            str(cam_obj.camera_matrix[row][col]))
                    else:
                        grid_line_edit = QLineEdit("0")
                    edit_obj[f"camera_matrix_{row}_{col}"] = grid_line_edit
                    grid_matrix.addWidget(grid_line_edit, row, col)

            layout.addRow(grid_matrix)
            grid_distortion = QGridLayout()
            grid_distortion.setColumnStretch(1, 4)
            grid_distortion.setColumnStretch(2, 4)
            layout.addRow(QLabel("Camera Distortion: [k1, k2, p1, p2, k3]"))
            for idx in range(5):
                if cam_obj.distortion_parameters:
                    grid_line_edit = QLineEdit(
                        str(cam_obj.distortion_parameters[idx]))
                else:
                    grid_line_edit = QLineEdit("0")
                edit_obj[f"distortion_parameters_{idx}"] = grid_line_edit
                grid_distortion.addWidget(grid_line_edit, 0, idx)

            layout.addRow(grid_distortion)

        layout.addRow(QHLine())
        combo_groups = QComboBox(self)
        ##### DEV FOR GROUP ####
        groups = ["No chosen"]
        for ((nm, _), group) in self.dm.groups:
            groups.append(f"{nm} [{group.description}]")
            print(nm, group.description)
        combo_groups.addItems([i for i in groups])

        combo_groups.setEditText("Choose group")
        combo_groups.currentTextChanged.connect(self.change_active_group)
        layout.addRow(QLabel("Choose group"), combo_groups)
        add_group = QPushButton("Add in group", self)
        add_group.clicked.connect(self.add_group_triggered)
        del_group = QPushButton("Del in group", self)
        del_group.clicked.connect(self.del_group_triggered)
        hl = QHBoxLayout()
        hl.addWidget(add_group)
        hl.addWidget(del_group)
        layout.addRow(hl)
        ########################
        formGroupBox.setLayout(layout)
        # layout
        mainLayout = QVBoxLayout()
        mainLayout.addWidget(formGroupBox)
        mainLayout.addWidget(buttonBox)
        dialog.setLayout(mainLayout)
        dialog.exec_()

    def add_group_triggered(self):
        if self.active_group:
            members = self.active_group.members
            if self.name_of_editable_obj not in members:
                members.append(self.name_of_editable_obj)

    def del_group_triggered(self):
        if self.active_group and self.name_of_editable_obj in self.active_group.members:
            self.active_group.members.remove(self.name_of_editable_obj)

    def change_active_group(self, value: str):
        if self.active_group:
            self.active_group = self.dm.get_object(value.split()[0], _Group)

    def rotateSelectedTiles(self):
        self.editor.save(self.map)
        is_selected_tile = self.mapviewer.is_selected_tile
        for ((nm, _), tile) in self.dm.tiles:
            if is_selected_tile(tile):
                frame: _Frame = self.dm.frames[nm]
                orien_val = get_degree_for_orientation(
                    tile.orientation
                ) - 90  # (rot_val[tile.orientation] + 90) % 360
                tile.orientation = get_orientation_for_degree(orien_val)
                frame.pose.yaw = {
                    'E': np.pi * 1.5,
                    'N': 0,
                    'W': np.pi,
                    'S': np.pi * 0.5,
                    None: 0
                }[tile.orientation]
        self.mapviewer.scene().update()

    def add_apriltag(self, apriltag: GroundAprilTagObject):
        layer = self.map.get_layer_by_type(LayerType.GROUND_APRILTAG)
        if layer is None:
            self.map.add_layer_from_data(LayerType.GROUND_APRILTAG, [apriltag])
        else:
            self.map.add_elem_to_layer_by_type(LayerType.GROUND_APRILTAG,
                                               apriltag)
        self.update_layer_tree()
        self.mapviewer.scene().update()

    def trimClicked(self):
        self.editor.save(self.map)
        self.editor.trimBorders(True, True, True, True,
                                MapTile(self.ui.delete_fill.currentData()))
        self.mapviewer.scene().update()
        self.update_layer_tree()

    def selectionUpdate(self):
        is_selected_tile = self.mapviewer.is_selected_tile
        if self.drawState == 'brush':
            self.editor.save(
                self.map)  # TODO: CTRL+Z need to fix because dt-world
            tiles = self.dm.tiles.only_tiles()
            for i in range(len(tiles)):
                for j in range(len(tiles[0])):
                    tile = tiles[i][j]
                    if is_selected_tile(tile):
                        tile.type = self.ui.default_fill.currentData()
                        tile.orientation = 'E'
        self.update_layer_tree()
        self.mapviewer.scene().update()

    def reset_duckietown_map(self, new_dm: DuckietownMap):
        self.dm = new_dm
        self.mapviewer.dm = new_dm
        # self.update_layer_tree()
        self.mapviewer.scene().update()

    def get_random_name(self, begin):
        return "{}_{}".format(begin, np.random.randint(1000))

    def show_info(self, name, title, text):
        name.set_window_name(title)
        name.set_text(text)
        name.show()