~adeodato/ code/ minirok/ csl2.blog/ entries/ 2008/ 02/ 22/ Playlist (4): hacer y deshacer

Playlist (4): hacer y deshacer

Una de las funcionalidades que resultaba muy difícil implementar en la versión anterior de Minirok era hacer y deshacer: al no estar las estructuras internas de la lista bajo nuestro control, tendríamos que haber añadido unas estructuras paralelas que mantener sincronizadas y que reflejaran los distintos estados por los que pasara la lista.

Con Qt4, y al usar el paradigma modelo/vista, sabía que implementar hacer y desacer para la lista tenía que ser al menos posible, sobre todo si (como he hecho) tenía en cuenta este requerimiento desde el principio. Lo que no me esperaba era que fuera tan (relativamente) fácil, gracias al Undo Framework de Qt4. En este modelo, cada modificación al estado de la lista se representa por un objeto, que sabe cómo deshacer su acción, y cómo rehacerla. Qt se encarga de todo lo demás.


Como expliqué en una de las entradas anteriores, el modelo ofrece dos métodos insert_items() y remove_items() para realizar modificaciones a la lista. Estas funciones son de funcionalidad limitada: por ejemplo sólo se pueden eliminar ítems contiguos en una misma llamada, y no tocan para nada la cola de reproducción.

Así, los objetos que representan alteraciones a la lista serán finos wrappers alrededor de estas dos funciones, con código para handlear los dos casos mencionados en el párrafo anterior.

Comencé con dos clases, InsertItemsCmd y RemoveItemsCmd, ambas derivadas de QUndoCommand. Como su funcionalidad es simétrica (es decir, el undo() de una hace lo mismo que que el redo() de la otra, y viceversa), he factorizado todo el código a un mixin, dejando la implementación de las clases comando en algo tan simple como:

  class InsertItemsCmd(QtGui.QUndoCommand, AlterItemlistMixin):
      undo = AlterItemlistMixin.remove_items
      redo = AlterItemlistMixin.insert_items

  class RemoveItemsCmd(QtGui.QUndoCommand, AlterItemlistMixin):
      undo = AlterItemlistMixin.insert_items
      redo = AlterItemlistMixin.remove_items

Con estas dos clases es posible representar cualquier modificación a la lista, incluido el drag and drop interno, creando una macro de dos comandos (es decir, una unidad indivisible): un RemoveItemsCmd primero, y un InsertItemsCmd que inserta los ítems eliminados por el primero.


Hay un caso particular de operación: eliminar todos los ítems de la lista (“clear playlist”). Aunque se puede expresar en términos de un RemoveItemsCmd (y una sola llamada a remove_items() en el modelo), hacerlo así resulta un poco ineficiente (ya que estas funciones iteran sobre todos los items eliminados).

Como es muy deseable que esta operación sea rápida, he creado una nueva función en el modelo, clear_itemlist(), como caso particular de remove_items(), pero más eficiente. Asimismo, he creado un nuevo comando ClearItemlistCmd, que hereda del mixin pero que proporciona su propia implementación de remove_items() (el método insert_items() original se conserva, pues no necesita modificaciones).

Esto último lo he hecho hoy mismo en la revisión 560 (diff).