Soren Bjornstad
Soren Bjornstad

Reputation: 1432

Prevent undo in a particular text box

Short version: How can I subclass or otherwise modify a QLineEdit so that the context menu's "undo" option won't undo anything?

Currently I have the following, which blocks Ctrl-Z from working:

class PasswordSaveQLineEdit(QLineEdit):
    def keyPressEvent(self,event):
        if event.key()==(QtCore.Qt.Key_Control and QtCore.Qt.Key_Z):
            self.undo()
        else:
            QLineEdit.keyPressEvent(self,event)

    def undo(self):
        pass

I could get away with disabling the context menu, which would then get rid of both possible ways to undo, but I would rather not. The undo() function doesn't appear to be called directly by either Ctrl-Z or selecting the context menu option.

I also saw this thread on clearing the undo history, which would also work fine, but that unfortunately only applies to Q(Plain)TextEdit and not to QLineEdit.

Explanation of why I need this (in case someone has a better suggestion for how to do it):

Using PyQt4 with the Qt Designer, I'm trying to implement a password entry box (a QLineEdit) where the user can check a box to show or hide the password. This part is working just fine:

def toggleShowPW(self):
    doShow = self.form.showPWCheck.isChecked()
    if doShow:
        self.form.passwordBox.setEchoMode(QLineEdit.Normal)
    else:
        self.form.passwordBox.setEchoMode(QLineEdit.Password)

self.form.showPWCheck.toggled.connect(self.toggleShowPW)

However, my application also needs an option to save the password and bring it back the next time this dialog is opened. Although the password is being stored in plaintext in the database (and I plan to provide a warning to the user about that when they select the option to save the password), I would like to add at least a little bit of security so that a user who walks by can't look at the password just by ticking the box. So I added an attribute to the class that keeps track of whether the password has been loaded from the configuration file, and changed the method to this:

def toggleShowPW(self):
    doShow = self.form.showPWCheck.isChecked()
    if doShow:
        # if password was saved in the config, don't let it be shown in
        # plaintext for some basic security
        if self.passwordWasLoaded:
            r = utils.questionBox("For security reasons, you cannot view "
                    "any part of a saved password. Would you like to erase "
                    "the saved password?", "Erase Password")
            if r == QMessageBox.Yes:
                self.form.passwordBox.setText("")
                self.passwordWasLoaded = False
            else:
                self.form.showPWCheck.setChecked(False)
                return
        self.form.passwordBox.setEchoMode(QLineEdit.Normal)
    else:
        self.form.passwordBox.setEchoMode(QLineEdit.Password)

I also added and connected a method called self.unsetPasswordWasLoaded, which sets the flag to False if all of the content in the password field is erased.

This works great. Unfortunately, a user can now view the password by erasing all of the content, ticking the box, and then doing an Undo. (It's not possible to do an undo when the echoMode is set to QLineEdit.Password, but when you change it back to normal the undo history persists from before.)

Upvotes: 0

Views: 456

Answers (1)

Tim Wakeham
Tim Wakeham

Reputation: 1039

You need to override how the context menu is created so you can disable to Undo option. If you store state elsewhere you can chose whether to enable or disable the undo option depending on that state. A very basic example -

# context menu requested signal handler
def display_context_menu(point):
    global line_edit

    # grab the standard context menu
    menu = line_edit.createStandardContextMenu()

    # search through the actions i nthe menu to find undo
    for action in menu.actions():
        if action.text() == '&Undo  Ctrl+Z':
            # set undo as disabled
            action.setDisabled(True)
            break

    # show context menu where we right clicked
    menu.exec_(line_edit.mapToGlobal(point))


# create line edit, set it to use a custom context menu, connect the context menu signal to handler
line_edit = QLineEdit()
line_edit.setContextMenuPolicy(Qt.CustomContextMenu)
line_edit.customContextMenuRequested.connect(display_context_menu)

line_edit.show()

Upvotes: 1

Related Questions