mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
ToC view for Edit Book
This commit is contained in:
parent
75abe74e20
commit
41c0926f23
@ -37,3 +37,4 @@ class NonReplaceDict(dict):
|
|||||||
|
|
||||||
actions = NonReplaceDict()
|
actions = NonReplaceDict()
|
||||||
editors = NonReplaceDict()
|
editors = NonReplaceDict()
|
||||||
|
TOP = object()
|
||||||
|
@ -104,6 +104,7 @@ class Boss(QObject):
|
|||||||
self.gui.check_book.item_activated.connect(self.check_item_activated)
|
self.gui.check_book.item_activated.connect(self.check_item_activated)
|
||||||
self.gui.check_book.check_requested.connect(self.check_requested)
|
self.gui.check_book.check_requested.connect(self.check_requested)
|
||||||
self.gui.check_book.fix_requested.connect(self.fix_requested)
|
self.gui.check_book.fix_requested.connect(self.fix_requested)
|
||||||
|
self.gui.toc_view.navigate_requested.connect(self.link_clicked)
|
||||||
|
|
||||||
def preferences(self):
|
def preferences(self):
|
||||||
p = Preferences(self.gui)
|
p = Preferences(self.gui)
|
||||||
@ -188,6 +189,7 @@ class Boss(QObject):
|
|||||||
parse_worker.clear()
|
parse_worker.clear()
|
||||||
container = job.result
|
container = job.result
|
||||||
set_current_container(container)
|
set_current_container(container)
|
||||||
|
with BusyCursor():
|
||||||
self.current_metadata = self.gui.current_metadata = container.mi
|
self.current_metadata = self.gui.current_metadata = container.mi
|
||||||
self.global_undo.open_book(container)
|
self.global_undo.open_book(container)
|
||||||
self.gui.update_window_title()
|
self.gui.update_window_title()
|
||||||
@ -204,6 +206,7 @@ class Boss(QObject):
|
|||||||
self.gui.update_recent_books()
|
self.gui.update_recent_books()
|
||||||
if ef:
|
if ef:
|
||||||
self.gui.file_list.request_edit(ef)
|
self.gui.file_list.request_edit(ef)
|
||||||
|
self.gui.toc_view.update_if_visible()
|
||||||
|
|
||||||
def update_editors_from_container(self, container=None):
|
def update_editors_from_container(self, container=None):
|
||||||
c = container or current_container()
|
c = container or current_container()
|
||||||
@ -291,7 +294,9 @@ class Boss(QObject):
|
|||||||
if d.exec_() != d.Accepted:
|
if d.exec_() != d.Accepted:
|
||||||
self.rewind_savepoint()
|
self.rewind_savepoint()
|
||||||
return
|
return
|
||||||
|
with BusyCursor():
|
||||||
self.update_editors_from_container()
|
self.update_editors_from_container()
|
||||||
|
self.gui.toc_view.update_if_visible()
|
||||||
|
|
||||||
def polish(self, action, name):
|
def polish(self, action, name):
|
||||||
self.commit_all_editors_to_container()
|
self.commit_all_editors_to_container()
|
||||||
@ -698,6 +703,8 @@ class Boss(QObject):
|
|||||||
|
|
||||||
@in_thread_job
|
@in_thread_job
|
||||||
def link_clicked(self, name, anchor):
|
def link_clicked(self, name, anchor):
|
||||||
|
if not name:
|
||||||
|
return
|
||||||
if name in editors:
|
if name in editors:
|
||||||
editor = editors[name]
|
editor = editors[name]
|
||||||
self.gui.central.show_editor(editor)
|
self.gui.central.show_editor(editor)
|
||||||
|
@ -15,7 +15,7 @@ from PyQt4.Qt import (
|
|||||||
QTextEdit, QTextFormat, QWidget, QSize, QPainter, Qt, QRect, pyqtSlot,
|
QTextEdit, QTextFormat, QWidget, QSize, QPainter, Qt, QRect, pyqtSlot,
|
||||||
QApplication, QMimeData)
|
QApplication, QMimeData)
|
||||||
|
|
||||||
from calibre.gui2.tweak_book import tprefs
|
from calibre.gui2.tweak_book import tprefs, TOP
|
||||||
from calibre.gui2.tweak_book.editor import SYNTAX_PROPERTY
|
from calibre.gui2.tweak_book.editor import SYNTAX_PROPERTY
|
||||||
from calibre.gui2.tweak_book.editor.themes import THEMES, default_theme, theme_color
|
from calibre.gui2.tweak_book.editor.themes import THEMES, default_theme, theme_color
|
||||||
from calibre.gui2.tweak_book.editor.syntax.base import SyntaxHighlighter
|
from calibre.gui2.tweak_book.editor.syntax.base import SyntaxHighlighter
|
||||||
@ -332,6 +332,11 @@ class TextEdit(QPlainTextEdit):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def go_to_anchor(self, anchor):
|
def go_to_anchor(self, anchor):
|
||||||
|
if anchor is TOP:
|
||||||
|
c = self.textCursor()
|
||||||
|
c.movePosition(c.Start)
|
||||||
|
self.setTextCursor(c)
|
||||||
|
return True
|
||||||
base = r'''%%s\s*=\s*['"]{0,1}%s''' % regex.escape(anchor)
|
base = r'''%%s\s*=\s*['"]{0,1}%s''' % regex.escape(anchor)
|
||||||
raw = unicode(self.toPlainText())
|
raw = unicode(self.toPlainText())
|
||||||
m = regex.search(base % 'id', raw)
|
m = regex.search(base % 'id', raw)
|
||||||
|
@ -6,12 +6,15 @@ from __future__ import (unicode_literals, division, absolute_import,
|
|||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
|
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||||
|
|
||||||
from PyQt4.Qt import (QDialog, pyqtSignal, QIcon, QVBoxLayout, QDialogButtonBox, QStackedWidget)
|
from PyQt4.Qt import (
|
||||||
|
QDialog, pyqtSignal, QIcon, QVBoxLayout, QDialogButtonBox, QStackedWidget,
|
||||||
|
QAction, QMenu, QTreeWidget, QTreeWidgetItem, QGridLayout, QWidget, Qt,
|
||||||
|
QSize, QStyledItemDelegate, QTimer)
|
||||||
|
|
||||||
from calibre.ebooks.oeb.polish.toc import commit_toc
|
from calibre.ebooks.oeb.polish.toc import commit_toc, get_toc
|
||||||
from calibre.gui2 import gprefs, error_dialog
|
from calibre.gui2 import gprefs, error_dialog
|
||||||
from calibre.gui2.toc.main import TOCView, ItemEdit
|
from calibre.gui2.toc.main import TOCView, ItemEdit
|
||||||
from calibre.gui2.tweak_book import current_container
|
from calibre.gui2.tweak_book import current_container, TOP
|
||||||
|
|
||||||
class TOCEditor(QDialog):
|
class TOCEditor(QDialog):
|
||||||
|
|
||||||
@ -94,3 +97,100 @@ class TOCEditor(QDialog):
|
|||||||
commit_toc(current_container(), toc, lang=self.toc_view.toc_lang,
|
commit_toc(current_container(), toc, lang=self.toc_view.toc_lang,
|
||||||
uid=self.toc_view.toc_uid)
|
uid=self.toc_view.toc_uid)
|
||||||
|
|
||||||
|
DEST_ROLE = Qt.UserRole
|
||||||
|
FRAG_ROLE = DEST_ROLE + 1
|
||||||
|
|
||||||
|
class Delegate(QStyledItemDelegate):
|
||||||
|
|
||||||
|
def sizeHint(self, *args):
|
||||||
|
ans = QStyledItemDelegate.sizeHint(self, *args)
|
||||||
|
return ans + QSize(0, 10)
|
||||||
|
|
||||||
|
class TOCViewer(QWidget):
|
||||||
|
|
||||||
|
navigate_requested = pyqtSignal(object, object)
|
||||||
|
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
QWidget.__init__(self, parent)
|
||||||
|
self.l = l = QGridLayout(self)
|
||||||
|
self.setLayout(l)
|
||||||
|
l.setContentsMargins(0, 0, 0, 0)
|
||||||
|
|
||||||
|
self.is_visible = False
|
||||||
|
self.view = QTreeWidget(self)
|
||||||
|
self.delegate = Delegate(self.view)
|
||||||
|
self.view.setItemDelegate(self.delegate)
|
||||||
|
self.view.setHeaderHidden(True)
|
||||||
|
self.view.setAnimated(True)
|
||||||
|
self.view.setContextMenuPolicy(Qt.CustomContextMenu)
|
||||||
|
self.view.customContextMenuRequested.connect(self.show_context_menu, type=Qt.QueuedConnection)
|
||||||
|
self.view.itemActivated.connect(self.emit_navigate)
|
||||||
|
self.view.itemClicked.connect(self.emit_navigate)
|
||||||
|
l.addWidget(self.view)
|
||||||
|
|
||||||
|
self.refresh_action = QAction(QIcon(I('view-refresh.png')), _('&Refresh'), self)
|
||||||
|
self.refresh_action.triggered.connect(self.build)
|
||||||
|
self._last_nav_request = None
|
||||||
|
|
||||||
|
def show_context_menu(self, pos):
|
||||||
|
menu = QMenu(self)
|
||||||
|
menu.addAction(self.refresh_action)
|
||||||
|
menu.addAction(_('&Expand all'), self.view.expandAll)
|
||||||
|
menu.addAction(_('&Collapse all'), self.view.collapseAll)
|
||||||
|
menu.exec_(self.view.mapToGlobal(pos))
|
||||||
|
|
||||||
|
def iteritems(self, parent=None):
|
||||||
|
if parent is None:
|
||||||
|
parent = self.invisibleRootItem()
|
||||||
|
for i in xrange(parent.childCount()):
|
||||||
|
child = parent.child(i)
|
||||||
|
yield child
|
||||||
|
for gc in self.iteritems(parent=child):
|
||||||
|
yield gc
|
||||||
|
|
||||||
|
def emit_navigate(self, *args):
|
||||||
|
item = self.view.currentItem()
|
||||||
|
if item is not None:
|
||||||
|
dest = unicode(item.data(0, DEST_ROLE).toString())
|
||||||
|
frag = unicode(item.data(0, FRAG_ROLE).toString())
|
||||||
|
if not frag:
|
||||||
|
frag = TOP
|
||||||
|
# Debounce as on some platforms clicking causes both itemActivated
|
||||||
|
# and itemClicked to be emitted
|
||||||
|
self._last_nav_request = (dest, frag)
|
||||||
|
QTimer.singleShot(0, self._emit_navigate)
|
||||||
|
|
||||||
|
def _emit_navigate(self):
|
||||||
|
if self._last_nav_request is not None:
|
||||||
|
self.navigate_requested.emit(*self._last_nav_request)
|
||||||
|
self._last_nav_request = None
|
||||||
|
|
||||||
|
def build(self):
|
||||||
|
c = current_container()
|
||||||
|
if c is None:
|
||||||
|
return
|
||||||
|
toc = get_toc(c, verify_destinations=False)
|
||||||
|
|
||||||
|
def process_node(toc, parent):
|
||||||
|
for child in toc:
|
||||||
|
node = QTreeWidgetItem(parent)
|
||||||
|
node.setText(0, child.title or '')
|
||||||
|
node.setData(0, DEST_ROLE, child.dest or '')
|
||||||
|
node.setData(0, FRAG_ROLE, child.frag or '')
|
||||||
|
tt = _('File: {0}\nAnchor: {1}').format(
|
||||||
|
child.dest or '', child.frag or '')
|
||||||
|
node.setData(0, Qt.ToolTipRole, tt)
|
||||||
|
process_node(child, node)
|
||||||
|
|
||||||
|
self.view.clear()
|
||||||
|
process_node(toc, self.view.invisibleRootItem())
|
||||||
|
|
||||||
|
def visibility_changed(self, visible):
|
||||||
|
self.is_visible = visible
|
||||||
|
if visible:
|
||||||
|
self.build()
|
||||||
|
|
||||||
|
def update_if_visible(self):
|
||||||
|
if self.is_visible:
|
||||||
|
self.build()
|
||||||
|
|
||||||
|
@ -25,6 +25,7 @@ from calibre.gui2.tweak_book.boss import Boss
|
|||||||
from calibre.gui2.tweak_book.preview import Preview
|
from calibre.gui2.tweak_book.preview import Preview
|
||||||
from calibre.gui2.tweak_book.search import SearchPanel
|
from calibre.gui2.tweak_book.search import SearchPanel
|
||||||
from calibre.gui2.tweak_book.check import Check
|
from calibre.gui2.tweak_book.check import Check
|
||||||
|
from calibre.gui2.tweak_book.toc import TOCViewer
|
||||||
|
|
||||||
class Central(QStackedWidget):
|
class Central(QStackedWidget):
|
||||||
|
|
||||||
@ -197,6 +198,7 @@ class Main(MainWindow):
|
|||||||
self.central = Central(self)
|
self.central = Central(self)
|
||||||
self.setCentralWidget(self.central)
|
self.setCentralWidget(self.central)
|
||||||
self.check_book = Check(self)
|
self.check_book = Check(self)
|
||||||
|
self.toc_view = TOCViewer(self)
|
||||||
|
|
||||||
self.create_actions()
|
self.create_actions()
|
||||||
self.create_toolbars()
|
self.create_toolbars()
|
||||||
@ -506,6 +508,13 @@ class Main(MainWindow):
|
|||||||
d.close() # By default the inspector window is closed
|
d.close() # By default the inspector window is closed
|
||||||
d.setFeatures(d.DockWidgetClosable | d.DockWidgetMovable) # QWebInspector does not work in a floating dock
|
d.setFeatures(d.DockWidgetClosable | d.DockWidgetMovable) # QWebInspector does not work in a floating dock
|
||||||
|
|
||||||
|
d = create(_('Table of Contents'), 'toc-viewer')
|
||||||
|
d.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea | Qt.BottomDockWidgetArea | Qt.TopDockWidgetArea)
|
||||||
|
d.setWidget(self.toc_view)
|
||||||
|
self.addDockWidget(Qt.LeftDockWidgetArea, d)
|
||||||
|
d.close() # Hidden by default
|
||||||
|
d.visibilityChanged.connect(self.toc_view.visibility_changed)
|
||||||
|
|
||||||
def resizeEvent(self, ev):
|
def resizeEvent(self, ev):
|
||||||
self.blocking_job.resize(ev.size())
|
self.blocking_job.resize(ev.size())
|
||||||
return super(Main, self).resizeEvent(ev)
|
return super(Main, self).resizeEvent(ev)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user