Results 1 to 2 of 2

Thread: Trouble when re-implementing undo framework for QLineEdits

  1. #1
    Join Date
    Mar 2014
    Posts
    6
    Qt products
    Qt4
    Platforms
    Windows

    Default Trouble when re-implementing undo framework for QLineEdits

    I have created a custom line edit widget so that I can incorporate undo/redo commands on it into my application's general undo stack (rather than use the built-in undo/redo facilities that come with QLineEdit widgets). The undo/redo logic is fairly straightforward: when the line edit widget receives focus, its content is immediately assigned to an instance variable (self.init_text); and when the line edit widget loses focus, if the text content differs from that stored in self.init_text, then a new QUndoCommand object is created. The undo() method will re-set the content to whatever is in self.init_text, while the redo() method will re-set the content to whatever was captured when the line edit widget lost focus. (In either method, the line edit will again receive focus so that it will be obvious to the user what the undo or redo command actually affected.)

    It seems to work just fine with one exception: if the user very quickly cycles through undo or redo commands through the QPushButtons, then the framework just breaks. I can't describe it much better than that because I'm not sure what's going on under the Qt hood, but, for example, the count() of the QUndoStack may be changed completely. The app continues to run with no errors reported on the terminal, but it is nonetheless a broken undo stack.

    I have created a little QDialog app so you can try to re-create the issue. (Using Python 2.7.3/PySide 1.2.1 ... if you have a recent PyQt binding installed, I don't think you should need to replace anything except the first two import statements.) For example, in the first tab's QLineEdit, if you type 'hello', then tab out, then click back in and type 'world', then tab out again, try very swiftly clicking the undo button (down to and beyond the bottom of the undo stack) and redo button (up to and beyond the top of the undo stack). For me, that was enough to break it.

    Qt Code:
    1. #!/usr/bin/python
    2. #coding=utf-8
    3. from PySide.QtCore import *
    4. from PySide.QtGui import *
    5. import sys
    6.  
    7. class CustomRightClick(QObject):
    8.  
    9. customRightClicked = Signal()
    10.  
    11. def __init__(self, parent=None):
    12. QObject.__init__(self, parent)
    13.  
    14. def eventFilter(self, obj, event):
    15. if event.type() == QEvent.ContextMenu:
    16. # emit signal so that your widgets can connect a slot to that signal
    17. self.customRightClicked.emit()
    18. return True
    19. else:
    20. # standard event processing
    21. return QObject.eventFilter(self, obj, event)
    22.  
    23. class CommandLineEdit(QUndoCommand):
    24.  
    25. def __init__(self, line_edit, init_text, tab_widget, tab_index, description):
    26. QUndoCommand.__init__(self, description)
    27. self._line_edit = line_edit
    28. self._current_text = line_edit.text()
    29. self._init_text = init_text
    30. self._tab_widget = tab_widget
    31. self._tab_index = tab_index
    32.  
    33. def undo(self):
    34. self._line_edit.setText(self._init_text)
    35. self._tab_widget.setCurrentIndex(self._tab_index)
    36. self._line_edit.setFocus(Qt.OtherFocusReason)
    37.  
    38. def redo(self):
    39. self._line_edit.setText(self._current_text)
    40. self._tab_widget.setCurrentIndex(self._tab_index)
    41. self._line_edit.setFocus(Qt.OtherFocusReason)
    42.  
    43. class CustomLineEdit(QLineEdit):
    44.  
    45. def __init__(self, parent, tab_widget, tab_index):
    46. super(CustomLineEdit, self).__init__(parent)
    47. self.parent = parent
    48. self.tab_widget = tab_widget
    49. self.tab_index = tab_index
    50. self.init_text = self.text()
    51. self.setContextMenuPolicy(Qt.CustomContextMenu)
    52.  
    53. undoAction=QAction("Undo", self)
    54. undoAction.triggered.connect(self.parent.undo_stack.undo)
    55.  
    56. self.customContextMenu = QMenu()
    57. self.customContextMenu.addAction(undoAction)
    58.  
    59. custom_clicker = CustomRightClick(self)
    60. self.installEventFilter(custom_clicker)
    61. self.right_clicked = False
    62. custom_clicker.customRightClicked.connect(self.menuShow)
    63.  
    64. def menuShow(self):
    65. self.right_clicked = True # set self.right_clicked to True so that the focusOutEvent won't push anything to the undo stack as a consequence of right-clicking
    66. self.customContextMenu.popup(QCursor.pos())
    67. self.right_clicked = False
    68.  
    69. # re-implement focusInEvent() so that it captures as an instance variable the current value of the text *at the time of the focusInEvent(). This will be utilized for the undo stack command push below
    70. def focusInEvent(self, event):
    71. self.init_text = self.text()
    72. QLineEdit.focusInEvent(self, event)
    73.  
    74. # re-implement focusOutEvent() so that it pushes the current text to the undo stack.... but only if there was a change!
    75. def focusOutEvent(self, event):
    76. if self.text() != self.init_text and not self.right_clicked:
    77. print "Focus out event. (self.text is %s and init_text is %s). Pushing onto undo stack. (Event reason is %s)" % (self.text(), self.init_text, event.reason())
    78. command = CommandLineEdit(self, self.init_text, self.tab_widget, self.tab_index, "editing a text box")
    79. self.parent.undo_stack.push(command)
    80. QLineEdit.focusOutEvent(self, event)
    81.  
    82. def keyPressEvent(self, event):
    83. if event.key() == Qt.Key_Z:
    84. if event.modifiers() & Qt.ControlModifier:
    85. self.parent.undo_stack.undo()
    86. else:
    87. QLineEdit.keyPressEvent(self, event)
    88. elif event.key() == Qt.Key_Y:
    89. if event.modifiers() & Qt.ControlModifier:
    90. self.parent.undo_stack.redo()
    91. else:
    92. QLineEdit.keyPressEvent(self, event)
    93. else:
    94. QLineEdit.keyPressEvent(self, event)
    95.  
    96. class Form(QDialog):
    97.  
    98. def __init__(self, parent=None):
    99. super(Form, self).__init__(parent)
    100.  
    101. self.undo_stack = QUndoStack()
    102.  
    103. self.tab_widget = QTabWidget()
    104.  
    105. self.line_edit1 = CustomLineEdit(self, self.tab_widget, 0)
    106. self.line_edit2 = CustomLineEdit(self, self.tab_widget, 1)
    107. self.undo_counter = QLineEdit()
    108.  
    109. tab1widget = QWidget()
    110. tab1layout = QHBoxLayout()
    111. tab1layout.addWidget(self.line_edit1)
    112. tab1widget.setLayout(tab1layout)
    113.  
    114. tab2widget = QWidget()
    115. tab2layout = QHBoxLayout()
    116. tab2layout.addWidget(self.line_edit2)
    117. tab2widget.setLayout(tab2layout)
    118.  
    119. self.tab_widget.addTab(tab1widget, "Tab 1")
    120. self.tab_widget.addTab(tab2widget, "Tab 2")
    121.  
    122. self.undo_button = QPushButton("Undo")
    123. self.redo_button = QPushButton("Redo")
    124. layout = QGridLayout()
    125. layout.addWidget(self.tab_widget, 0, 0, 1, 2)
    126. layout.addWidget(self.undo_button, 1, 0)
    127. layout.addWidget(self.redo_button, 1, 1)
    128. layout.addWidget(QLabel("Undo Stack Counter"), 2, 0)
    129. layout.addWidget(self.undo_counter)
    130. self.setLayout(layout)
    131.  
    132. self.undo_button.clicked.connect(self.undo_stack.undo)
    133. self.redo_button.clicked.connect(self.undo_stack.redo)
    134. self.undo_stack.indexChanged.connect(self.changeUndoCount)
    135.  
    136. def changeUndoCount(self, index):
    137. self.undo_counter.setText("%s / %s" % (index, self.undo_stack.count()))
    138.  
    139. app = QApplication(sys.argv)
    140. form = Form()
    141. form.show()
    142. app.exec_()
    To copy to clipboard, switch view to plain text mode 

    Is this a Qt bug? A PySide bug? Or is there a problem in my re-implementation? Any help is appreciated!

  2. #2
    Join Date
    Jan 2006
    Location
    Warsaw, Poland
    Posts
    33,359
    Thanks
    3
    Thanked 5,015 Times in 4,792 Posts
    Qt products
    Qt3 Qt4 Qt5 Qt/Embedded
    Platforms
    Unix/X11 Windows Android Maemo/MeeGo
    Wiki edits
    10

    Default Re: Trouble when re-implementing undo framework for QLineEdits

    I'd suggest modifying your implementation. Instead of reacting on focus events, react on textChanged() or textEdited() signals, it should be much easier than tracking focus events.
    Your biological and technological distinctiveness will be added to our own. Resistance is futile.

    Please ask Qt related questions on the forum and not using private messages or visitor messages.


Similar Threads

  1. Replies: 0
    Last Post: 21st January 2014, 07:05
  2. Syncing QTextDocument's undo stack with custom undo stack
    By Dini Selimović in forum Newbie
    Replies: 0
    Last Post: 24th June 2012, 14:11
  3. Idea for sorting values from QLineEdits?
    By ewwen in forum Newbie
    Replies: 10
    Last Post: 26th May 2011, 09:45
  4. Trouble implementing a linked list.
    By Bonafide in forum Qt Programming
    Replies: 3
    Last Post: 21st February 2010, 22:13
  5. Qt Property Browser Framework - Redo/Undo
    By monadic_kid in forum Qt Programming
    Replies: 2
    Last Post: 3rd December 2009, 11:46

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  
Digia, Qt and their respective logos are trademarks of Digia Plc in Finland and/or other countries worldwide.