This commit is contained in:
GRiker 2012-09-13 06:26:06 -06:00
commit 1cc0a6861d
16 changed files with 302 additions and 41 deletions

View File

@ -108,10 +108,10 @@ After creating the saved search, you can use it as a restriction.
Useful Template Functions Useful Template Functions
------------------------- -------------------------
You might want to use the genre information in a template, such as with save to disk or send to device. The question might then be "How do I get the outermost genre name or names?" An |app| template function, subitems, is provided to make doing this easier. You might want to use the genre information in a template, such as with save to disk or send to device. The question might then be "How do I get the outermost genre name or names?" A |app| template function, subitems, is provided to make doing this easier.
For example, assume you want to add the outermost genre level to the save-to-disk template to make genre folders, as in "History/The Gathering Storm - Churchill, Winston". To do this, you must extract the first level of the hierarchy and add it to the front along with a slash to indicate that it should make a folder. The template below accomplishes this:: For example, assume you want to add the outermost genre level to the save-to-disk template to make genre folders, as in "History/The Gathering Storm - Churchill, Winston". To do this, you must extract the first level of the hierarchy and add it to the front along with a slash to indicate that it should make a folder. The template below accomplishes this::
{#genre:subitems(0,1)||/}{title} - {authors} {#genre:subitems(0,1)||/}{title} - {authors}
See :ref:`The |app| template language <templatelangcalibre>` for more information templates and the subitem function. See :ref:`The template language <templatelangcalibre>` for more information templates and the :func:`subitems` function.

View File

@ -95,6 +95,10 @@ class DevicePlugin(Plugin):
#: call post_yank_cleanup(). #: call post_yank_cleanup().
MANAGES_DEVICE_PRESENCE = False MANAGES_DEVICE_PRESENCE = False
#: If set the True, calibre will call the :meth:`get_driveinfo()` method
#: after the books lists have been loaded to get the driveinfo.
SLOW_DRIVEINFO = False
@classmethod @classmethod
def get_gui_name(cls): def get_gui_name(cls):
if hasattr(cls, 'gui_name'): if hasattr(cls, 'gui_name'):
@ -352,6 +356,18 @@ class DevicePlugin(Plugin):
""" """
raise NotImplementedError() raise NotImplementedError()
def get_driveinfo(self):
'''
Return the driveinfo dictionary. Usually called from
get_device_information(), but if loading the driveinfo is slow for this
driver, then it should set SLOW_DRIVEINFO. In this case, this method
will be called by calibre after the book lists have been loaded. Note
that it is not called on the device thread, so the driver should cache
the drive info in the books() method and this function should return
the cached data.
'''
return {}
def card_prefix(self, end_session=True): def card_prefix(self, end_session=True):
''' '''
Return a 2 element list of the prefix to paths on the cards. Return a 2 element list of the prefix to paths on the cards.

View File

@ -35,6 +35,7 @@ class MTP_DEVICE(BASE):
MANAGES_DEVICE_PRESENCE = True MANAGES_DEVICE_PRESENCE = True
FORMATS = ['epub', 'azw3', 'mobi', 'pdf'] FORMATS = ['epub', 'azw3', 'mobi', 'pdf']
DEVICE_PLUGBOARD_NAME = 'MTP_DEVICE' DEVICE_PLUGBOARD_NAME = 'MTP_DEVICE'
SLOW_DRIVEINFO = True
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
BASE.__init__(self, *args, **kwargs) BASE.__init__(self, *args, **kwargs)
@ -76,6 +77,7 @@ class MTP_DEVICE(BASE):
def open(self, devices, library_uuid): def open(self, devices, library_uuid):
self.current_library_uuid = library_uuid self.current_library_uuid = library_uuid
self.location_paths = None self.location_paths = None
self.driveinfo = {}
BASE.open(self, devices, library_uuid) BASE.open(self, devices, library_uuid)
h = self.prefs['history'] h = self.prefs['history']
if self.current_serial_num: if self.current_serial_num:
@ -109,13 +111,17 @@ class MTP_DEVICE(BASE):
self.put_file(storage, self.DRIVEINFO, BytesIO(raw), len(raw)) self.put_file(storage, self.DRIVEINFO, BytesIO(raw), len(raw))
self.driveinfo[location_code] = dinfo self.driveinfo[location_code] = dinfo
def get_driveinfo(self):
if not self.driveinfo:
self.driveinfo = {}
for sid, location_code in ( (self._main_id, 'main'), (self._carda_id,
'A'), (self._cardb_id, 'B')):
if sid is None: continue
self._update_drive_info(self.filesystem_cache.storage(sid), location_code)
return self.driveinfo
def get_device_information(self, end_session=True): def get_device_information(self, end_session=True):
self.report_progress(1.0, _('Get device information...')) self.report_progress(1.0, _('Get device information...'))
self.driveinfo = {}
for sid, location_code in ( (self._main_id, 'main'), (self._carda_id,
'A'), (self._cardb_id, 'B')):
if sid is None: continue
self._update_drive_info(self.filesystem_cache.storage(sid), location_code)
dinfo = self.get_basic_device_information() dinfo = self.get_basic_device_information()
return tuple( list(dinfo) + [self.driveinfo] ) return tuple( list(dinfo) + [self.driveinfo] )
@ -135,6 +141,7 @@ class MTP_DEVICE(BASE):
def books(self, oncard=None, end_session=True): def books(self, oncard=None, end_session=True):
from calibre.devices.mtp.books import JSONCodec from calibre.devices.mtp.books import JSONCodec
from calibre.devices.mtp.books import BookList, Book from calibre.devices.mtp.books import BookList, Book
self.get_driveinfo() # Ensure driveinfo is loaded
sid = {'carda':self._carda_id, 'cardb':self._cardb_id}.get(oncard, sid = {'carda':self._carda_id, 'cardb':self._cardb_id}.get(oncard,
self._main_id) self._main_id)
if sid is None: if sid is None:

View File

@ -230,6 +230,9 @@ class FilesystemCache(object):
continue # Ignore .txt files in the root continue # Ignore .txt files in the root
yield x yield x
def __len__(self):
return len(self.id_map)
def resolve_mtp_id_path(self, path): def resolve_mtp_id_path(self, path):
if not path.startswith('mtp:::'): if not path.startswith('mtp:::'):
raise ValueError('%s is not a valid MTP path'%path) raise ValueError('%s is not a valid MTP path'%path)

View File

@ -222,7 +222,8 @@ class MTP_DEVICE(MTPDeviceBase):
self.current_friendly_name, self.current_friendly_name,
self.format_errorstack(all_errs))) self.format_errorstack(all_errs)))
self._filesystem_cache = FilesystemCache(storage, all_items) self._filesystem_cache = FilesystemCache(storage, all_items)
debug('Filesystem metadata loaded in %g seconds'%(time.time()-st)) debug('Filesystem metadata loaded in %g seconds (%d objects)'%(
time.time()-st, len(self._filesystem_cache)))
return self._filesystem_cache return self._filesystem_cache
@synchronous @synchronous

View File

@ -220,7 +220,8 @@ class MTP_DEVICE(MTPDeviceBase):
all_storage.append(storage) all_storage.append(storage)
items.append(id_map.itervalues()) items.append(id_map.itervalues())
self._filesystem_cache = FilesystemCache(all_storage, chain(*items)) self._filesystem_cache = FilesystemCache(all_storage, chain(*items))
debug('Filesystem metadata loaded in %g seconds'%(time.time()-st)) debug('Filesystem metadata loaded in %g seconds (%d objects)'%(
time.time()-st, len(self._filesystem_cache)))
return self._filesystem_cache return self._filesystem_cache
@same_thread @same_thread

View File

@ -499,6 +499,7 @@ class FileIconProvider(QFileIconProvider):
self.icons = {} self.icons = {}
for key in self.__class__.ICONS.keys(): for key in self.__class__.ICONS.keys():
self.icons[key] = I('mimetypes/')+self.__class__.ICONS[key]+'.png' self.icons[key] = I('mimetypes/')+self.__class__.ICONS[key]+'.png'
self.icons['calibre'] = I('lt.png')
for i in ('dir', 'default', 'zero'): for i in ('dir', 'default', 'zero'):
self.icons[i] = QIcon(self.icons[i]) self.icons[i] = QIcon(self.icons[i])

View File

@ -8,8 +8,8 @@ import shutil
from PyQt4.Qt import QString, SIGNAL from PyQt4.Qt import QString, SIGNAL
from calibre.gui2.convert.single import Config, sort_formats_by_preference, \ from calibre.gui2.convert.single import (Config, sort_formats_by_preference,
GroupModel GroupModel, gprefs)
from calibre.customize.ui import available_output_formats from calibre.customize.ui import available_output_formats
from calibre.gui2 import ResizableDialog from calibre.gui2 import ResizableDialog
from calibre.gui2.convert.look_and_feel import LookAndFeelWidget from calibre.gui2.convert.look_and_feel import LookAndFeelWidget
@ -62,6 +62,9 @@ class BulkConfig(Config):
'settings.')) 'settings.'))
o.setChecked(False) o.setChecked(False)
geom = gprefs.get('convert_bulk_dialog_geom', None)
if geom:
self.restoreGeometry(geom)
def setup_pipeline(self, *args): def setup_pipeline(self, *args):
oidx = self.groups.currentIndex().row() oidx = self.groups.currentIndex().row()
@ -139,3 +142,9 @@ class BulkConfig(Config):
self._recommendations = recs self._recommendations = recs
ResizableDialog.accept(self) ResizableDialog.accept(self)
def done(self, r):
if self.isVisible():
gprefs['convert_bulk_dialog_geom'] = \
bytearray(self.saveGeometry())
return ResizableDialog.done(self, r)

View File

@ -10,7 +10,7 @@ import cPickle, shutil
from PyQt4.Qt import QString, SIGNAL, QAbstractListModel, Qt, QVariant, QFont from PyQt4.Qt import QString, SIGNAL, QAbstractListModel, Qt, QVariant, QFont
from calibre.gui2 import ResizableDialog, NONE from calibre.gui2 import ResizableDialog, NONE, gprefs
from calibre.ebooks.conversion.config import (GuiRecommendations, save_specifics, from calibre.ebooks.conversion.config import (GuiRecommendations, save_specifics,
load_specifics) load_specifics)
from calibre.gui2.convert.single_ui import Ui_Dialog from calibre.gui2.convert.single_ui import Ui_Dialog
@ -146,6 +146,9 @@ class Config(ResizableDialog, Ui_Dialog):
rb = self.buttonBox.button(self.buttonBox.RestoreDefaults) rb = self.buttonBox.button(self.buttonBox.RestoreDefaults)
self.connect(rb, SIGNAL('clicked()'), self.restore_defaults) self.connect(rb, SIGNAL('clicked()'), self.restore_defaults)
self.groups.setMouseTracking(True) self.groups.setMouseTracking(True)
geom = gprefs.get('convert_single_dialog_geom', None)
if geom:
self.restoreGeometry(geom)
def restore_defaults(self): def restore_defaults(self):
delete_specifics(self.db, self.book_id) delete_specifics(self.db, self.book_id)
@ -263,6 +266,12 @@ class Config(ResizableDialog, Ui_Dialog):
self.break_cycles() self.break_cycles()
ResizableDialog.reject(self) ResizableDialog.reject(self)
def done(self, r):
if self.isVisible():
gprefs['convert_single_dialog_geom'] = \
bytearray(self.saveGeometry())
return ResizableDialog.done(self, r)
def break_cycles(self): def break_cycles(self):
for i in range(self.stack.count()): for i in range(self.stack.count()):
w = self.stack.widget(i) w = self.stack.widget(i)

View File

@ -433,6 +433,15 @@ class DeviceManager(Thread): # {{{
return self.create_job_step(self._get_device_information, done, return self.create_job_step(self._get_device_information, done,
description=_('Get device information'), to_job=add_as_step_to_job) description=_('Get device information'), to_job=add_as_step_to_job)
def slow_driveinfo(self):
''' Update the stored device information with the driveinfo if the
device indicates that getting driveinfo is slow '''
info = self._device_information['info']
if (not info[4] and self.device.SLOW_DRIVEINFO):
info = list(info)
info[4] = self.device.get_driveinfo()
self._device_information['info'] = tuple(info)
def get_current_device_information(self): def get_current_device_information(self):
return self._device_information return self._device_information
@ -1023,6 +1032,7 @@ class DeviceMixin(object): # {{{
if job.failed: if job.failed:
self.device_job_exception(job) self.device_job_exception(job)
return return
self.device_manager.slow_driveinfo()
# set_books_in_library might schedule a sync_booklists job # set_books_in_library might schedule a sync_booklists job
self.set_books_in_library(job.result, reset=True, add_as_step_to_job=job) self.set_books_in_library(job.result, reset=True, add_as_step_to_job=job)
mainlist, cardalist, cardblist = job.result mainlist, cardalist, cardblist = job.result

View File

@ -18,6 +18,7 @@ from calibre.ebooks import BOOK_EXTENSIONS
from calibre.gui2 import error_dialog from calibre.gui2 import error_dialog
from calibre.gui2.dialogs.template_dialog import TemplateDialog from calibre.gui2.dialogs.template_dialog import TemplateDialog
from calibre.utils.date import parse_date from calibre.utils.date import parse_date
from calibre.gui2.device_drivers.mtp_folder_browser import Browser
class FormatsConfig(QWidget): # {{{ class FormatsConfig(QWidget): # {{{
@ -117,19 +118,36 @@ class TemplateConfig(QWidget): # {{{
class SendToConfig(QWidget): # {{{ class SendToConfig(QWidget): # {{{
def __init__(self, val): def __init__(self, val, device):
QWidget.__init__(self) QWidget.__init__(self)
self.t = t = QLineEdit(self) self.t = t = QLineEdit(self)
t.setText(', '.join(val or [])) t.setText(', '.join(val or []))
t.setCursorPosition(0) t.setCursorPosition(0)
self.l = l = QVBoxLayout(self) self.l = l = QGridLayout(self)
self.setLayout(l) self.setLayout(l)
self.m = m = QLabel('<p>'+_('''A <b>list of &folders</b> on the device to self.m = m = QLabel('<p>'+_('''A <b>list of &folders</b> on the device to
which to send ebooks. The first one that exists will be used:''')) which to send ebooks. The first one that exists will be used:'''))
m.setWordWrap(True) m.setWordWrap(True)
m.setBuddy(t) m.setBuddy(t)
l.addWidget(m) l.addWidget(m, 0, 0, 1, 2)
l.addWidget(t) l.addWidget(t, 1, 0)
self.b = b = QToolButton()
l.addWidget(b, 1, 1)
b.setIcon(QIcon(I('document_open.png')))
b.clicked.connect(self.browse)
b.setToolTip(_('Browse for a folder on the device'))
self._device = weakref.ref(device)
@property
def device(self):
return self._device()
def browse(self):
b = Browser(self.device.filesystem_cache, show_files=False,
parent=self)
if b.exec_() == b.Accepted:
sid, path = b.current_item
self.t.setText('/'.join(path[1:]))
@property @property
def value(self): def value(self):
@ -183,8 +201,9 @@ class Rule(QWidget):
remove = pyqtSignal(object) remove = pyqtSignal(object)
def __init__(self, rule=None): def __init__(self, device, rule=None):
QWidget.__init__(self) QWidget.__init__(self)
self._device = weakref.ref(device)
self.l = l = QHBoxLayout() self.l = l = QHBoxLayout()
self.setLayout(l) self.setLayout(l)
@ -198,6 +217,11 @@ class Rule(QWidget):
self.folder = f = QLineEdit(self) self.folder = f = QLineEdit(self)
f.setPlaceholderText(_('Folder on the device')) f.setPlaceholderText(_('Folder on the device'))
l.addWidget(f) l.addWidget(f)
self.b = b = QToolButton()
l.addWidget(b)
b.setIcon(QIcon(I('document_open.png')))
b.clicked.connect(self.browse)
b.setToolTip(_('Browse for a folder on the device'))
self.rb = rb = QPushButton(QIcon(I('list_remove.png')), self.rb = rb = QPushButton(QIcon(I('list_remove.png')),
_('&Remove rule'), self) _('&Remove rule'), self)
l.addWidget(rb) l.addWidget(rb)
@ -217,6 +241,17 @@ class Rule(QWidget):
self.ignore = False self.ignore = False
@property
def device(self):
return self._device()
def browse(self):
b = Browser(self.device.filesystem_cache, show_files=False,
parent=self)
if b.exec_() == b.Accepted:
sid, path = b.current_item
self.folder.setText('/'.join(path[1:]))
def removed(self): def removed(self):
self.remove.emit(self) self.remove.emit(self)
@ -232,8 +267,9 @@ class Rule(QWidget):
class FormatRules(QGroupBox): class FormatRules(QGroupBox):
def __init__(self, rules): def __init__(self, device, rules):
QGroupBox.__init__(self, _('Format specific sending')) QGroupBox.__init__(self, _('Format specific sending'))
self._device = weakref.ref(device)
self.l = l = QVBoxLayout() self.l = l = QVBoxLayout()
self.setLayout(l) self.setLayout(l)
self.la = la = QLabel('<p>'+_( self.la = la = QLabel('<p>'+_(
@ -251,7 +287,7 @@ class FormatRules(QGroupBox):
l.addWidget(sa) l.addWidget(sa)
self.widgets = [] self.widgets = []
for rule in rules: for rule in rules:
r = Rule(rule) r = Rule(device, rule)
self.widgets.append(r) self.widgets.append(r)
w.l.addWidget(r) w.l.addWidget(r)
r.remove.connect(self.remove_rule) r.remove.connect(self.remove_rule)
@ -264,8 +300,12 @@ class FormatRules(QGroupBox):
b.clicked.connect(self.add_rule) b.clicked.connect(self.add_rule)
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Ignored) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Ignored)
@property
def device(self):
return self._device()
def add_rule(self): def add_rule(self):
r = Rule() r = Rule(self.device)
self.widgets.append(r) self.widgets.append(r)
self.w.l.addWidget(r) self.w.l.addWidget(r)
r.remove.connect(self.remove_rule) r.remove.connect(self.remove_rule)
@ -319,10 +359,10 @@ class MTPConfig(QTabWidget):
l = self.base.l = QGridLayout(self.base) l = self.base.l = QGridLayout(self.base)
self.base.setLayout(l) self.base.setLayout(l)
self.rules = r = FormatRules(self.get_pref('rules')) self.rules = r = FormatRules(self.device, self.get_pref('rules'))
self.formats = FormatsConfig(set(BOOK_EXTENSIONS), self.formats = FormatsConfig(set(BOOK_EXTENSIONS),
self.get_pref('format_map')) self.get_pref('format_map'))
self.send_to = SendToConfig(self.get_pref('send_to')) self.send_to = SendToConfig(self.get_pref('send_to'), self.device)
self.template = TemplateConfig(self.get_pref('send_template')) self.template = TemplateConfig(self.get_pref('send_template'))
self.base.la = la = QLabel(_( self.base.la = la = QLabel(_(
'Choose the formats to send to the %s')%self.device.current_friendly_name) 'Choose the formats to send to the %s')%self.device.current_friendly_name)

View File

@ -0,0 +1,119 @@
#!/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__ = '2012, Kovid Goyal <kovid at kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
from operator import attrgetter
from PyQt4.Qt import (QTabWidget, QTreeWidget, QTreeWidgetItem, Qt, QDialog,
QDialogButtonBox, QVBoxLayout, QSize, pyqtSignal, QIcon)
from calibre.gui2 import file_icon_provider
def item(f, parent):
name = f.name
if not f.is_folder:
name += ' [%s]'%f.last_mod_string
ans = QTreeWidgetItem(parent, [name])
ans.setData(0, Qt.UserRole, f.full_path)
if f.is_folder:
ext = 'dir'
else:
ext = f.name.rpartition('.')[-1]
ans.setData(0, Qt.DecorationRole, file_icon_provider().icon_from_ext(ext))
return ans
class Storage(QTreeWidget):
def __init__(self, storage, show_files):
QTreeWidget.__init__(self)
self.show_files = show_files
self.create_children(storage, self)
self.name = storage.name
self.object_id = storage.persistent_id
self.setMinimumHeight(350)
self.setHeaderHidden(True)
def create_children(self, f, parent):
for child in sorted(f.folders, key=attrgetter('name')):
i = item(child, parent)
self.create_children(child, i)
if self.show_files:
for child in sorted(f.files, key=attrgetter('name')):
i = item(child, parent)
@property
def current_item(self):
item = self.currentItem()
if item is not None:
return (self.object_id, item.data(0, Qt.UserRole).toPyObject())
return None
class Folders(QTabWidget):
selected = pyqtSignal()
def __init__(self, filesystem_cache, show_files=True):
QTabWidget.__init__(self)
self.fs = filesystem_cache
for storage in self.fs.entries:
w = Storage(storage, show_files)
self.addTab(w, w.name)
w.doubleClicked.connect(self.selected)
self.setCurrentIndex(0)
@property
def current_item(self):
w = self.currentWidget()
if w is not None:
return w.current_item
class Browser(QDialog):
def __init__(self, filesystem_cache, show_files=True, parent=None):
QDialog.__init__(self, parent)
self.l = l = QVBoxLayout()
self.setLayout(l)
self.folders = cw = Folders(filesystem_cache, show_files=show_files)
l.addWidget(cw)
bb = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel)
l.addWidget(bb)
bb.accepted.connect(self.accept)
bb.rejected.connect(self.reject)
self.setMinimumSize(QSize(500, 500))
self.folders.selected.connect(self.accept)
self.setWindowTitle(_('Choose folder on device'))
self.setWindowIcon(QIcon(I('devices/galaxy_s3.png')))
@property
def current_item(self):
return self.folders.current_item
def browse():
from calibre.gui2 import Application
from calibre.devices.mtp.driver import MTP_DEVICE
from calibre.devices.scanner import DeviceScanner
s = DeviceScanner()
s.scan()
app = Application([])
app
dev = MTP_DEVICE(None)
dev.startup()
cd = dev.detect_managed_devices(s.devices)
if cd is None:
raise ValueError('No MTP device found')
dev.open(cd, 'test')
d = Browser(dev.filesystem_cache)
d.exec_()
dev.shutdown()
return d.current_item
if __name__ == '__main__':
print (browse())

View File

@ -76,6 +76,9 @@ def config(defaults=None):
return c return c
def load_themes():
return JSONConfig('viewer_themes')
class ConfigDialog(QDialog, Ui_Dialog): class ConfigDialog(QDialog, Ui_Dialog):
def __init__(self, shortcuts, parent=None): def __init__(self, shortcuts, parent=None):
@ -113,7 +116,7 @@ class ConfigDialog(QDialog, Ui_Dialog):
self.hyphenate_default_lang.setVisible(False) self.hyphenate_default_lang.setVisible(False)
self.hyphenate_label.setVisible(False) self.hyphenate_label.setVisible(False)
self.themes = JSONConfig('viewer_themes') self.themes = load_themes()
self.save_theme_button.clicked.connect(self.save_theme) self.save_theme_button.clicked.connect(self.save_theme)
self.load_theme_button.m = m = QMenu() self.load_theme_button.m = m = QMenu()
self.load_theme_button.setMenu(m) self.load_theme_button.setMenu(m)

View File

@ -21,7 +21,7 @@ from calibre.customize.ui import all_viewer_plugins
from calibre.gui2.viewer.keys import SHORTCUTS from calibre.gui2.viewer.keys import SHORTCUTS
from calibre.gui2.viewer.javascript import JavaScriptLoader from calibre.gui2.viewer.javascript import JavaScriptLoader
from calibre.gui2.viewer.position import PagePosition from calibre.gui2.viewer.position import PagePosition
from calibre.gui2.viewer.config import config, ConfigDialog from calibre.gui2.viewer.config import config, ConfigDialog, load_themes
from calibre.gui2.viewer.image_popup import ImagePopup from calibre.gui2.viewer.image_popup import ImagePopup
from calibre.ebooks.oeb.display.webview import load_html from calibre.ebooks.oeb.display.webview import load_html
from calibre.constants import isxp, iswindows from calibre.constants import isxp, iswindows
@ -31,8 +31,7 @@ class Document(QWebPage): # {{{
page_turn = pyqtSignal(object) page_turn = pyqtSignal(object)
def set_font_settings(self): def set_font_settings(self, opts):
opts = config().parse()
settings = self.settings() settings = self.settings()
settings.setFontSize(QWebSettings.DefaultFontSize, opts.default_font_size) settings.setFontSize(QWebSettings.DefaultFontSize, opts.default_font_size)
settings.setFontSize(QWebSettings.DefaultFixedFontSize, opts.mono_font_size) settings.setFontSize(QWebSettings.DefaultFixedFontSize, opts.mono_font_size)
@ -47,11 +46,15 @@ class Document(QWebPage): # {{{
def do_config(self, parent=None): def do_config(self, parent=None):
d = ConfigDialog(self.shortcuts, parent) d = ConfigDialog(self.shortcuts, parent)
if d.exec_() == QDialog.Accepted: if d.exec_() == QDialog.Accepted:
with self.page_position: opts = config().parse()
self.set_font_settings() self.apply_settings(opts)
self.set_user_stylesheet()
self.misc_config() def apply_settings(self, opts):
self.after_load() with self.page_position:
self.set_font_settings(opts)
self.set_user_stylesheet(opts)
self.misc_config(opts)
self.after_load()
def __init__(self, shortcuts, parent=None, debug_javascript=False): def __init__(self, shortcuts, parent=None, debug_javascript=False):
QWebPage.__init__(self, parent) QWebPage.__init__(self, parent)
@ -87,7 +90,8 @@ class Document(QWebPage): # {{{
self.all_viewer_plugins = tuple(all_viewer_plugins()) self.all_viewer_plugins = tuple(all_viewer_plugins())
for pl in self.all_viewer_plugins: for pl in self.all_viewer_plugins:
pl.load_fonts() pl.load_fonts()
self.set_font_settings() opts = config().parse()
self.set_font_settings(opts)
# Security # Security
settings.setAttribute(QWebSettings.JavaEnabled, False) settings.setAttribute(QWebSettings.JavaEnabled, False)
@ -98,8 +102,8 @@ class Document(QWebPage): # {{{
# Miscellaneous # Miscellaneous
settings.setAttribute(QWebSettings.LinksIncludedInFocusChain, True) settings.setAttribute(QWebSettings.LinksIncludedInFocusChain, True)
settings.setAttribute(QWebSettings.DeveloperExtrasEnabled, True) settings.setAttribute(QWebSettings.DeveloperExtrasEnabled, True)
self.set_user_stylesheet() self.set_user_stylesheet(opts)
self.misc_config() self.misc_config(opts)
# Load javascript # Load javascript
self.mainFrame().javaScriptWindowObjectCleared.connect( self.mainFrame().javaScriptWindowObjectCleared.connect(
@ -112,8 +116,7 @@ class Document(QWebPage): # {{{
mf.setScrollBarPolicy(Qt.Vertical, Qt.ScrollBarAlwaysOff) mf.setScrollBarPolicy(Qt.Vertical, Qt.ScrollBarAlwaysOff)
mf.setScrollBarPolicy(Qt.Horizontal, Qt.ScrollBarAlwaysOff) mf.setScrollBarPolicy(Qt.Horizontal, Qt.ScrollBarAlwaysOff)
def set_user_stylesheet(self): def set_user_stylesheet(self, opts):
opts = config().parse()
bg = opts.background_color or 'white' bg = opts.background_color or 'white'
brules = ['background-color: %s !important'%bg] brules = ['background-color: %s !important'%bg]
prefix = ''' prefix = '''
@ -127,8 +130,7 @@ class Document(QWebPage): # {{{
data += b64encode(raw.encode('utf-8')) data += b64encode(raw.encode('utf-8'))
self.settings().setUserStyleSheetUrl(QUrl(data)) self.settings().setUserStyleSheetUrl(QUrl(data))
def misc_config(self): def misc_config(self, opts):
opts = config().parse()
self.hyphenate = opts.hyphenate self.hyphenate = opts.hyphenate
self.hyphenate_default_lang = opts.hyphenate_default_lang self.hyphenate_default_lang = opts.hyphenate_default_lang
self.do_fit_images = opts.fit_images self.do_fit_images = opts.fit_images
@ -560,6 +562,15 @@ class DocumentView(QWebView): # {{{
self.document.switch_to_fullscreen_mode() self.document.switch_to_fullscreen_mode()
self.setFocus(Qt.OtherFocusReason) self.setFocus(Qt.OtherFocusReason)
def load_theme(self, theme_id):
themes = load_themes()
theme = themes[theme_id]
opts = config(theme).parse()
self.document.apply_settings(opts)
if self.document.in_fullscreen_mode:
self.document.switch_to_fullscreen_mode()
self.setFocus(Qt.OtherFocusReason)
def bookmark(self): def bookmark(self):
return self.document.bookmark() return self.document.bookmark()

View File

@ -245,8 +245,7 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
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_bookmark.triggered[bool].connect(self.bookmark) self.action_bookmark.triggered[bool].connect(self.bookmark)
self.action_preferences.triggered.connect(lambda : self.action_preferences.triggered.connect(self.do_config)
self.view.config(self))
self.pos.editingFinished.connect(self.goto_page_num) self.pos.editingFinished.connect(self.goto_page_num)
self.vertical_scrollbar.valueChanged[int].connect(lambda self.vertical_scrollbar.valueChanged[int].connect(lambda
x:self.goto_page(x/100.)) x:self.goto_page(x/100.))
@ -259,6 +258,10 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
self.action_bookmark.setMenu(self.bookmarks_menu) 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()
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)
@ -845,6 +848,21 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
getattr(self, o).setEnabled(False) getattr(self, o).setEnabled(False)
self.setCursor(Qt.BusyCursor) self.setCursor(Qt.BusyCursor)
def load_theme_menu(self):
from calibre.gui2.viewer.config import load_themes
self.themes_menu.clear()
for key in load_themes():
title = key[len('theme_'):]
self.themes_menu.addAction(title, partial(self.load_theme,
key))
def load_theme(self, theme_id):
self.view.load_theme(theme_id)
def do_config(self):
self.view.config(self)
self.load_theme_menu()
def bookmark(self, *args): def bookmark(self, *args):
num = 1 num = 1
bm = None bm = None

View File

@ -7,7 +7,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>653</width> <width>653</width>
<height>672</height> <height>746</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@ -141,6 +141,7 @@
<addaction name="separator"/> <addaction name="separator"/>
<addaction name="action_preferences"/> <addaction name="action_preferences"/>
<addaction name="action_metadata"/> <addaction name="action_metadata"/>
<addaction name="action_load_theme"/>
<addaction name="separator"/> <addaction name="separator"/>
<addaction name="action_print"/> <addaction name="action_print"/>
</widget> </widget>
@ -332,6 +333,18 @@
<string>Toggle Paged mode</string> <string>Toggle Paged mode</string>
</property> </property>
</action> </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> </widget>
<customwidgets> <customwidgets>
<customwidget> <customwidget>