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' __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>' __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
import traceback, os, sys, functools, textwrap import traceback, os, sys, functools
from functools import partial from functools import partial
from threading import Thread from threading import Thread
from PyQt4.Qt import ( from PyQt4.Qt import (
QApplication, Qt, QIcon, QTimer, QByteArray, QSize, QTime, QDoubleSpinBox, QApplication, Qt, QIcon, QTimer, QByteArray, QSize, QTime, QLabel,
QLabel, QPropertyAnimation, pyqtSignal, QUrl, QRegExpValidator, QRegExp, QPropertyAnimation, QUrl, QInputDialog, QAction, QModelIndex)
QLineEdit, QToolButton, QMenu, QInputDialog, QAction, QModelIndex, QPalette,
QPainter, QBrush, QColor)
from PyQt4.QtWebKit import QWebView
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.printing import Printing
from calibre.gui2.viewer.bookmarkmanager import BookmarkManager from calibre.gui2.viewer.bookmarkmanager import BookmarkManager
from calibre.gui2.viewer.toc import TOC from calibre.gui2.viewer.toc import TOC
from calibre.gui2.widgets import ProgressIndicator from calibre.gui2.widgets import ProgressIndicator
from calibre.gui2.main_window import MainWindow from calibre.gui2 import (Application, ORG_NAME, APP_UID, choose_files,
from calibre.gui2 import (Application, ORG_NAME, APP_UID, choose_files, rating_font, info_dialog, error_dialog, open_url, setup_gui_option_parser, detach_gui)
info_dialog, error_dialog, open_url, available_height, setup_gui_option_parser, detach_gui)
from calibre.ebooks.oeb.iterator.book import EbookIterator from calibre.ebooks.oeb.iterator.book import EbookIterator
from calibre.ebooks import DRMError from calibre.ebooks import DRMError
from calibre.constants import islinux, filesystem_encoding from calibre.constants import islinux, filesystem_encoding
from calibre.utils.config import Config, StringConfig, JSONConfig 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.customize.ui import available_input_formats
from calibre import as_unicode, force_unicode, isbytestring from calibre import as_unicode, force_unicode, isbytestring
from calibre.ptempfile import reset_base_dir from calibre.ptempfile import reset_base_dir
@ -47,145 +42,15 @@ class Worker(Thread):
self.exception = err self.exception = err
self.traceback = traceback.format_exc() 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): class RecentAction(QAction):
def __init__(self, path, parent): def __init__(self, path, parent):
self.path = path self.path = path
QAction.__init__(self, os.path.basename(path), parent) 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 ' FLOW_MODE_TT = _('Switch to paged mode - where the text is broken up '
'into pages like a paper book') 'into pages like a paper book')
PAGED_MODE_TT = _('Switch to flow mode - where the text is not broken up ' 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, def __init__(self, pathtoebook=None, debug_javascript=False, open_at=None,
start_in_fullscreen=False): start_in_fullscreen=False):
MainWindow.__init__(self, None) MainWindow.__init__(self, debug_javascript)
self.setupUi(self)
self.view.initialize_view(debug_javascript)
self.view.magnification_changed.connect(self.magnification_changed) self.view.magnification_changed.connect(self.magnification_changed)
self.show_toc_on_open = False self.show_toc_on_open = False
self.current_book_has_toc = False self.current_book_has_toc = False
@ -212,31 +75,11 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
self.selected_text = None self.selected_text = None
self.was_maximized = False self.was_maximized = False
self.read_settings() 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.pos.value_changed.connect(self.update_pos_label)
self.splitter.setCollapsible(0, False)
self.splitter.setCollapsible(1, False)
self.pos.setMinimumWidth(150) 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.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.view.set_manager(self)
self.pi = ProgressIndicator(self) self.pi = ProgressIndicator(self)
self.toc.setVisible(False)
self.action_quit = QAction(_('&Quit'), self) self.action_quit = QAction(_('&Quit'), self)
self.addAction(self.action_quit) self.addAction(self.action_quit)
self.view_resized_timer = QTimer(self) self.view_resized_timer = QTimer(self)
@ -244,11 +87,6 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
self.view_resized_timer.setSingleShot(True) self.view_resized_timer.setSingleShot(True)
self.resize_in_progress = False self.resize_in_progress = False
self.action_quit.triggered.connect(self.quit) 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_reference_mode.triggered[bool].connect(self.view.reference_mode)
self.action_metadata.triggered[bool].connect(self.metadata.setVisible) self.action_metadata.triggered[bool].connect(self.metadata.setVisible)
self.action_table_of_contents.toggled[bool].connect(self.set_toc_visible) 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_next.triggered.connect(self.find_next)
self.action_find_previous.triggered.connect(self.find_previous) self.action_find_previous.triggered.connect(self.find_previous)
self.action_full_screen.triggered[bool].connect(self.toggle_fullscreen) 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_back.triggered[bool].connect(self.back)
self.action_forward.triggered[bool].connect(self.forward) self.action_forward.triggered[bool].connect(self.forward)
self.action_preferences.triggered.connect(self.do_config) 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.toc.pressed[QModelIndex].connect(self.toc_clicked)
self.reference.goto.connect(self.goto) self.reference.goto.connect(self.goto)
self.bookmarks_menu = QMenu()
self.action_bookmark.setMenu(self.bookmarks_menu)
self.set_bookmarks([]) 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() self.load_theme_menu()
if pathtoebook is not None: if pathtoebook is not None:
f = functools.partial(self.load_ebook, pathtoebook, open_at=open_at) f = functools.partial(self.load_ebook, pathtoebook, open_at=open_at)
QTimer.singleShot(50, f) QTimer.singleShot(50, f)
self.view.setMinimumSize(100, 100) 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(''' self.full_screen_label = QLabel('''
<center> <center>
<h1>%s</h1> <h1>%s</h1>
@ -338,28 +163,19 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
font-size: larger; font-size: larger;
padding: 5px; padding: 5px;
}''' }'''
self.original_frame_style = self.frame.frameStyle()
self.pos_label = QLabel('2000/4000', self) self.pos_label = QLabel('2000/4000', self)
self.pos_label.setVisible(False) self.pos_label.setVisible(False)
self.pos_label.setFocusPolicy(Qt.NoFocus) self.pos_label.setFocusPolicy(Qt.NoFocus)
self.clock_timer = QTimer(self) self.clock_timer = QTimer(self)
self.clock_timer.timeout.connect(self.update_clock) 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.action_print.triggered.connect(self.print_book)
self.print_menu.actions()[0].triggered.connect(self.print_preview) self.print_menu.actions()[0].triggered.connect(self.print_preview)
self.open_history_menu = QMenu()
self.clear_recent_history_action = QAction( self.clear_recent_history_action = QAction(
_('Clear list of recently opened books'), self) _('Clear list of recently opened books'), self)
self.clear_recent_history_action.triggered.connect(self.clear_recent_history) self.clear_recent_history_action.triggered.connect(self.clear_recent_history)
self.build_recent_menu() self.build_recent_menu()
self.action_open_ebook.setMenu(self.open_history_menu)
self.open_history_menu.triggered[QAction].connect(self.open_recent) 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'): for x in ('tool_bar', 'tool_bar2'):
x = getattr(self, x) x = getattr(self, x)
@ -400,7 +216,7 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
self.load_path(self.view.last_loaded_path) self.load_path(self.view.last_loaded_path)
def set_toc_visible(self, yes): def set_toc_visible(self, yes):
self.toc.setVisible(yes) self.toc_dock.setVisible(yes)
def clear_recent_history(self, *args): def clear_recent_history(self, *args):
vprefs.set('viewer_open_history', []) vprefs.set('viewer_open_history', [])
@ -445,19 +261,16 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
def save_state(self): def save_state(self):
state = bytearray(self.saveState(self.STATE_VERSION)) state = bytearray(self.saveState(self.STATE_VERSION))
vprefs['viewer_toolbar_state'] = state vprefs['main_window_state'] = state
if not self.isFullScreen(): if not self.isFullScreen():
vprefs.set('viewer_window_geometry', bytearray(self.saveGeometry())) vprefs.set('viewer_window_geometry', bytearray(self.saveGeometry()))
if self.current_book_has_toc: if self.current_book_has_toc:
vprefs.set('viewer_toc_isvisible', bool(self.toc.isVisible())) vprefs.set('viewer_toc_isvisible', bool(self.toc_dock.isVisible()))
if self.toc.isVisible():
vprefs.set('viewer_splitter_state',
bytearray(self.splitter.saveState()))
vprefs['multiplier'] = self.view.multiplier vprefs['multiplier'] = self.view.multiplier
vprefs['in_paged_mode'] = not self.action_toggle_paged_mode.isChecked() vprefs['in_paged_mode'] = not self.action_toggle_paged_mode.isChecked()
def restore_state(self): def restore_state(self):
state = vprefs.get('viewer_toolbar_state', None) state = vprefs.get('main_window_state', None)
if state is not None: if state is not None:
try: try:
state = QByteArray(state) state = QByteArray(state)
@ -471,6 +284,7 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
# specific location, ensure they are visible. # specific location, ensure they are visible.
self.tool_bar.setVisible(True) self.tool_bar.setVisible(True)
self.tool_bar2.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', self.action_toggle_paged_mode.setChecked(not vprefs.get('in_paged_mode',
True)) True))
self.toggle_paged_mode(self.action_toggle_paged_mode.isChecked(), self.toggle_paged_mode(self.action_toggle_paged_mode.isChecked(),
@ -516,13 +330,7 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
self.was_maximized = self.isMaximized() self.was_maximized = self.isMaximized()
if not self.view.document.fullscreen_scrollbar: if not self.view.document.fullscreen_scrollbar:
self.vertical_scrollbar.setVisible(False) 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.centralwidget.layout().setContentsMargins(0, 0, 0, 0)
self.frame.setFrameStyle(self.frame.NoFrame|self.frame.Plain)
super(EbookViewer, self).showFullScreen() super(EbookViewer, self).showFullScreen()
@ -587,17 +395,11 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
self.view.document.page_position.save() self.view.document.page_position.save()
self.clock_label.setVisible(False) self.clock_label.setVisible(False)
self.pos_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.clock_timer.stop()
self.vertical_scrollbar.setVisible(True) self.vertical_scrollbar.setVisible(True)
self.window_mode_changed = 'normal' self.window_mode_changed = 'normal'
self.settings_changed() self.settings_changed()
self.full_screen_label.setVisible(False) 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: if self.was_maximized:
super(EbookViewer, self).showMaximized() super(EbookViewer, self).showMaximized()
else: else:
@ -1154,20 +956,15 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
def read_settings(self): def read_settings(self):
c = config().parse() c = config().parse()
self.splitter.setSizes([1, 300])
if c.remember_window_size: if c.remember_window_size:
wg = vprefs.get('viewer_window_geometry', None) wg = vprefs.get('viewer_window_geometry', None)
if wg is not None: if wg is not None:
self.restoreGeometry(wg) 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) 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: if self.height() > av:
self.resize(self.width(), av) self.resize(self.width(), av)
self.splitter.setCollapsible(0, False)
self.splitter.setCollapsible(1, False)
def config(defaults=None): def config(defaults=None):
desc = _('Options to control the ebook viewer') 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): def __init__(self, *args):
QTreeView.__init__(self, *args) QTreeView.__init__(self, *args)
self.setCursor(Qt.PointingHandCursor)
self.setMinimumWidth(80)
self.header().close()
self.setStyleSheet(''' self.setStyleSheet('''
QTreeView { QTreeView {
background-color: palette(window); background-color: palette(window);
color: palette(window-text); color: palette(window-text);
border: none; border: none;
} }
QTreeView::item { QTreeView::item {
border: 1px solid transparent; border: 1px solid transparent;
padding-top:0.5ex; padding-top:0.5ex;
@ -34,17 +38,6 @@ class TOCView(QTreeView):
border: 1px solid #bfcde4; border: 1px solid #bfcde4;
border-radius: 6px; 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): class TOCItem(QStandardItem):
@ -216,7 +209,6 @@ class TOC(QStandardItemModel):
self.all_items = depth_first = [] self.all_items = depth_first = []
for t in toc: for t in toc:
self.appendRow(TOCItem(spine, t, 0, depth_first)) self.appendRow(TOCItem(spine, t, 0, depth_first))
self.setHorizontalHeaderItem(0, QStandardItem(_('Table of Contents')))
for x in depth_first: for x in depth_first:
possible_enders = [t for t in depth_first if t.depth <= x.depth 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)