1#!/usr/local/bin/python3.8
2# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
3
4
5__license__   = 'GPL v3'
6__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
7__docformat__ = 'restructuredtext en'
8
9import time
10
11from qt.core import QTimer, QDialog, QDialogButtonBox, QCheckBox, QVBoxLayout, QLabel, Qt
12
13from calibre.gui2 import error_dialog, question_dialog
14from calibre.gui2.actions import InterfaceAction
15
16
17class Choose(QDialog):
18
19    def __init__(self, fmts, parent=None):
20        QDialog.__init__(self, parent)
21        self.l = l = QVBoxLayout(self)
22        self.setLayout(l)
23        self.setWindowTitle(_('Choose format to edit'))
24
25        self.la = la = QLabel(_(
26            'This book has multiple formats that can be edited. Choose the format you want to edit.'))
27        l.addWidget(la)
28
29        self.rem = QCheckBox(_('Always ask when more than one format is available'))
30        self.rem.setChecked(True)
31        l.addWidget(self.rem)
32
33        self.bb = bb = QDialogButtonBox(self)
34        l.addWidget(bb)
35        bb.accepted.connect(self.accept)
36        bb.rejected.connect(self.reject)
37        self.buts = buts = []
38        for fmt in fmts:
39            b = bb.addButton(fmt.upper(), QDialogButtonBox.ButtonRole.AcceptRole)
40            b.setObjectName(fmt)
41            connect_lambda(b.clicked, self, lambda self: self.chosen(self.sender().objectName()))
42            buts.append(b)
43
44        self.fmt = None
45        self.resize(self.sizeHint())
46
47    def chosen(self, fmt):
48        self.fmt = fmt
49
50    def accept(self):
51        from calibre.gui2.tweak_book import tprefs
52        tprefs['choose_tweak_fmt'] = self.rem.isChecked()
53        QDialog.accept(self)
54
55
56class TweakEpubAction(InterfaceAction):
57
58    name = 'Tweak ePub'
59    action_spec = (_('Edit book'), 'edit_book.png', _('Edit books in the EPUB or AZW formats'), _('T'))
60    dont_add_to = frozenset(('context-menu-device',))
61    action_type = 'current'
62
63    accepts_drops = True
64
65    def accept_enter_event(self, event, mime_data):
66        if mime_data.hasFormat("application/calibre+from_library"):
67            return True
68        return False
69
70    def accept_drag_move_event(self, event, mime_data):
71        if mime_data.hasFormat("application/calibre+from_library"):
72            return True
73        return False
74
75    def drop_event(self, event, mime_data):
76        mime = 'application/calibre+from_library'
77        if mime_data.hasFormat(mime):
78            self.dropped_ids = tuple(map(int, mime_data.data(mime).data().split()))
79            QTimer.singleShot(1, self.do_drop)
80            return True
81        return False
82
83    def do_drop(self):
84        book_ids = self.dropped_ids
85        del self.dropped_ids
86        if book_ids:
87            self.do_tweak(book_ids[0])
88
89    def genesis(self):
90        self.qaction.triggered.connect(self.tweak_book)
91
92    def tweak_book(self):
93        row = self.gui.library_view.currentIndex()
94        if not row.isValid():
95            return error_dialog(self.gui, _('Cannot Edit book'),
96                    _('No book selected'), show=True)
97
98        book_id = self.gui.library_view.model().id(row)
99        self.do_tweak(book_id)
100
101    def do_tweak(self, book_id):
102        if self.gui.current_view() is not self.gui.library_view:
103            return error_dialog(self.gui, _('Cannot edit book'), _(
104                'Editing of books on the device is not supported'), show=True)
105        from calibre.ebooks.oeb.polish.main import SUPPORTED
106        db = self.gui.library_view.model().db
107        fmts = db.formats(book_id, index_is_id=True) or ''
108        fmts = [x.upper().strip() for x in fmts.split(',') if x]
109        tweakable_fmts = set(fmts).intersection(SUPPORTED)
110        if not tweakable_fmts:
111            if not fmts:
112                if not question_dialog(self.gui, _('No editable formats'),
113                    _('Do you want to create an empty EPUB file to edit?')):
114                    return
115                tweakable_fmts = {'EPUB'}
116                self.gui.iactions['Add Books'].add_empty_format_to_book(book_id, 'EPUB')
117                current_idx = self.gui.library_view.currentIndex()
118                if current_idx.isValid():
119                    self.gui.library_view.model().current_changed(current_idx, current_idx)
120            else:
121                return error_dialog(self.gui, _('Cannot edit book'), _(
122                    'The book must be in the %s formats to edit.'
123                    '\n\nFirst convert the book to one of these formats.'
124                ) % (_(' or ').join(SUPPORTED)), show=True)
125        from calibre.gui2.tweak_book import tprefs
126        tprefs.refresh()  # In case they were changed in a Tweak Book process
127        if len(tweakable_fmts) > 1:
128            if tprefs['choose_tweak_fmt']:
129                d = Choose(sorted(tweakable_fmts, key=tprefs.defaults['tweak_fmt_order'].index), self.gui)
130                if d.exec() != QDialog.DialogCode.Accepted:
131                    return
132                tweakable_fmts = {d.fmt}
133            else:
134                fmts = [f for f in tprefs['tweak_fmt_order'] if f in tweakable_fmts]
135                if not fmts:
136                    fmts = [f for f in tprefs.defaults['tweak_fmt_order'] if f in tweakable_fmts]
137                tweakable_fmts = {fmts[0]}
138
139        fmt = tuple(tweakable_fmts)[0]
140        self.ebook_edit_format(book_id, fmt)
141
142    def ebook_edit_format(self, book_id, fmt):
143        '''
144        Also called from edit_metadata formats list.  In that context,
145        SUPPORTED check was already done.
146        '''
147        db = self.gui.library_view.model().db
148        from calibre.gui2.tweak_book import tprefs
149        tprefs.refresh()  # In case they were changed in a Tweak Book process
150        path = db.new_api.format_abspath(book_id, fmt)
151        if path is None:
152            return error_dialog(self.gui, _('File missing'), _(
153                'The %s format is missing from the calibre library. You should run'
154                ' library maintenance.') % fmt, show=True)
155        tweak = 'ebook-edit'
156        try:
157            self.gui.setCursor(Qt.CursorShape.BusyCursor)
158            if tprefs['update_metadata_from_calibre']:
159                db.new_api.embed_metadata((book_id,), only_fmts={fmt})
160            notify = '%d:%s:%s:%s' % (book_id, fmt, db.library_id, db.library_path)
161            self.gui.job_manager.launch_gui_app(tweak, kwargs=dict(path=path, notify=notify))
162            time.sleep(2)
163        finally:
164            self.gui.unsetCursor()
165