PDA

View Full Version : Undo edit of QListWidgetItem?



neuronet
14th March 2015, 16:49
Note this is a repost of a question at Stack Overflow that I just put a bounty on:
http://stackoverflow.com/questions/28954565/how-to-undo-an-edit-of-a-qlistwidgetitem-in-qt-pyqt

Short version

How do you implement undo functionality for edits made on QListWidgetItems?

Details

I am using a QListWidget to learn my way around PyQt's Undo Framework (with the help of an article on the topic: http://www.informit.com/articles/article.aspx?p=1187104). I am fine with undo/redo when I implement a command myself (like deleting an item from the list). Below is a full code example that shows a list, lets you delete items,and undo/redo such deletions. The application displays the list on the right, and the undo stack on the left.

The problem is, I also want to make the QListWidgetItems in the widget editable. This is easy enough: just add the ItemIsEditable flag to each item. But how do I push such edits onto the undo stack, so I can then undo/redo them?

Simple working example

from PySide import QtGui, QtCore

class TodoList(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
self.initUI()
self.show()

def initUI(self):
self.todoList = self.makeTodoList()
self.undoStack = QtGui.QUndoStack(self)
undoView = QtGui.QUndoView(self.undoStack)
buttonLayout = self.buttonSetup()
mainLayout = QtGui.QHBoxLayout(self)
mainLayout.addWidget(undoView)
mainLayout.addWidget(self.todoList)
mainLayout.addLayout(buttonLayout)
self.setLayout(mainLayout)
self.makeConnections()

def buttonSetup(self):
#Make buttons
self.deleteButton = QtGui.QPushButton("Delete")
self.undoButton = QtGui.QPushButton("Undo")
self.redoButton = QtGui.QPushButton("Redo")
self.quitButton = QtGui.QPushButton("Quit")
#Lay them out
buttonLayout = QtGui.QVBoxLayout()
buttonLayout.addWidget(self.deleteButton)
buttonLayout.addStretch()
buttonLayout.addWidget(self.undoButton)
buttonLayout.addWidget(self.redoButton)
buttonLayout.addStretch()
buttonLayout.addWidget(self.quitButton)
return buttonLayout

def makeConnections(self):
self.deleteButton.clicked.connect(self.deleteItem)
self.quitButton.clicked.connect(self.close)
self.undoButton.clicked.connect(self.undoStack.und o)
self.redoButton.clicked.connect(self.undoStack.red o)

def deleteItem(self):
rowSelected=self.todoList.currentRow()
rowItem = self.todoList.item(rowSelected)
if rowItem is None:
return
command = CommandDelete(self.todoList, rowItem, rowSelected,
"Delete item '{0}'".format(rowItem.text()))
self.undoStack.push(command)

def makeTodoList(self):
todoList = QtGui.QListWidget()
allTasks = ('Fix door', 'Make dinner', 'Read',
'Program in PySide', 'Be nice to everyone')
for task in allTasks:
todoItem=QtGui.QListWidgetItem(task)
todoList.addItem(todoItem)
todoItem.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
return todoList


class CommandDelete(QtGui.QUndoCommand):
def __init__(self, listWidget, item, row, description):
super(CommandDelete, self).__init__(description)
self.listWidget = listWidget
self.string = item.text()
self.row = row

def redo(self):
self.listWidget.takeItem(self.row)

def undo(self):
addItem = QtGui.QListWidgetItem(self.string)
addItem.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
self.listWidget.insertItem(self.row, addItem)

if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
myList=TodoList()
sys.exit(app.exec_())

wysota
14th March 2015, 16:59
Have you seen Using Undo/Redo with Item Views?

neuronet
14th March 2015, 18:45
Have you seen Using Undo/Redo with Item Views?

Yes about a week ago, and was very excited to see it (I posted here about it: http://www.qtcentre.org/threads/61468-QStandardItemModel-Undo?highlight=undo+model). ;) ) Unfortunately I got utterly confused in the sudoku model by the c++. So I suppose what I am trying to do falls under the following in your article:

Implementing such a pattern is a minor effort in most cases. However, main drawback is that if something calls your model directly without interfacing through the undo/redo framework, the undo stack will lose coherence and won't be able to bring the model back to a desired state---some changes will be ignored and some will be overwritten with those stored in the undo stack.

That said, I got lost before implementing the model above in my original post, so perhaps now I will understand it better and will be able to follow your c++ code. Would be great to translate your example to PyQt. I am not sure I am able, unfortunately.

wysota
14th March 2015, 23:52
You can repeat the process for a much simpler model, e.g. one containing a list of strings which you can add, remove or modify.

neuronet
15th March 2015, 02:36
Wysota yes that is the goal. I did it first for the simple case because of the article already available for PyQt. I will also try to do it with this simple example using the technique you recommend. I'll try to translate to PyQt/PySide and post somewhere...

neuronet
22nd March 2015, 15:50
Follow-up: I got a very instructive answer to this question at SO:
http://stackoverflow.com/a/29166218/1886357