Files
lintunes/lintunes/undo.py
Trav f6d35fa594 Import existing LinTunes project
Snapshot of the existing codebase before working through the TASKS.md
backlog. Real library data (data/) and the iTunes import fixture
(itunes-test-library/) are gitignored.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 21:12:01 -04:00

72 lines
1.9 KiB
Python

"""A tiny closure-based undo/redo stack.
Each Command stores a human label plus two zero-arg callables — one to undo the
change and one to redo it. The change is assumed to have already been applied by
the caller, so push() only records it. The stack keeps a bounded history (10
levels by default); pushing past the limit silently drops the oldest entry.
"""
import collections
from PyQt6.QtCore import QObject, pyqtSignal
class Command:
def __init__(self, label: str, undo, redo):
self.label = label
self._undo = undo
self._redo = redo
def undo(self):
self._undo()
def redo(self):
self._redo()
class UndoStack(QObject):
changed = pyqtSignal()
def __init__(self, maxlen: int = 10, parent=None):
super().__init__(parent)
self._undo: collections.deque[Command] = collections.deque(maxlen=maxlen)
self._redo: list[Command] = []
def push(self, command: Command):
"""Record an already-applied change; clears the redo branch."""
self._undo.append(command)
self._redo.clear()
self.changed.emit()
def undo(self):
if not self._undo:
return
command = self._undo.pop()
command.undo()
self._redo.append(command)
self.changed.emit()
def redo(self):
if not self._redo:
return
command = self._redo.pop()
command.redo()
self._undo.append(command)
self.changed.emit()
def can_undo(self) -> bool:
return bool(self._undo)
def can_redo(self) -> bool:
return bool(self._redo)
def undo_label(self) -> str:
return self._undo[-1].label if self._undo else ""
def redo_label(self) -> str:
return self._redo[-1].label if self._redo else ""
def clear(self):
self._undo.clear()
self._redo.clear()
self.changed.emit()