Home > Net >  Prevent recursion when changing QTreeWidgetItem flags
Prevent recursion when changing QTreeWidgetItem flags

Time:11-05

I'm trying to make a very basic password manager and I've encountered an issue when trying to edit an item. When the button "Edit Password" is pressed, it makes the currently selected item editable, and I would like it to be removed once the user is done making modifications. Trying to remove the flag ItemIsEditable causes it to go into infinite recursion on the line item.setFlags(item.flags() & ~Qt.ItemIsEditable).

# app class -------------------------------------------------------------------------- #
class App(QApplication):
    # initialisation ----------------------------------------------------------------- #
    def __init__(self, argv):
        super().__init__(argv)

        self.__ready = False

        self.__setup__()
        self.__load_data__()

        self.__ready = True

    # private methods ---------------------------------------------------------------- #
    def __load_data__(self):
        self.data_tree.headerItem().setText(0, "Client")
        self.data_tree.headerItem().setText(1, "Workstation")
        self.data_tree.headerItem().setText(2, "Login")
        self.data_tree.headerItem().setText(3, "Password")

        for level_1, client in enumerate(self.data["clients"]):
            row_1 = QTreeWidgetItem(self.data_tree)

            self.data_tree.topLevelItem(level_1).setText(0, client["name"])

            for level_2, workstation in enumerate(client["workstations"]):
                row_2 = QTreeWidgetItem(row_1)

                self.data_tree.topLevelItem(level_1).child(level_2).setText(
                    1, workstation["name"]
                )

                for level_3, login in enumerate(workstation["logins"]):
                    row_3 = QTreeWidgetItem(row_2)

                    row_3.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable)

                    self.data_tree.topLevelItem(level_1).child(level_2).child(
                        level_3
                    ).setText(2, login["username"])
                    self.data_tree.topLevelItem(level_1).child(level_2).child(
                        level_3
                    ).setText(3, login["password"])

        self.data_tree.setSortingEnabled(True)

    def __setup__(self):
        self.data = pd.read_json("list.json")
        self.main_window = QMainWindow()
        self.central_widget = QWidget(self.main_window)
        self.data_tree = QTreeWidget(self.central_widget)
        self.edit_password_button = QPushButton(self.central_widget)

        self.central_widget.setGeometry(QRect(0, 0, 500, 500))
        self.central_widget.setObjectName("central_widget")

        self.data_tree.itemChanged.connect(self.save_password)
        self.data_tree.setGeometry(QRect(0, 0, 500, 450))
        self.data_tree.setObjectName("data_tree")
        self.data_tree.sortByColumn(0, Qt.SortOrder.AscendingOrder)

        self.main_window.setCentralWidget(self.central_widget)
        self.main_window.setGeometry(QRect(200, 200, 500, 500))
        self.main_window.setWindowTitle("Password Manager")
        self.main_window.show()

        self.edit_password_button.clicked.connect(self.edit_password)
        self.edit_password_button.setGeometry(QRect(345, 455, 150, 40))
        self.edit_password_button.setText("Edit Password")

    # events ------------------------------------------------------------------------- #
    @pyqtSlot()
    def edit_password(self):
        try:
            item = self.data_tree.currentItem()

            item.setFlags(item.flags() | Qt.ItemIsEditable)
            self.data_tree.scrollToItem(item)
            self.data_tree.editItem(item, 3)
        except Exception as e:
            print(e)

    @pyqtSlot(QTreeWidgetItem, int)
    def save_password(self, item, column):
        if not self.__ready:
            return

        for client in self.data["clients"]:
            if client["name"] == item.parent().parent().text(0):
                for workstation in client["workstations"]:
                    if workstation["name"] == item.parent().text(1):
                        for login in workstation["logins"]:
                            if login["username"] == item.text(2):
                                login["password"] = item.text(3)

        with open("list.json", "w") as file:
            json.dump(json.loads(self.data.to_json()), file, indent=4)

        item.setFlags(item.flags() & ~Qt.ItemIsEditable)

    def simulate_password(self):
        keyboard.write(self.data_tree.currentItem().text(3))

    def test_func(self):
        print("test")


app = App(sys.argv)

keyboard.add_hotkey("ctrl insert", app.simulate_password)

sys.exit(app.exec())

CodePudding user response:

The itemChanged signal is emitted for all changes to an item, not just to its text. To avoid the recursion, you can temporarily block signals whilst setting the flags, so that the save_password slot doesn't get triggered again:

@pyqtSlot()
def edit_password(self):
    item = self.data_tree.currentItem()

    blocked = item.treeWidget().blockSignals(True)
    item.setFlags(item.flags() | Qt.ItemIsEditable)
    item.treeWidget().blockSignals(blocked)

    self.data_tree.scrollToItem(item)
    self.data_tree.editItem(item, 3)

@pyqtSlot(QTreeWidgetItem, int)
def save_password(self, item, column):
    ...
    blocked = item.treeWidget().blockSignals(True)
    item.setFlags(item.flags() & ~Qt.ItemIsEditable)
    item.treeWidget().blockSignals(blocked)
  • Related