diff --git a/src/calibre/gui2/tweak_book/boss.py b/src/calibre/gui2/tweak_book/boss.py new file mode 100644 index 0000000000..7cbc6a25fe --- /dev/null +++ b/src/calibre/gui2/tweak_book/boss.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python +# vim:fileencoding=utf-8 +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'GPL v3' +__copyright__ = '2013, Kovid Goyal ' + +import tempfile, shutil + +from PyQt4.Qt import QObject + +from calibre.gui2 import error_dialog +from calibre.ptempfile import PersistentTemporaryDirectory +from calibre.ebooks.oeb.polish.main import SUPPORTED +from calibre.ebooks.oeb.polish.container import get_container, clone_container +from calibre.gui2.tweak_book import set_current_container, current_container +from calibre.gui2.tweak_book.undo import GlobalUndoHistory + +class Boss(QObject): + + def __init__(self, parent=None): + QObject.__init__(self, parent) + self.global_undo = GlobalUndoHistory() + self.container_count = 0 + self.tdir = None + + def __call__(self, gui): + self.gui = gui + gui.file_list.delete_requested.connect(self.delete_requested) + + def mkdtemp(self): + self.container_count += 1 + return tempfile.mkdtemp(prefix='%05d-' % self.container_count, dir=self.tdir) + + def check_dirtied(self): + # TODO: Implement this + return True + + def open_book(self, path): + ext = path.rpartition('.')[-1].upper() + if ext not in SUPPORTED: + return error_dialog(self.gui, _('Unsupported format'), + _('Tweaking is only supported for books in the %s formats.' + ' Convert your book to one of these formats first.') % _(' and ').join(sorted(SUPPORTED)), + show=True) + + if not self.check_dirtied(): + return + + self.container_count = -1 + if self.tdir: + shutil.rmtree(self.tdir, ignore_errors=True) + self.tdir = PersistentTemporaryDirectory() + self.gui.blocking_job('open_book', _('Opening book, please wait...'), self.book_opened, get_container, path, tdir=self.mkdtemp()) + + def book_opened(self, job): + if job.traceback is not None: + return error_dialog(self.gui, _('Failed to open book'), + _('Failed to open book, click Show details for more information.'), + det_msg=job.traceback, show=True) + container = job.result + set_current_container(container) + self.current_metadata = self.gui.current_metadata = container.mi + self.global_undo.open_book(container) + self.gui.update_window_title() + self.gui.file_list.build(container) + + def add_savepoint(self, msg): + nc = clone_container(current_container(), self.mkdtemp()) + self.global_undo.add_savepoint(nc, msg) + set_current_container(nc) + + def delete_requested(self, spine_items, other_items): + if not self.check_dirtied(): + return + self.add_savepoint(_('Delete files')) + c = current_container() + c.remove_from_spine(spine_items) + for name in other_items: + c.remove_item(name) + self.gui.file_list.delete_done(spine_items, other_items) + + diff --git a/src/calibre/gui2/tweak_book/file_list.py b/src/calibre/gui2/tweak_book/file_list.py index 9c210f8872..b7f1dce6d9 100644 --- a/src/calibre/gui2/tweak_book/file_list.py +++ b/src/calibre/gui2/tweak_book/file_list.py @@ -260,7 +260,7 @@ class FileList(QTreeWidget): removals.append(child) for c in removals: - c.parent().removeChild(c.parent().indexOfChild(c)) + c.parent().removeChild(c) class FileListWidget(QWidget): @@ -274,6 +274,8 @@ class FileListWidget(QWidget): self.layout().setContentsMargins(0, 0, 0, 0) for x in ('delete_requested',): getattr(self.file_list, x).connect(getattr(self, x)) + for x in ('delete_done',): + setattr(self, x, getattr(self.file_list, x)) def build(self, container): self.file_list.build(container) diff --git a/src/calibre/gui2/tweak_book/main.py b/src/calibre/gui2/tweak_book/main.py index 57abb2b786..be5aca6eb8 100644 --- a/src/calibre/gui2/tweak_book/main.py +++ b/src/calibre/gui2/tweak_book/main.py @@ -40,7 +40,7 @@ def main(args=sys.argv): sys.excepthook = main.unhandled_exception main.show() if len(args) > 1: - main.open_book(args[1]) + main.boss.open_book(args[1]) app.exec_() if __name__ == '__main__': diff --git a/src/calibre/gui2/tweak_book/ui.py b/src/calibre/gui2/tweak_book/ui.py index 1f6a2055a4..a6e2c6c852 100644 --- a/src/calibre/gui2/tweak_book/ui.py +++ b/src/calibre/gui2/tweak_book/ui.py @@ -6,23 +6,13 @@ from __future__ import (unicode_literals, division, absolute_import, __license__ = 'GPL v3' __copyright__ = '2013, Kovid Goyal ' -import shutil, tempfile - from PyQt4.Qt import QDockWidget, Qt, QLabel, QIcon -from calibre.ebooks.oeb.polish.container import get_container -from calibre.ebooks.oeb.polish.main import SUPPORTED -from calibre.ptempfile import PersistentTemporaryDirectory -from calibre.gui2 import error_dialog from calibre.gui2.main_window import MainWindow -from calibre.gui2.tweak_book import set_current_container, current_container +from calibre.gui2.tweak_book import current_container from calibre.gui2.tweak_book.file_list import FileListWidget from calibre.gui2.tweak_book.job import BlockingJob -from calibre.gui2.tweak_book.undo import GlobalUndoHistory - -def load_book(path_to_ebook, base_tdir): - tdir = tempfile.mkdtemp(dir=base_tdir) - return get_container(path_to_ebook, tdir=tdir) +from calibre.gui2.tweak_book.boss import Boss class Main(MainWindow): @@ -30,13 +20,13 @@ class Main(MainWindow): def __init__(self, opts): MainWindow.__init__(self, opts, disable_automatic_gc=True) + self.boss = Boss(self) self.setWindowTitle(self.APP_NAME) self.setWindowIcon(QIcon(I('tweak.png'))) self.opts = opts - self.tdir = None self.path_to_ebook = None self.container = None - self.global_undo = GlobalUndoHistory() + self.current_metadata = None self.blocking_job = BlockingJob(self) self.file_list_dock = d = QDockWidget(_('&Files Browser'), self) @@ -49,37 +39,11 @@ class Main(MainWindow): self.l = QLabel('Placeholder') self.setCentralWidget(self.l) + self.boss(self) def resizeEvent(self, ev): self.blocking_job.resize(ev.size()) return super(Main, self).resizeEvent(ev) - def open_book(self, path): - ext = path.rpartition('.')[-1].upper() - if ext not in SUPPORTED: - return error_dialog(self, _('Unsupported format'), - _('Tweaking is only supported for books in the %s formats.' - ' Convert your book to one of these formats first.') % _(' and ').join(sorted(SUPPORTED)), - show=True) - - # TODO: Handle already open, dirtied book - - if self.tdir: - shutil.rmtree(self.tdir, ignore_errors=True) - self.tdir = PersistentTemporaryDirectory() - self.blocking_job('open_book', _('Opening book, please wait...'), self.book_opened, load_book, path, self.tdir) - - def book_opened(self, job): - if job.traceback is not None: - return error_dialog(self, _('Failed to open book'), - _('Failed to open book, click Show details for more information.'), - det_msg=job.traceback, show=True) - container = job.result - set_current_container(container) - self.current_metadata = container.mi - self.global_undo.open_book(container) - self.update_window_title() - self.file_list.build(container) - def update_window_title(self): self.setWindowTitle(self.current_metadata.title + ' [%s] - %s' %(current_container().book_type.upper(), self.APP_NAME)) diff --git a/src/calibre/gui2/tweak_book/undo.py b/src/calibre/gui2/tweak_book/undo.py index c221c0327c..865ba90ef7 100644 --- a/src/calibre/gui2/tweak_book/undo.py +++ b/src/calibre/gui2/tweak_book/undo.py @@ -6,18 +6,51 @@ from __future__ import (unicode_literals, division, absolute_import, __license__ = 'GPL v3' __copyright__ = '2013, Kovid Goyal ' +import shutil + +def cleanup(containers): + for container in containers: + try: + shutil.rmtree(container.root, ignore_errors=True) + except: + pass class State(object): def __init__(self, container): self.container = container - self.operation = None + self.message = None class GlobalUndoHistory(object): def __init__(self): self.states = [] + self.pos = 0 + + @property + def current_container(self): + return self.states[self.pos].container def open_book(self, container): self.states = [State(container)] + self.pos = 0 + + def add_savepoint(self, new_container, message): + self.states[self.pos].message = message + extra = self.states[self.pos+1:] + cleanup(extra) + self.states = self.states[:self.pos+1] + self.states.append(State(new_container)) + self.pos += 1 + + def undo(self): + if self.pos > 0: + self.pos -= 1 + return self.current_container + + def redo(self): + if self.pos < len(self.states) - 1: + self.pos += 1 + return self.current_container +