mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Add a user interface action for the ToC editor
This commit is contained in:
parent
72cd8c5478
commit
732a57c560
@ -531,6 +531,8 @@ Calibre has several keyboard shortcuts to save you time and mouse movement. Thes
|
|||||||
- Get Books
|
- Get Books
|
||||||
* - :kbd:`I`
|
* - :kbd:`I`
|
||||||
- Show book details
|
- Show book details
|
||||||
|
* - :kbd:`K`
|
||||||
|
- Edit Table of Contents
|
||||||
* - :kbd:`M`
|
* - :kbd:`M`
|
||||||
- Merge selected records
|
- Merge selected records
|
||||||
* - :kbd:`Alt+M`
|
* - :kbd:`Alt+M`
|
||||||
|
BIN
resources/images/toc.png
Normal file
BIN
resources/images/toc.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 49 KiB |
@ -789,6 +789,11 @@ class ActionPolish(InterfaceActionBase):
|
|||||||
actual_plugin = 'calibre.gui2.actions.polish:PolishAction'
|
actual_plugin = 'calibre.gui2.actions.polish:PolishAction'
|
||||||
description = _('Fine tune your ebooks')
|
description = _('Fine tune your ebooks')
|
||||||
|
|
||||||
|
class ActionEditToC(InterfaceActionBase):
|
||||||
|
name = 'Edit ToC'
|
||||||
|
actual_plugin = 'calibre.gui2.actions.toc_edit:ToCEditAction'
|
||||||
|
description = _('Edit the Table of Contents in your books')
|
||||||
|
|
||||||
class ActionDelete(InterfaceActionBase):
|
class ActionDelete(InterfaceActionBase):
|
||||||
name = 'Remove Books'
|
name = 'Remove Books'
|
||||||
actual_plugin = 'calibre.gui2.actions.delete:DeleteAction'
|
actual_plugin = 'calibre.gui2.actions.delete:DeleteAction'
|
||||||
@ -929,7 +934,7 @@ plugins += [ActionAdd, ActionFetchAnnotations, ActionGenerateCatalog,
|
|||||||
ActionSendToDevice, ActionHelp, ActionPreferences, ActionSimilarBooks,
|
ActionSendToDevice, ActionHelp, ActionPreferences, ActionSimilarBooks,
|
||||||
ActionAddToLibrary, ActionEditCollections, ActionChooseLibrary,
|
ActionAddToLibrary, ActionEditCollections, ActionChooseLibrary,
|
||||||
ActionCopyToLibrary, ActionTweakEpub, ActionNextMatch, ActionStore,
|
ActionCopyToLibrary, ActionTweakEpub, ActionNextMatch, ActionStore,
|
||||||
ActionPluginUpdater, ActionPickRandom]
|
ActionPluginUpdater, ActionPickRandom, ActionEditToC]
|
||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
|
148
src/calibre/gui2/actions/toc_edit.py
Normal file
148
src/calibre/gui2/actions/toc_edit.py
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:fdm=marker:ai
|
||||||
|
from __future__ import (unicode_literals, division, absolute_import,
|
||||||
|
print_function)
|
||||||
|
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||||
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
|
from PyQt4.Qt import (QTimer, QDialog, QGridLayout, QCheckBox, QLabel,
|
||||||
|
QDialogButtonBox, QIcon)
|
||||||
|
|
||||||
|
from calibre.gui2 import error_dialog
|
||||||
|
from calibre.gui2.actions import InterfaceAction
|
||||||
|
|
||||||
|
SUPPORTED = {'EPUB', 'AZW3'}
|
||||||
|
|
||||||
|
class ChooseFormat(QDialog): # {{{
|
||||||
|
|
||||||
|
def __init__(self, formats, parent=None):
|
||||||
|
QDialog.__init__(self, parent)
|
||||||
|
self.setWindowTitle(_('Choose format to edit'))
|
||||||
|
self.setWindowIcon(QIcon(I('dialog_question.png')))
|
||||||
|
l = self.l = QGridLayout()
|
||||||
|
self.setLayout(l)
|
||||||
|
la = self.la = QLabel(_('Choose which format you want to edit:'))
|
||||||
|
formats = sorted(formats)
|
||||||
|
l.addWidget(la, 0, 0, 1, -1)
|
||||||
|
self.buttons = []
|
||||||
|
for i, f in enumerate(formats):
|
||||||
|
b = QCheckBox('&' + f, self)
|
||||||
|
l.addWidget(b, 1, i)
|
||||||
|
self.buttons.append(b)
|
||||||
|
if i == 0:
|
||||||
|
b.setChecked(True)
|
||||||
|
bb = self.bb = QDialogButtonBox(
|
||||||
|
QDialogButtonBox.Ok|QDialogButtonBox.Cancel)
|
||||||
|
bb.addButton(_('&All formats'),
|
||||||
|
bb.ActionRole).clicked.connect(self.do_all)
|
||||||
|
bb.accepted.connect(self.accept)
|
||||||
|
bb.rejected.connect(self.reject)
|
||||||
|
l.addWidget(bb, l.rowCount(), 0, 1, -1)
|
||||||
|
self.resize(self.sizeHint())
|
||||||
|
|
||||||
|
def do_all(self):
|
||||||
|
for b in self.buttons:
|
||||||
|
b.setChecked(True)
|
||||||
|
self.accept()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def formats(self):
|
||||||
|
for b in self.buttons:
|
||||||
|
if b.isChecked():
|
||||||
|
yield unicode(b.text())[1:]
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
class ToCEditAction(InterfaceAction):
|
||||||
|
|
||||||
|
name = 'Edit ToC'
|
||||||
|
action_spec = (_('Edit ToC'), 'toc.png',
|
||||||
|
_('Edit the Table of Contents in your books'), _('K'))
|
||||||
|
dont_add_to = frozenset(['context-menu-device'])
|
||||||
|
action_type = 'current'
|
||||||
|
accepts_drops = True
|
||||||
|
|
||||||
|
def accept_enter_event(self, event, mime_data):
|
||||||
|
if mime_data.hasFormat("application/calibre+from_library"):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def accept_drag_move_event(self, event, mime_data):
|
||||||
|
if mime_data.hasFormat("application/calibre+from_library"):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def drop_event(self, event, mime_data):
|
||||||
|
mime = 'application/calibre+from_library'
|
||||||
|
if mime_data.hasFormat(mime):
|
||||||
|
self.dropped_ids = tuple(map(int, str(mime_data.data(mime)).split()))
|
||||||
|
QTimer.singleShot(1, self.do_drop)
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def do_drop(self):
|
||||||
|
book_id_map = self.get_supported_books(self.dropped_ids)
|
||||||
|
del self.dropped_ids
|
||||||
|
if book_id_map:
|
||||||
|
self.do_edit(book_id_map)
|
||||||
|
|
||||||
|
def genesis(self):
|
||||||
|
self.qaction.triggered.connect(self.edit_books)
|
||||||
|
|
||||||
|
def get_supported_books(self, book_ids):
|
||||||
|
db = self.gui.library_view.model().db
|
||||||
|
supported = set(SUPPORTED)
|
||||||
|
ans = [(x, set( (db.formats(x, index_is_id=True) or '').split(',') )
|
||||||
|
.intersection(supported)) for x in book_ids]
|
||||||
|
ans = [x for x in ans if x[1]]
|
||||||
|
if not ans:
|
||||||
|
error_dialog(self.gui, _('Cannot edit ToC'),
|
||||||
|
_('Editing Table of Contents is only supported for books in the %s'
|
||||||
|
' formats. Convert to one of those formats before polishing.')
|
||||||
|
%_(' or ').join(sorted(supported)), show=True)
|
||||||
|
ans = OrderedDict(ans)
|
||||||
|
return ans
|
||||||
|
|
||||||
|
def get_books_for_editing(self):
|
||||||
|
rows = [r.row() for r in
|
||||||
|
self.gui.library_view.selectionModel().selectedRows()]
|
||||||
|
if not rows or len(rows) == 0:
|
||||||
|
d = error_dialog(self.gui, _('Cannot edit ToC'),
|
||||||
|
_('No books selected'))
|
||||||
|
d.exec_()
|
||||||
|
return None
|
||||||
|
db = self.gui.current_db
|
||||||
|
ans = (db.id(r) for r in rows)
|
||||||
|
return self.get_supported_books(ans)
|
||||||
|
|
||||||
|
def do_edit(self, book_id_map):
|
||||||
|
for book_id, fmts in book_id_map.iteritems():
|
||||||
|
if len(fmts) > 1:
|
||||||
|
d = ChooseFormat(fmts, self.gui)
|
||||||
|
if d.exec_() != d.Accepted:
|
||||||
|
return
|
||||||
|
fmts = d.formats
|
||||||
|
for fmt in fmts:
|
||||||
|
self.do_one(book_id, fmt)
|
||||||
|
|
||||||
|
def do_one(self, book_id, fmt):
|
||||||
|
from calibre.gui2.toc.main import TOCEditor
|
||||||
|
db = self.gui.current_db
|
||||||
|
path = db.format(book_id, fmt, index_is_id=True, as_path=True)
|
||||||
|
title = db.title(book_id, index_is_id=True) + ' [%s]'%fmt
|
||||||
|
d = TOCEditor(path, title=title, parent=self.gui)
|
||||||
|
d.start()
|
||||||
|
if d.exec_() == d.Accepted:
|
||||||
|
with open(path, 'rb') as f:
|
||||||
|
db.add_format(book_id, fmt, f, index_is_id=True)
|
||||||
|
|
||||||
|
def edit_books(self):
|
||||||
|
book_id_map = self.get_books_for_editing()
|
||||||
|
if not book_id_map:
|
||||||
|
return
|
||||||
|
self.do_edit(book_id_map)
|
||||||
|
|
||||||
|
|
@ -37,6 +37,7 @@ class Page(QWebPage): # {{{
|
|||||||
def javaScriptAlert(self, frame, msg):
|
def javaScriptAlert(self, frame, msg):
|
||||||
self.log(unicode(msg))
|
self.log(unicode(msg))
|
||||||
|
|
||||||
|
@pyqtSlot(result=bool)
|
||||||
def shouldInterruptJavaScript(self):
|
def shouldInterruptJavaScript(self):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -425,6 +425,7 @@ class TOCEditor(QDialog): # {{{
|
|||||||
def __init__(self, pathtobook, title=None, parent=None):
|
def __init__(self, pathtobook, title=None, parent=None):
|
||||||
QDialog.__init__(self, parent)
|
QDialog.__init__(self, parent)
|
||||||
self.pathtobook = pathtobook
|
self.pathtobook = pathtobook
|
||||||
|
self.working = True
|
||||||
|
|
||||||
t = title or os.path.basename(pathtobook)
|
t = title or os.path.basename(pathtobook)
|
||||||
self.setWindowTitle(_('Edit the ToC in %s')%t)
|
self.setWindowTitle(_('Edit the ToC in %s')%t)
|
||||||
@ -444,6 +445,7 @@ class TOCEditor(QDialog): # {{{
|
|||||||
pi.startAnimation()
|
pi.startAnimation()
|
||||||
ll.addWidget(pi, alignment=Qt.AlignHCenter|Qt.AlignCenter)
|
ll.addWidget(pi, alignment=Qt.AlignHCenter|Qt.AlignCenter)
|
||||||
la = self.la = QLabel(_('Loading %s, please wait...')%t)
|
la = self.la = QLabel(_('Loading %s, please wait...')%t)
|
||||||
|
la.setWordWrap(True)
|
||||||
la.setStyleSheet('QLabel { font-size: 20pt }')
|
la.setStyleSheet('QLabel { font-size: 20pt }')
|
||||||
ll.addWidget(la, alignment=Qt.AlignHCenter|Qt.AlignTop)
|
ll.addWidget(la, alignment=Qt.AlignHCenter|Qt.AlignTop)
|
||||||
self.toc_view = TOCView(self)
|
self.toc_view = TOCView(self)
|
||||||
@ -460,7 +462,6 @@ class TOCEditor(QDialog): # {{{
|
|||||||
self.explode_done.connect(self.read_toc, type=Qt.QueuedConnection)
|
self.explode_done.connect(self.read_toc, type=Qt.QueuedConnection)
|
||||||
|
|
||||||
self.resize(950, 630)
|
self.resize(950, 630)
|
||||||
self.working = True
|
|
||||||
|
|
||||||
def add_new_item(self, item, where):
|
def add_new_item(self, item, where):
|
||||||
self.item_edit(item, where)
|
self.item_edit(item, where)
|
||||||
@ -491,6 +492,7 @@ class TOCEditor(QDialog): # {{{
|
|||||||
def explode(self):
|
def explode(self):
|
||||||
self.ebook = get_container(self.pathtobook, log=self.log)
|
self.ebook = get_container(self.pathtobook, log=self.log)
|
||||||
if self.working:
|
if self.working:
|
||||||
|
self.working = False
|
||||||
self.explode_done.emit()
|
self.explode_done.emit()
|
||||||
|
|
||||||
def read_toc(self):
|
def read_toc(self):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user