E-book viewer: Make the Table of Contents panel a dockable window so it can be moved around and even made into a separate floating window

Also refactor the viewer's ui code to not use Qt Designer.
This commit is contained in:
Kovid Goyal 2014-08-05 15:34:45 +05:30
parent e01cb3f319
commit d61083a513
4 changed files with 297 additions and 550 deletions

View File

@ -1,30 +1,25 @@
__license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
import traceback, os, sys, functools, textwrap
import traceback, os, sys, functools
from functools import partial
from threading import Thread
from PyQt4.Qt import (
QApplication, Qt, QIcon, QTimer, QByteArray, QSize, QTime, QDoubleSpinBox,
QLabel, QPropertyAnimation, pyqtSignal, QUrl, QRegExpValidator, QRegExp,
QLineEdit, QToolButton, QMenu, QInputDialog, QAction, QModelIndex, QPalette,
QPainter, QBrush, QColor)
from PyQt4.QtWebKit import QWebView
QApplication, Qt, QIcon, QTimer, QByteArray, QSize, QTime, QLabel,
QPropertyAnimation, QUrl, QInputDialog, QAction, QModelIndex)
from calibre.gui2.viewer.main_ui import Ui_EbookViewer
from calibre.gui2.viewer.ui import Main as MainWindow
from calibre.gui2.viewer.printing import Printing
from calibre.gui2.viewer.bookmarkmanager import BookmarkManager
from calibre.gui2.viewer.toc import TOC
from calibre.gui2.widgets import ProgressIndicator
from calibre.gui2.main_window import MainWindow
from calibre.gui2 import (Application, ORG_NAME, APP_UID, choose_files, rating_font,
info_dialog, error_dialog, open_url, available_height, setup_gui_option_parser, detach_gui)
from calibre.gui2 import (Application, ORG_NAME, APP_UID, choose_files,
info_dialog, error_dialog, open_url, setup_gui_option_parser, detach_gui)
from calibre.ebooks.oeb.iterator.book import EbookIterator
from calibre.ebooks import DRMError
from calibre.constants import islinux, filesystem_encoding
from calibre.utils.config import Config, StringConfig, JSONConfig
from calibre.gui2.search_box import SearchBox2
from calibre.customize.ui import available_input_formats
from calibre import as_unicode, force_unicode, isbytestring
from calibre.ptempfile import reset_base_dir
@ -47,145 +42,15 @@ class Worker(Thread):
self.exception = err
self.traceback = traceback.format_exc()
class History(list):
def __init__(self, action_back, action_forward):
self.action_back = action_back
self.action_forward = action_forward
super(History, self).__init__(self)
self.insert_pos = 0
self.back_pos = None
self.forward_pos = None
self.set_actions()
def set_actions(self):
self.action_back.setDisabled(self.back_pos is None)
self.action_forward.setDisabled(self.forward_pos is None)
def back(self, from_pos):
# Back clicked
if self.back_pos is None:
return None
item = self[self.back_pos]
self.forward_pos = self.back_pos+1
if self.forward_pos >= len(self):
self.append(from_pos)
self.forward_pos = len(self) - 1
self.insert_pos = self.forward_pos
self.back_pos = None if self.back_pos == 0 else self.back_pos - 1
self.set_actions()
return item
def forward(self, from_pos):
if self.forward_pos is None:
return None
item = self[self.forward_pos]
self.back_pos = self.forward_pos - 1
if self.back_pos < 0:
self.back_pos = None
self.insert_pos = self.back_pos or 0
self.forward_pos = None if self.forward_pos > len(self) - 2 else self.forward_pos + 1
self.set_actions()
return item
def add(self, item):
self[self.insert_pos:] = []
while self.insert_pos > 0 and self[self.insert_pos-1] == item:
self.insert_pos -= 1
self[self.insert_pos:] = []
self.insert(self.insert_pos, item)
# The next back must go to item
self.back_pos = self.insert_pos
self.insert_pos += 1
# There can be no forward
self.forward_pos = None
self.set_actions()
class Metadata(QWebView):
def __init__(self, parent):
QWebView.__init__(self, parent.centralWidget())
s = self.settings()
s.setAttribute(s.JavascriptEnabled, False)
self.page().setLinkDelegationPolicy(self.page().DelegateAllLinks)
self.setAttribute(Qt.WA_OpaquePaintEvent, False)
palette = self.palette()
palette.setBrush(QPalette.Base, Qt.transparent)
self.page().setPalette(palette)
self.css = P('templates/book_details.css', data=True).decode('utf-8')
self.view = parent.splitter
self.setGeometry(self.view.geometry())
self.setVisible(False)
def show_opf(self, opf, ext=''):
from calibre.gui2.book_details import render_html
from calibre.ebooks.metadata.book.render import mi_to_html
def render_data(mi, use_roman_numbers=True, all_fields=False):
return mi_to_html(mi, use_roman_numbers=use_roman_numbers, rating_font=rating_font())
mi = opf.to_book_metadata()
html = render_html(mi, self.css, True, self, render_data_func=render_data)
self.setHtml(html)
def setVisible(self, x):
if x:
self.setGeometry(self.view.geometry())
QWebView.setVisible(self, x)
def paintEvent(self, ev):
p = QPainter(self)
p.fillRect(ev.region().boundingRect(), QBrush(QColor(200, 200, 200, 220), Qt.SolidPattern))
p.end()
QWebView.paintEvent(self, ev)
class DoubleSpinBox(QDoubleSpinBox):
value_changed = pyqtSignal(object, object)
def __init__(self, *args, **kwargs):
QDoubleSpinBox.__init__(self, *args, **kwargs)
self.tt = _('Position in book')
self.setToolTip(self.tt)
def set_value(self, val):
self.blockSignals(True)
self.setValue(val)
self.setToolTip(self.tt +
' [{0:.0%}]'.format(float(val)/self.maximum()))
self.blockSignals(False)
self.value_changed.emit(self.value(), self.maximum())
class Reference(QLineEdit):
goto = pyqtSignal(object)
def __init__(self, *args):
QLineEdit.__init__(self, *args)
self.setValidator(QRegExpValidator(QRegExp(r'\d+\.\d+'), self))
self.setToolTip(textwrap.fill('<p>'+_(
'Go to a reference. To get reference numbers, use the <i>reference '
'mode</i>, by clicking the reference mode button in the toolbar.')))
if hasattr(self, 'setPlaceholderText'):
self.setPlaceholderText(_('Go to...'))
self.editingFinished.connect(self.editing_finished)
def editing_finished(self):
text = unicode(self.text())
self.setText('')
self.goto.emit(text)
class RecentAction(QAction):
def __init__(self, path, parent):
self.path = path
QAction.__init__(self, os.path.basename(path), parent)
class EbookViewer(MainWindow, Ui_EbookViewer):
class EbookViewer(MainWindow):
STATE_VERSION = 1
STATE_VERSION = 2
FLOW_MODE_TT = _('Switch to paged mode - where the text is broken up '
'into pages like a paper book')
PAGED_MODE_TT = _('Switch to flow mode - where the text is not broken up '
@ -193,9 +58,7 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
def __init__(self, pathtoebook=None, debug_javascript=False, open_at=None,
start_in_fullscreen=False):
MainWindow.__init__(self, None)
self.setupUi(self)
self.view.initialize_view(debug_javascript)
MainWindow.__init__(self, debug_javascript)
self.view.magnification_changed.connect(self.magnification_changed)
self.show_toc_on_open = False
self.current_book_has_toc = False
@ -212,31 +75,11 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
self.selected_text = None
self.was_maximized = False
self.read_settings()
self.history = History(self.action_back, self.action_forward)
self.metadata = Metadata(self)
self.pos = DoubleSpinBox()
self.pos.setDecimals(1)
self.pos.setSuffix('/'+_('Unknown')+' ')
self.pos.setMinimum(1.)
self.pos.value_changed.connect(self.update_pos_label)
self.splitter.setCollapsible(0, False)
self.splitter.setCollapsible(1, False)
self.pos.setMinimumWidth(150)
self.tool_bar2.insertWidget(self.action_find_next, self.pos)
self.reference = Reference()
self.tool_bar2.insertSeparator(self.action_find_next)
self.tool_bar2.insertWidget(self.action_find_next, self.reference)
self.tool_bar2.insertSeparator(self.action_find_next)
self.setFocusPolicy(Qt.StrongFocus)
self.search = SearchBox2(self)
self.search.setMinimumContentsLength(20)
self.search.initialize('viewer_search_history')
self.search.setToolTip(_('Search for text in book'))
self.search.setMinimumWidth(200)
self.tool_bar2.insertWidget(self.action_find_next, self.search)
self.view.set_manager(self)
self.pi = ProgressIndicator(self)
self.toc.setVisible(False)
self.action_quit = QAction(_('&Quit'), self)
self.addAction(self.action_quit)
self.view_resized_timer = QTimer(self)
@ -244,11 +87,6 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
self.view_resized_timer.setSingleShot(True)
self.resize_in_progress = False
self.action_quit.triggered.connect(self.quit)
self.action_copy.setDisabled(True)
self.action_metadata.setCheckable(True)
self.action_table_of_contents.setCheckable(True)
self.toc.setMinimumWidth(80)
self.action_reference_mode.setCheckable(True)
self.action_reference_mode.triggered[bool].connect(self.view.reference_mode)
self.action_metadata.triggered[bool].connect(self.metadata.setVisible)
self.action_table_of_contents.toggled[bool].connect(self.set_toc_visible)
@ -261,8 +99,6 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
self.action_find_next.triggered.connect(self.find_next)
self.action_find_previous.triggered.connect(self.find_previous)
self.action_full_screen.triggered[bool].connect(self.toggle_fullscreen)
self.action_full_screen.setToolTip(_('Toggle full screen [%s]') %
_(' or ').join([x for x in self.view.shortcuts.get_shortcuts('Fullscreen')]))
self.action_back.triggered[bool].connect(self.back)
self.action_forward.triggered[bool].connect(self.forward)
self.action_preferences.triggered.connect(self.do_config)
@ -274,24 +110,13 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
self.toc.pressed[QModelIndex].connect(self.toc_clicked)
self.reference.goto.connect(self.goto)
self.bookmarks_menu = QMenu()
self.action_bookmark.setMenu(self.bookmarks_menu)
self.set_bookmarks([])
self.themes_menu = QMenu()
self.action_load_theme.setMenu(self.themes_menu)
self.tool_bar.widgetForAction(self.action_load_theme).setPopupMode(QToolButton.InstantPopup)
self.load_theme_menu()
if pathtoebook is not None:
f = functools.partial(self.load_ebook, pathtoebook, open_at=open_at)
QTimer.singleShot(50, f)
self.view.setMinimumSize(100, 100)
self.toc.setCursor(Qt.PointingHandCursor)
self.tool_bar.setContextMenuPolicy(Qt.PreventContextMenu)
self.tool_bar2.setContextMenuPolicy(Qt.PreventContextMenu)
self.tool_bar.widgetForAction(self.action_bookmark).setPopupMode(QToolButton.InstantPopup)
self.action_full_screen.setCheckable(True)
self.full_screen_label = QLabel('''
<center>
<h1>%s</h1>
@ -338,28 +163,19 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
font-size: larger;
padding: 5px;
}'''
self.original_frame_style = self.frame.frameStyle()
self.pos_label = QLabel('2000/4000', self)
self.pos_label.setVisible(False)
self.pos_label.setFocusPolicy(Qt.NoFocus)
self.clock_timer = QTimer(self)
self.clock_timer.timeout.connect(self.update_clock)
self.print_menu = QMenu()
self.print_menu.addAction(QIcon(I('print-preview.png')), _('Print Preview'))
self.action_print.setMenu(self.print_menu)
self.tool_bar.widgetForAction(self.action_print).setPopupMode(QToolButton.MenuButtonPopup)
self.action_print.triggered.connect(self.print_book)
self.print_menu.actions()[0].triggered.connect(self.print_preview)
self.open_history_menu = QMenu()
self.clear_recent_history_action = QAction(
_('Clear list of recently opened books'), self)
self.clear_recent_history_action.triggered.connect(self.clear_recent_history)
self.build_recent_menu()
self.action_open_ebook.setMenu(self.open_history_menu)
self.open_history_menu.triggered[QAction].connect(self.open_recent)
w = self.tool_bar.widgetForAction(self.action_open_ebook)
w.setPopupMode(QToolButton.MenuButtonPopup)
for x in ('tool_bar', 'tool_bar2'):
x = getattr(self, x)
@ -400,7 +216,7 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
self.load_path(self.view.last_loaded_path)
def set_toc_visible(self, yes):
self.toc.setVisible(yes)
self.toc_dock.setVisible(yes)
def clear_recent_history(self, *args):
vprefs.set('viewer_open_history', [])
@ -445,19 +261,16 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
def save_state(self):
state = bytearray(self.saveState(self.STATE_VERSION))
vprefs['viewer_toolbar_state'] = state
vprefs['main_window_state'] = state
if not self.isFullScreen():
vprefs.set('viewer_window_geometry', bytearray(self.saveGeometry()))
if self.current_book_has_toc:
vprefs.set('viewer_toc_isvisible', bool(self.toc.isVisible()))
if self.toc.isVisible():
vprefs.set('viewer_splitter_state',
bytearray(self.splitter.saveState()))
vprefs.set('viewer_toc_isvisible', bool(self.toc_dock.isVisible()))
vprefs['multiplier'] = self.view.multiplier
vprefs['in_paged_mode'] = not self.action_toggle_paged_mode.isChecked()
def restore_state(self):
state = vprefs.get('viewer_toolbar_state', None)
state = vprefs.get('main_window_state', None)
if state is not None:
try:
state = QByteArray(state)
@ -471,6 +284,7 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
# specific location, ensure they are visible.
self.tool_bar.setVisible(True)
self.tool_bar2.setVisible(True)
self.toc_dock.close() # This will be opened on book open, if the book has a toc and it was previously opened
self.action_toggle_paged_mode.setChecked(not vprefs.get('in_paged_mode',
True))
self.toggle_paged_mode(self.action_toggle_paged_mode.isChecked(),
@ -516,13 +330,7 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
self.was_maximized = self.isMaximized()
if not self.view.document.fullscreen_scrollbar:
self.vertical_scrollbar.setVisible(False)
self.frame.layout().setSpacing(0)
self._original_frame_margins = (
self.centralwidget.layout().contentsMargins(),
self.frame.layout().contentsMargins())
self.frame.layout().setContentsMargins(0, 0, 0, 0)
self.centralwidget.layout().setContentsMargins(0, 0, 0, 0)
self.frame.setFrameStyle(self.frame.NoFrame|self.frame.Plain)
super(EbookViewer, self).showFullScreen()
@ -587,17 +395,11 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
self.view.document.page_position.save()
self.clock_label.setVisible(False)
self.pos_label.setVisible(False)
self.frame.setFrameStyle(self.original_frame_style)
self.frame.layout().setSpacing(-1)
self.clock_timer.stop()
self.vertical_scrollbar.setVisible(True)
self.window_mode_changed = 'normal'
self.settings_changed()
self.full_screen_label.setVisible(False)
if hasattr(self, '_original_frame_margins'):
om = self._original_frame_margins
self.centralwidget.layout().setContentsMargins(om[0])
self.frame.layout().setContentsMargins(om[1])
if self.was_maximized:
super(EbookViewer, self).showMaximized()
else:
@ -1154,20 +956,15 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
def read_settings(self):
c = config().parse()
self.splitter.setSizes([1, 300])
if c.remember_window_size:
wg = vprefs.get('viewer_window_geometry', None)
if wg is not None:
self.restoreGeometry(wg)
ss = vprefs.get('viewer_splitter_state', None)
if ss is not None:
self.splitter.restoreState(ss)
self.show_toc_on_open = vprefs.get('viewer_toc_isvisible', False)
av = available_height() - 30
desktop = QApplication.instance().desktop()
av = desktop.availableGeometry(self).height() - 30
if self.height() > av:
self.resize(self.width(), av)
self.splitter.setCollapsible(0, False)
self.splitter.setCollapsible(1, False)
def config(defaults=None):
desc = _('Options to control the ebook viewer')

View File

@ -1,319 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>EbookViewer</class>
<widget class="QMainWindow" name="EbookViewer">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>653</width>
<height>746</height>
</rect>
</property>
<property name="windowTitle">
<string>E-book Viewer</string>
</property>
<property name="windowIcon">
<iconset resource="../../../../resources/images.qrc">
<normaloff>:/images/viewer.png</normaloff>:/images/viewer.png</iconset>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="childrenCollapsible">
<bool>false</bool>
</property>
<widget class="TOCView" name="toc">
<property name="minimumSize">
<size>
<width>150</width>
<height>0</height>
</size>
</property>
</widget>
<widget class="QFrame" name="frame">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="1">
<widget class="QScrollBar" name="vertical_scrollbar">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QScrollBar" name="horizontal_scrollbar">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="DocumentView" name="view" native="true"/>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<widget class="QToolBar" name="tool_bar">
<property name="windowTitle">
<string>toolBar</string>
</property>
<property name="iconSize">
<size>
<width>32</width>
<height>32</height>
</size>
</property>
<attribute name="toolBarArea">
<enum>LeftToolBarArea</enum>
</attribute>
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
<addaction name="action_back"/>
<addaction name="action_forward"/>
<addaction name="separator"/>
<addaction name="action_open_ebook"/>
<addaction name="action_copy"/>
<addaction name="action_font_size_larger"/>
<addaction name="action_font_size_smaller"/>
<addaction name="action_table_of_contents"/>
<addaction name="action_full_screen"/>
<addaction name="separator"/>
<addaction name="action_previous_page"/>
<addaction name="action_next_page"/>
<addaction name="separator"/>
<addaction name="action_bookmark"/>
<addaction name="action_reference_mode"/>
<addaction name="separator"/>
<addaction name="action_preferences"/>
<addaction name="action_metadata"/>
<addaction name="action_load_theme"/>
<addaction name="separator"/>
<addaction name="action_print"/>
</widget>
<widget class="QToolBar" name="tool_bar2">
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
</attribute>
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
<addaction name="action_find_next"/>
<addaction name="action_find_previous"/>
<addaction name="action_toggle_paged_mode"/>
</widget>
<action name="action_back">
<property name="icon">
<iconset resource="../../../../resources/images.qrc">
<normaloff>:/images/back.png</normaloff>:/images/back.png</iconset>
</property>
<property name="text">
<string>Back</string>
</property>
</action>
<action name="action_forward">
<property name="icon">
<iconset resource="../../../../resources/images.qrc">
<normaloff>:/images/forward.png</normaloff>:/images/forward.png</iconset>
</property>
<property name="text">
<string>Forward</string>
</property>
</action>
<action name="action_next_page">
<property name="icon">
<iconset resource="../../../../resources/images.qrc">
<normaloff>:/images/next.png</normaloff>:/images/next.png</iconset>
</property>
<property name="text">
<string>Next page</string>
</property>
</action>
<action name="action_previous_page">
<property name="icon">
<iconset resource="../../../../resources/images.qrc">
<normaloff>:/images/previous.png</normaloff>:/images/previous.png</iconset>
</property>
<property name="text">
<string>Previous page</string>
</property>
</action>
<action name="action_font_size_larger">
<property name="icon">
<iconset resource="../../../../resources/images.qrc">
<normaloff>:/images/font_size_larger.png</normaloff>:/images/font_size_larger.png</iconset>
</property>
<property name="text">
<string>Increase font size</string>
</property>
</action>
<action name="action_font_size_smaller">
<property name="icon">
<iconset resource="../../../../resources/images.qrc">
<normaloff>:/images/font_size_smaller.png</normaloff>:/images/font_size_smaller.png</iconset>
</property>
<property name="text">
<string>Decrease font size</string>
</property>
</action>
<action name="action_table_of_contents">
<property name="icon">
<iconset resource="../../../../resources/images.qrc">
<normaloff>:/images/highlight_only_on.png</normaloff>:/images/highlight_only_on.png</iconset>
</property>
<property name="text">
<string>Table of Contents</string>
</property>
</action>
<action name="action_metadata">
<property name="icon">
<iconset resource="../../../../resources/images.qrc">
<normaloff>:/images/dialog_information.png</normaloff>:/images/dialog_information.png</iconset>
</property>
<property name="text">
<string>Metadata</string>
</property>
</action>
<action name="action_open_ebook">
<property name="icon">
<iconset resource="../../../../resources/images.qrc">
<normaloff>:/images/document_open.png</normaloff>:/images/document_open.png</iconset>
</property>
<property name="text">
<string>Open ebook</string>
</property>
</action>
<action name="action_find_next">
<property name="icon">
<iconset resource="../../../../resources/images.qrc">
<normaloff>:/images/arrow-down.png</normaloff>:/images/arrow-down.png</iconset>
</property>
<property name="text">
<string>Find next</string>
</property>
<property name="toolTip">
<string>Find next occurrence</string>
</property>
</action>
<action name="action_copy">
<property name="icon">
<iconset resource="../../../../resources/images.qrc">
<normaloff>:/images/edit-copy.png</normaloff>:/images/edit-copy.png</iconset>
</property>
<property name="text">
<string>Copy to clipboard</string>
</property>
</action>
<action name="action_preferences">
<property name="icon">
<iconset resource="../../../../resources/images.qrc">
<normaloff>:/images/config.png</normaloff>:/images/config.png</iconset>
</property>
<property name="text">
<string>Preferences</string>
</property>
</action>
<action name="action_reference_mode">
<property name="icon">
<iconset resource="../../../../resources/images.qrc">
<normaloff>:/images/lookfeel.png</normaloff>:/images/lookfeel.png</iconset>
</property>
<property name="text">
<string>Reference Mode</string>
</property>
</action>
<action name="action_bookmark">
<property name="icon">
<iconset resource="../../../../resources/images.qrc">
<normaloff>:/images/bookmarks.png</normaloff>:/images/bookmarks.png</iconset>
</property>
<property name="text">
<string>Bookmark</string>
</property>
</action>
<action name="action_full_screen">
<property name="icon">
<iconset resource="../../../../resources/images.qrc">
<normaloff>:/images/page.png</normaloff>:/images/page.png</iconset>
</property>
<property name="text">
<string>Toggle full screen</string>
</property>
</action>
<action name="action_print">
<property name="icon">
<iconset resource="../../../../resources/images.qrc">
<normaloff>:/images/print.png</normaloff>:/images/print.png</iconset>
</property>
<property name="text">
<string>Print</string>
</property>
</action>
<action name="action_find_previous">
<property name="icon">
<iconset resource="../../../../resources/images.qrc">
<normaloff>:/images/arrow-up.png</normaloff>:/images/arrow-up.png</iconset>
</property>
<property name="text">
<string>Find previous</string>
</property>
<property name="toolTip">
<string>Find previous occurrence</string>
</property>
</action>
<action name="action_toggle_paged_mode">
<property name="checkable">
<bool>true</bool>
</property>
<property name="icon">
<iconset resource="../../../../resources/images.qrc">
<normaloff>:/images/scroll.png</normaloff>:/images/scroll.png</iconset>
</property>
<property name="text">
<string>Toggle Paged mode</string>
</property>
</action>
<action name="action_load_theme">
<property name="icon">
<iconset resource="../../../../resources/images.qrc">
<normaloff>:/images/wizard.png</normaloff>:/images/wizard.png</iconset>
</property>
<property name="text">
<string>Load theme</string>
</property>
<property name="toolTip">
<string>Load a theme</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>
<class>DocumentView</class>
<extends>QWidget</extends>
<header>calibre/gui2/viewer/documentview.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>TOCView</class>
<extends>QTreeView</extends>
<header>calibre/gui2/viewer/toc.h</header>
</customwidget>
</customwidgets>
<resources>
<include location="../../../../resources/images.qrc"/>
</resources>
<connections/>
</ui>

View File

@ -17,12 +17,16 @@ class TOCView(QTreeView):
def __init__(self, *args):
QTreeView.__init__(self, *args)
self.setCursor(Qt.PointingHandCursor)
self.setMinimumWidth(80)
self.header().close()
self.setStyleSheet('''
QTreeView {
background-color: palette(window);
color: palette(window-text);
border: none;
}
QTreeView::item {
border: 1px solid transparent;
padding-top:0.5ex;
@ -34,17 +38,6 @@ class TOCView(QTreeView):
border: 1px solid #bfcde4;
border-radius: 6px;
}
QHeaderView::section {
background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #616161, stop: 0.5 #505050,
stop: 0.6 #434343, stop:1 #656565);
color: white;
padding-left: 4px;
padding-top: 0.5ex;
padding-bottom: 0.5ex;
border: 1px solid #6c6c6c;
font-weight: bold;
}
''')
class TOCItem(QStandardItem):
@ -216,7 +209,6 @@ class TOC(QStandardItemModel):
self.all_items = depth_first = []
for t in toc:
self.appendRow(TOCItem(spine, t, 0, depth_first))
self.setHorizontalHeaderItem(0, QStandardItem(_('Table of Contents')))
for x in depth_first:
possible_enders = [t for t in depth_first if t.depth <= x.depth

View File

@ -0,0 +1,277 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
from __future__ import (unicode_literals, division, absolute_import,
print_function)
__license__ = 'GPL v3'
__copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>'
import textwrap
from PyQt4.Qt import (
QIcon, QWidget, Qt, QGridLayout, QScrollBar, QToolBar, QAction,
QToolButton, QMenu, QDoubleSpinBox, pyqtSignal, QLineEdit,
QRegExpValidator, QRegExp, QPalette, QColor, QBrush, QPainter,
QDockWidget, QSize)
from PyQt4.QtWebKit import QWebView
from calibre.gui2 import rating_font
from calibre.gui2.main_window import MainWindow
from calibre.gui2.search_box import SearchBox2
from calibre.gui2.viewer.documentview import DocumentView
from calibre.gui2.viewer.toc import TOCView
class DoubleSpinBox(QDoubleSpinBox): # {{{
value_changed = pyqtSignal(object, object)
def __init__(self, *args, **kwargs):
QDoubleSpinBox.__init__(self, *args, **kwargs)
self.tt = _('Position in book')
self.setToolTip(self.tt)
def set_value(self, val):
self.blockSignals(True)
self.setValue(val)
self.setToolTip(self.tt +
' [{0:.0%}]'.format(float(val)/self.maximum()))
self.blockSignals(False)
self.value_changed.emit(self.value(), self.maximum())
# }}}
class Reference(QLineEdit): # {{{
goto = pyqtSignal(object)
def __init__(self, *args):
QLineEdit.__init__(self, *args)
self.setValidator(QRegExpValidator(QRegExp(r'\d+\.\d+'), self))
self.setToolTip(textwrap.fill('<p>'+_(
'Go to a reference. To get reference numbers, use the <i>reference '
'mode</i>, by clicking the reference mode button in the toolbar.')))
if hasattr(self, 'setPlaceholderText'):
self.setPlaceholderText(_('Go to...'))
self.editingFinished.connect(self.editing_finished)
def editing_finished(self):
text = unicode(self.text())
self.setText('')
self.goto.emit(text)
# }}}
class Metadata(QWebView): # {{{
def __init__(self, parent):
QWebView.__init__(self, parent.centralWidget())
s = self.settings()
s.setAttribute(s.JavascriptEnabled, False)
self.page().setLinkDelegationPolicy(self.page().DelegateAllLinks)
self.setAttribute(Qt.WA_OpaquePaintEvent, False)
palette = self.palette()
palette.setBrush(QPalette.Base, Qt.transparent)
self.page().setPalette(palette)
self.css = P('templates/book_details.css', data=True).decode('utf-8')
self.view = parent.centralwidget
self.setGeometry(self.view.geometry())
self.setVisible(False)
def show_opf(self, opf, ext=''):
from calibre.gui2.book_details import render_html
from calibre.ebooks.metadata.book.render import mi_to_html
def render_data(mi, use_roman_numbers=True, all_fields=False):
return mi_to_html(mi, use_roman_numbers=use_roman_numbers, rating_font=rating_font())
mi = opf.to_book_metadata()
html = render_html(mi, self.css, True, self, render_data_func=render_data)
self.setHtml(html)
def setVisible(self, x):
if x:
self.setGeometry(self.view.geometry())
QWebView.setVisible(self, x)
def paintEvent(self, ev):
p = QPainter(self)
p.fillRect(ev.region().boundingRect(), QBrush(QColor(200, 200, 200, 220), Qt.SolidPattern))
p.end()
QWebView.paintEvent(self, ev)
# }}}
class History(list): # {{{
def __init__(self, action_back, action_forward):
self.action_back = action_back
self.action_forward = action_forward
super(History, self).__init__(self)
self.insert_pos = 0
self.back_pos = None
self.forward_pos = None
self.set_actions()
def set_actions(self):
self.action_back.setDisabled(self.back_pos is None)
self.action_forward.setDisabled(self.forward_pos is None)
def back(self, from_pos):
# Back clicked
if self.back_pos is None:
return None
item = self[self.back_pos]
self.forward_pos = self.back_pos+1
if self.forward_pos >= len(self):
self.append(from_pos)
self.forward_pos = len(self) - 1
self.insert_pos = self.forward_pos
self.back_pos = None if self.back_pos == 0 else self.back_pos - 1
self.set_actions()
return item
def forward(self, from_pos):
if self.forward_pos is None:
return None
item = self[self.forward_pos]
self.back_pos = self.forward_pos - 1
if self.back_pos < 0:
self.back_pos = None
self.insert_pos = self.back_pos or 0
self.forward_pos = None if self.forward_pos > len(self) - 2 else self.forward_pos + 1
self.set_actions()
return item
def add(self, item):
self[self.insert_pos:] = []
while self.insert_pos > 0 and self[self.insert_pos-1] == item:
self.insert_pos -= 1
self[self.insert_pos:] = []
self.insert(self.insert_pos, item)
# The next back must go to item
self.back_pos = self.insert_pos
self.insert_pos += 1
# There can be no forward
self.forward_pos = None
self.set_actions()
# }}}
class Main(MainWindow):
def __init__(self, debug_javascript):
MainWindow.__init__(self, None)
self.setObjectName('EbookViewer')
self.setWindowIcon(QIcon(I('viewer.png')))
self.setDockOptions(self.AnimatedDocks | self.AllowTabbedDocks)
self.centralwidget = c = QWidget(self)
c.setObjectName('centralwidget')
self.setCentralWidget(c)
self.central_layout = cl = QGridLayout(c)
c.setLayout(cl), cl.setContentsMargins(0, 0, 0, 0)
self.view = v = DocumentView(self)
self.view.initialize_view(debug_javascript)
v.setObjectName('view')
cl.addWidget(v)
self.vertical_scrollbar = vs = QScrollBar(c)
vs.setOrientation(Qt.Vertical), vs.setObjectName("vertical_scrollbar")
cl.addWidget(vs, 0, 1, 2, 1)
self.horizontal_scrollbar = hs = QScrollBar(c)
hs.setOrientation(Qt.Vertical), hs.setObjectName("horizontal_scrollbar")
cl.addWidget(hs, 1, 0, 1, 1)
self.tool_bar = tb = QToolBar(self)
tb.setObjectName('tool_bar'), tb.setIconSize(QSize(32, 32))
self.addToolBar(Qt.LeftToolBarArea, tb)
self.tool_bar2 = tb2 = QToolBar(self)
tb2.setObjectName('tool_bar2')
self.addToolBar(Qt.TopToolBarArea, tb2)
self.tool_bar.setContextMenuPolicy(Qt.PreventContextMenu)
self.tool_bar2.setContextMenuPolicy(Qt.PreventContextMenu)
self.pos = DoubleSpinBox()
self.pos.setDecimals(1)
self.pos.setSuffix('/'+_('Unknown')+' ')
self.pos.setMinimum(1.)
self.tool_bar2.addWidget(self.pos)
self.tool_bar2.addSeparator()
self.reference = Reference()
self.tool_bar2.addWidget(self.reference)
self.tool_bar2.addSeparator()
self.search = SearchBox2(self)
self.search.setMinimumContentsLength(20)
self.search.initialize('viewer_search_history')
self.search.setToolTip(_('Search for text in book'))
self.search.setMinimumWidth(200)
self.tool_bar2.addWidget(self.search)
self.toc_dock = d = QDockWidget(_('Table of Contents'), self)
self.toc = TOCView(self)
d.setObjectName('toc-dock')
d.setWidget(self.toc)
d.close() # starts out hidden
self.addDockWidget(Qt.LeftDockWidgetArea, d)
d.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea)
self.create_actions()
self.metadata = Metadata(self)
self.history = History(self.action_back, self.action_forward)
self.resize(653, 746)
def create_actions(self):
def a(name, text, icon, tb=None, sc_name=None, menu_name=None, popup_mode=QToolButton.MenuButtonPopup):
name = 'action_' + name
if isinstance(text, QDockWidget):
ac = text.toggleViewAction()
ac.setIcon(QIcon(I(icon)))
else:
ac = QAction(QIcon(I(icon)), text, self)
setattr(self, name, ac)
ac.setObjectName(name)
(tb or self.tool_bar).addAction(ac)
if sc_name:
ac.setToolTip(unicode(ac.text()) + (' [%s]' % _(' or ').join(self.view.shortcuts.get_shortcuts(sc_name))))
if menu_name is not None:
menu_name += '_menu'
m = QMenu()
setattr(self, menu_name, m)
ac.setMenu(m)
w = (tb or self.tool_bar).widgetForAction(ac)
w.setPopupMode(popup_mode)
return ac
a('back', _('Back'), 'back.png')
a('forward', _('Forward'), 'forward.png')
self.tool_bar.addSeparator()
a('open_ebook', _('Open ebook'), 'document_open.png', menu_name='open_history')
a('copy', _('Copy to clipboard'), 'edit-copy.png').setDisabled(True)
a('font_size_larger', _('Increase font size'), 'font_size_larger.png')
a('font_size_smaller', _('Decrease font size'), 'font_size_smaller.png')
a('table_of_contents', self.toc_dock, 'highlight_only_on.png')
a('full_screen', _('Toggle full screen'), 'page.png', sc_name='Fullscreen').setCheckable(True)
self.tool_bar.addSeparator()
a('previous_page', _('Previous page'), 'previous.png')
a('next_page', _('Next page'), 'next.png')
self.tool_bar.addSeparator()
a('bookmark', _('Bookmark'), 'bookmarks.png', menu_name='bookmarks', popup_mode=QToolButton.InstantPopup)
a('reference_mode', _('Reference mode'), 'lookfeel.png').setCheckable(True)
self.tool_bar.addSeparator()
a('preferences', _('Preferences'), 'config.png')
a('metadata', _('Show book metadata'), 'dialog_information.png').setCheckable(True)
a('load_theme', _('Load a theme'), 'wizard.png', menu_name='themes', popup_mode=QToolButton.InstantPopup)
self.tool_bar.addSeparator()
a('print', _('Print'), 'print.png', menu_name='print')
self.print_menu.addAction(QIcon(I('print-preview.png')), _('Print Preview'))
a('find_next', _('Find next occurrence'), 'arrow-down.png', tb=self.tool_bar2)
a('find_previous', _('Find previous occurrence'), 'arrow-up.png', tb=self.tool_bar2)
a('toggle_paged_mode', _('Toggle paged mode'), 'scroll.png', tb=self.tool_bar2)