mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-08-11 09:13:57 -04:00
Edit book: Add a tool to view the list of checkpoints and compare the current state of the book to the state at the specified checkpoint.
This commit is contained in:
parent
4763a8f9af
commit
83d0b29938
@ -109,6 +109,8 @@ class Boss(QObject):
|
||||
self.gui.check_book.fix_requested.connect(self.fix_requested)
|
||||
self.gui.toc_view.navigate_requested.connect(self.link_clicked)
|
||||
self.gui.image_browser.image_activated.connect(self.image_activated)
|
||||
self.gui.checkpoints.revert_requested.connect(self.revert_requested)
|
||||
self.gui.checkpoints.compare_requested.connect(self.compare_requested)
|
||||
|
||||
def preferences(self):
|
||||
p = Preferences(self.gui)
|
||||
@ -411,11 +413,11 @@ class Boss(QObject):
|
||||
d.line_activated.connect(line_activated)
|
||||
return d
|
||||
|
||||
def show_current_diff(self, allow_revert=True):
|
||||
def show_current_diff(self, allow_revert=True, to_container=None):
|
||||
self.commit_all_editors_to_container()
|
||||
d = self.create_diff_dialog()
|
||||
d.revert_requested.connect(partial(self.revert_requested, self.global_undo.previous_container))
|
||||
d.container_diff(self.global_undo.previous_container, self.global_undo.current_container)
|
||||
d.container_diff(to_container or self.global_undo.previous_container, self.global_undo.current_container)
|
||||
|
||||
def compare_book(self):
|
||||
self.commit_all_editors_to_container()
|
||||
@ -434,6 +436,9 @@ class Boss(QObject):
|
||||
set_current_container(nc)
|
||||
self.apply_container_update_to_gui()
|
||||
|
||||
def compare_requested(self, container):
|
||||
self.show_current_diff(to_container=container)
|
||||
|
||||
# Renaming {{{
|
||||
|
||||
def rationalize_folders(self):
|
||||
|
@ -25,6 +25,7 @@ from calibre.gui2.tweak_book import current_container, tprefs, actions, capitali
|
||||
from calibre.gui2.tweak_book.file_list import FileListWidget
|
||||
from calibre.gui2.tweak_book.job import BlockingJob
|
||||
from calibre.gui2.tweak_book.boss import Boss
|
||||
from calibre.gui2.tweak_book.undo import CheckpointView
|
||||
from calibre.gui2.tweak_book.preview import Preview
|
||||
from calibre.gui2.tweak_book.search import SearchPanel
|
||||
from calibre.gui2.tweak_book.check import Check
|
||||
@ -581,6 +582,13 @@ class Main(MainWindow):
|
||||
d.close() # Hidden by default
|
||||
d.visibilityChanged.connect(self.toc_view.visibility_changed)
|
||||
|
||||
d = create(_('Checkpoints'), 'checkpoints')
|
||||
d.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea | Qt.BottomDockWidgetArea | Qt.TopDockWidgetArea)
|
||||
self.checkpoints = CheckpointView(self.boss.global_undo, parent=d)
|
||||
d.setWidget(self.checkpoints)
|
||||
self.addDockWidget(Qt.LeftDockWidgetArea, d)
|
||||
d.close() # Hidden by default
|
||||
|
||||
def resizeEvent(self, ev):
|
||||
self.blocking_job.resize(ev.size())
|
||||
return super(Main, self).resizeEvent(ev)
|
||||
|
@ -8,6 +8,14 @@ __copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
|
||||
import shutil
|
||||
|
||||
from PyQt4.Qt import (
|
||||
QAbstractListModel, Qt, QModelIndex, QVariant, QApplication, QWidget,
|
||||
QGridLayout, QListView, QStyledItemDelegate, pyqtSignal, QPushButton, QIcon)
|
||||
|
||||
from calibre.gui2 import NONE, error_dialog
|
||||
|
||||
ROOT = QModelIndex()
|
||||
|
||||
MAX_SAVEPOINTS = 100
|
||||
|
||||
def cleanup(containers):
|
||||
@ -23,12 +31,35 @@ class State(object):
|
||||
self.container = container
|
||||
self.message = None
|
||||
|
||||
class GlobalUndoHistory(object):
|
||||
class GlobalUndoHistory(QAbstractListModel):
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self, parent=None):
|
||||
QAbstractListModel.__init__(self, parent)
|
||||
self.states = []
|
||||
self.pos = 0
|
||||
|
||||
def rowCount(self, parent=ROOT):
|
||||
return len(self.states)
|
||||
|
||||
def data(self, index, role=Qt.DisplayRole):
|
||||
if role == Qt.DisplayRole:
|
||||
row = index.row()
|
||||
msg = self.states[row].message
|
||||
if self.pos == row:
|
||||
msg = _('Current state')
|
||||
elif not msg:
|
||||
msg = _('[Unnamed state]')
|
||||
else:
|
||||
msg = _('Before %s') % msg
|
||||
return QVariant(msg)
|
||||
if role == Qt.FontRole and index.row() == self.pos:
|
||||
f = QApplication.instance().font()
|
||||
f.setBold(True)
|
||||
return QVariant(f)
|
||||
if role == Qt.UserRole:
|
||||
return QVariant(self.states[index.row()])
|
||||
return NONE
|
||||
|
||||
@property
|
||||
def current_container(self):
|
||||
return self.states[self.pos].container
|
||||
@ -40,6 +71,7 @@ class GlobalUndoHistory(object):
|
||||
def open_book(self, container):
|
||||
self.states = [State(container)]
|
||||
self.pos = 0
|
||||
self.reset()
|
||||
|
||||
def add_savepoint(self, new_container, message):
|
||||
try:
|
||||
@ -48,14 +80,23 @@ class GlobalUndoHistory(object):
|
||||
raise IndexError('The checkpoint stack has an incorrect position pointer. This should never happen: self.pos = %r, len(self.states) = %r' % (
|
||||
self.pos, len(self.states)))
|
||||
extra = self.states[self.pos+1:]
|
||||
if extra:
|
||||
self.beginRemoveRows(ROOT, self.pos+1, len(self.states) - 1)
|
||||
cleanup(extra)
|
||||
self.states = self.states[:self.pos+1]
|
||||
if extra:
|
||||
self.endRemoveRows()
|
||||
self.beginInsertRows(ROOT, self.pos+1, self.pos+1)
|
||||
self.states.append(State(new_container))
|
||||
self.pos += 1
|
||||
self.endInsertRows()
|
||||
self.dataChanged.emit(self.index(self.pos-1), self.index(self.pos))
|
||||
if len(self.states) > MAX_SAVEPOINTS:
|
||||
num = len(self.states) - MAX_SAVEPOINTS
|
||||
self.beginRemoveRows(ROOT, 0, num - 1)
|
||||
cleanup(self.states[:num])
|
||||
self.states = self.states[num:]
|
||||
self.endRemoveRows()
|
||||
|
||||
def rewind_savepoint(self):
|
||||
''' Revert back to the last save point, should only be used immediately
|
||||
@ -64,8 +105,11 @@ class GlobalUndoHistory(object):
|
||||
where you create savepoint, perform some operation, operation fails, so
|
||||
revert to state before creating savepoint. '''
|
||||
if self.pos > 0 and self.pos == len(self.states) - 1:
|
||||
self.beginRemoveRows(ROOT, self.pos, self.pos)
|
||||
self.pos -= 1
|
||||
cleanup([self.states.pop().container])
|
||||
self.endRemoveRows()
|
||||
self.dataChanged.emit(self.index(self.pos))
|
||||
ans = self.current_container
|
||||
ans.message = None
|
||||
return ans
|
||||
@ -73,17 +117,22 @@ class GlobalUndoHistory(object):
|
||||
def undo(self):
|
||||
if self.pos > 0:
|
||||
self.pos -= 1
|
||||
self.dataChanged.emit(self.index(self.pos), self.index(self.pos+1))
|
||||
return self.current_container
|
||||
|
||||
def redo(self):
|
||||
if self.pos < len(self.states) - 1:
|
||||
self.pos += 1
|
||||
self.dataChanged.emit(self.index(self.pos-1), self.index(self.pos))
|
||||
return self.current_container
|
||||
|
||||
def revert_to(self, container):
|
||||
for i, state in enumerate(self.states):
|
||||
if state.container is container:
|
||||
opos = self.pos
|
||||
self.pos = i
|
||||
for x in (i, opos):
|
||||
self.dataChanged.emit(self.index(x), self.index(x))
|
||||
return container
|
||||
|
||||
@property
|
||||
@ -106,3 +155,69 @@ class GlobalUndoHistory(object):
|
||||
return ''
|
||||
return self.states[self.pos].message or ''
|
||||
|
||||
class SpacedDelegate(QStyledItemDelegate):
|
||||
|
||||
def sizeHint(self, *args):
|
||||
ans = QStyledItemDelegate.sizeHint(self, *args)
|
||||
ans.setHeight(ans.height() + 4)
|
||||
return ans
|
||||
|
||||
class CheckpointView(QWidget):
|
||||
|
||||
revert_requested = pyqtSignal(object)
|
||||
compare_requested = pyqtSignal(object)
|
||||
|
||||
def __init__(self, model, parent=None):
|
||||
QWidget.__init__(self, parent)
|
||||
self.l = l = QGridLayout(self)
|
||||
self.setLayout(l)
|
||||
self.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
self.view = v = QListView(self)
|
||||
self.d = SpacedDelegate(v)
|
||||
v.doubleClicked.connect(self.double_clicked)
|
||||
v.setItemDelegate(self.d)
|
||||
v.setModel(model)
|
||||
l.addWidget(v, 0, 0, 1, -1)
|
||||
model.dataChanged.connect(self.data_changed)
|
||||
|
||||
self.rb = b = QPushButton(QIcon(I('edit-undo.png')), _('&Revert to'), self)
|
||||
b.setToolTip(_('Revert the book to the selected checkpoint'))
|
||||
b.clicked.connect(self.revert_clicked)
|
||||
l.addWidget(b, 1, 1)
|
||||
|
||||
self.cb = b = QPushButton(QIcon(I('diff.png')), _('&Compare'), self)
|
||||
b.setToolTip(_('Compare the state of the book at the selected checkpoint with the current state'))
|
||||
b.clicked.connect(self.compare_clicked)
|
||||
l.addWidget(b, 1, 0)
|
||||
|
||||
def data_changed(self, *args):
|
||||
self.view.clearSelection()
|
||||
m = self.view.model()
|
||||
sm = self.view.selectionModel()
|
||||
sm.select(m.index(m.pos), sm.ClearAndSelect)
|
||||
self.view.setCurrentIndex(m.index(m.pos))
|
||||
|
||||
def double_clicked(self, index):
|
||||
pass # Too much danger of accidental double click
|
||||
|
||||
def revert_clicked(self):
|
||||
m = self.view.model()
|
||||
row = self.view.currentIndex().row()
|
||||
if row < 0:
|
||||
return
|
||||
if row == m.pos:
|
||||
return error_dialog(self, _('Cannot revert'), _(
|
||||
'Cannot revert to the current state'), show=True)
|
||||
self.revert_requested.emit(m.states[row].container)
|
||||
|
||||
def compare_clicked(self):
|
||||
m = self.view.model()
|
||||
row = self.view.currentIndex().row()
|
||||
if row < 0:
|
||||
return
|
||||
if row == m.pos:
|
||||
return error_dialog(self, _('Cannot compare'), _(
|
||||
'There is no point comparing the current state to itself'), show=True)
|
||||
self.compare_requested.emit(m.states[row].container)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user