Merge from trunk

This commit is contained in:
Charles Haley 2012-07-13 15:45:23 +02:00
commit 37ddf7d075
120 changed files with 37002 additions and 34089 deletions

View File

@ -19,6 +19,74 @@
# new recipes: # new recipes:
# - title: # - title:
- version: 0.8.60
date: 2012-07-13
new features:
- title: "When searching, allow use of un-accented characters to match accented characters in all fields and all languages (not just authors and English as before)"
description: "The rules for matching un-accented characters are done in a language dependent way. So if your calibre interface language is set to English, n will match both n and ñ, but if it is set to Spanish, it will match only n, as in Spanish ñ is a separate alphabet in Spanish. This makes searching a little slower, so if you have a very large library you can turn it off via Preferences->Searching."
type: major
- title: "Content server: Show a best guess for the IP address the content server is currently listening at in the connect/share menu."
tickets: [1024128]
- title: "E-book viewer: Add an option to show a clock in full screen mode."
tickets: [1022086]
- title: "Drivers for Paquito Imaginarium and a few Android phones"
tickets: [1024021,1023613,1023461,1022401]
- title: "HTMLZ Output: Add option to use the book title as the filename for the html file inside the archive"
- title: "Make the list of displayed fields in the book details panel a per library setting"
- title: "Have autocomplete on authors/series/tags/etc. ignore accented characters when finding matches (similar to the changes to search above)"
- title: "Support for retina displays in OS X (I hope)"
tickets: [1022191]
- title: "Remove the dependency on the zip command line tool when developing plugins"
bug fixes:
- title: "Kobo driver: Do not perform write operations on the Kobo database if its version is newer than the latest version the driver supports, for safety"
- title: "KF8 Input: Ignore encoding declarations inside the html markup, as they are sometimes incorrect."
tickets: [1022933]
- title: "Force refresh of cached composite column values when values in the cache are changed"
- title: "Fix a regression that broke calibre --shutdown-running-calibre on windows."
tickets: [1022504]
- title: "Possible workaround for Qt 4.8.2 open file dialog failing on some linux distros."
tickets: [1022019]
- title: "Catalogs: Fix some epubcheck errors when generating catalogs in EPUB format"
- title: "Linux installer: When calling the xdg utilities use system libraries rather than the libraries bundled with calibre"
- title: "Fix numeric sort for composite custom columns that use custom separators"
tickets: [1021814]
- title: "Tag browser: When grouping by first letter, handle languages that have 'letters' made of more than one character. This can be turned off via Preferences->Tweaks"
improved recipes:
- Hola magazine
- Adventure Gamers
- Cosmopolitan UK
- Onda Rock
new recipes:
- title: Empire Magazine
author: Dave Asbury
- title: NZZ Folio
author: Bernd Leinfelder
- title: Warentest
author: asdfdsfksd
- version: 0.8.59 - version: 0.8.59
date: 2012-07-06 date: 2012-07-06

View File

@ -198,7 +198,7 @@ You can insert print statements anywhere in your plugin code, they will be outpu
You can quickly test changes to your plugin by using the following command You can quickly test changes to your plugin by using the following command
line:: line::
calibre -s; calibre-customize -b /path/to/your/plugin/directory; calibre calibre-debug -s; calibre-customize -b /path/to/your/plugin/directory; calibre
This will shutdown a running calibre, wait for the shutdown to complete, then update your plugin in |app| and relaunch |app|. This will shutdown a running calibre, wait for the shutdown to complete, then update your plugin in |app| and relaunch |app|.

View File

@ -33,6 +33,7 @@ class FazNet(BasicNewsRecipe):
('Technik & Motor', 'http://www.faz.net/aktuell/technik-motor/?rssview=1'), ('Technik & Motor', 'http://www.faz.net/aktuell/technik-motor/?rssview=1'),
('Wissen', 'http://www.faz.net/aktuell/wissen/?rssview=1'), ('Wissen', 'http://www.faz.net/aktuell/wissen/?rssview=1'),
('Reise', 'http://www.faz.net/aktuell/reise/?rssview=1'), ('Reise', 'http://www.faz.net/aktuell/reise/?rssview=1'),
('Beruf & Chance', 'http://www.faz.net/aktuell/beruf-chance/?rssview=1') ('Beruf & Chance', 'http://www.faz.net/aktuell/beruf-chance/?rssview=1'),
('Rhein-Main', 'http://www.faz.net/aktuell/rhein-main/?rssview=1') # AGe add 2012-07-13
] ]

View File

@ -1,38 +1,73 @@
#!/usr/bin/env python #!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
__license__ = 'GPL v3' __license__ = 'GPL v3'
__copyright__ = '2010, Brendan Sleight <bms.calibre at barwap.com>' __copyright__ = '30 June 2012, desUBIKado'
__author__ = 'desUBIKado'
__description__ = 'Diario de actualidad, moda y belleza'
__version__ = 'v0.01'
__date__ = '30, June 2012'
''' '''
hola.com http://www.hola.com/
''' '''
from calibre.web.feeds.news import BasicNewsRecipe from calibre.web.feeds.news import BasicNewsRecipe
class Hackaday(BasicNewsRecipe): class hola_es(BasicNewsRecipe):
title = u'Hola' __author__ = 'desUBIKado'
__author__ = 'bmsleight' description = 'Diario de actualidad, moda y belleza'
description = 'diario de actualidad, moda y belleza.' title = u'¡Hola!'
oldest_article = 10 publisher = 'Hola S.L.'
max_articles_per_feed = 100 category = 'Spanish celebrities, Entertainment News, Royalty, Daily Variety, Hollywood'
no_stylesheets = True
language = 'es' language = 'es'
masthead_url = 'http://imagenes.hola.com/comunes/2008/logo-holacom.gif'
timefmt = '[%a, %d %b, %Y]'
oldest_article = 7
delay = 1
encoding = 'utf-8'
max_articles_per_feed = 100
use_embedded_content = False use_embedded_content = False
remove_empty_feeds = True
keep_only_tags = [ remove_javascript = True
dict(name='div', attrs={'id':'cuerpo'}) no_stylesheets = True
]
feeds = [ feeds = [
(u'Famosos' , u'http://www.hola.com/famosos/rss.xml' ), (u'Famosos' , u'http://www.hola.com/famosos/rss.xml' )
(u'Realeza' , u'http://www.hola.com/realeza/rss.xml' ), ,(u'Realeza' , u'http://www.hola.com/realeza/rss.xml' )
(u'Cine' , u'http://www.hola.com/cine/rss.xml' ), ,(u'Cine' , u'http://www.hola.com/cine/rss.xml' )
(u'Música' , u'http://www.hola.com/musica/rss.xml' ), ,(u'M\xfasica' , u'http://www.hola.com/musica/rss.xml' )
(u'Moda y modelos' , u'http://www.hola.com/moda/portada/rss.xml' ), ,(u'Moda y modelos' , u'http://www.hola.com/moda/portada/rss.xml' )
(u'Belleza y salud', u'http://www.hola.com/belleza/portada/rss.xml' ), ,(u'Belleza y salud', u'http://www.hola.com/belleza/portada/rss.xml' )
(u'Niños' , u'http://www.hola.com/ninos/rss.xml' ), ,(u'Ni\xf1os' , u'http://www.hola.com/ninos/rss.xml' )
(u'Todas las noticias', u'http://int2.hola.com/app/feeds/rss_hola.php'),
] ]
keep_only_tags = [dict(name='div', attrs={'id':['cuerpo','com']})]
remove_tags = [dict(name='div', attrs={'id':['relacionadas','slide-enlaces-patrocinados','comentarios']}),
dict(name='div', attrs={'class':['slide-enlaces-patricinados-tit','compartir']})
]
remove_tags_after = dict(name='div' , attrs={'id':'comentarios'})
# Recuperamos la portada de papel (la imagen 520 tiene mayor resolucion)
def get_cover_url(self):
index = 'http://www.hola.com/abono/ediciondigital/'
soup = self.index_to_soup(index)
for image in soup.findAll('img',src=True):
if image['src'].endswith('portada-revista-hola-520.jpg'):
return 'http://www.hola.com/' + image['src']
return None
def get_article_url(self, article): def get_article_url(self, article):
url = article.get('guid', None) url = article.get('guid', None)
return url return url
extra_css = '''
h1{font-family:Arial,Helvetica,sans-serif; font-weight:bold;font-size:30px;}
h2{font-family:Arial,Helvetica,sans-serif; font-weight:normal; font-style:italic; font-size:18px;}
'''

View File

@ -8,7 +8,7 @@ class NatGeoMag(BasicNewsRecipe):
oldest_article = 31 oldest_article = 31
max_articles_per_feed = 50 max_articles_per_feed = 50
category = 'geography, magazine' category = 'geography, magazine'
language = 'en_US' language = 'en'
publication_type = 'magazine' publication_type = 'magazine'
cover_url = 'http://www.yourlogoresources.com/wp-content/uploads/2011/09/national-geographic-logo.jpg' cover_url = 'http://www.yourlogoresources.com/wp-content/uploads/2011/09/national-geographic-logo.jpg'
use_embedded_content = False use_embedded_content = False

View File

@ -522,6 +522,6 @@ default_tweak_format = None
# consideration when partitioning by first letter. # consideration when partitioning by first letter.
# Examples: # Examples:
# enable_multicharacters_in_tag_browser = True # enable_multicharacters_in_tag_browser = True
# enable_multicharacters_in_tag_browser = True # enable_multicharacters_in_tag_browser = False
enable_multicharacters_in_tag_browser = True enable_multicharacters_in_tag_browser = True

View File

@ -12,7 +12,7 @@ import sys, os, shutil, platform, subprocess, stat, py_compile, glob, \
from setup import Command, modules, basenames, functions, __version__, \ from setup import Command, modules, basenames, functions, __version__, \
__appname__ __appname__
SITE_PACKAGES = ['IPython', 'PIL', 'dateutil', 'dns', 'PyQt4', 'mechanize', SITE_PACKAGES = ['PIL', 'dateutil', 'dns', 'PyQt4', 'mechanize',
'sip.so', 'BeautifulSoup.py', 'cssutils', 'encutils', 'lxml', 'sip.so', 'BeautifulSoup.py', 'cssutils', 'encutils', 'lxml',
'sipconfig.py', 'xdg', 'dbus', '_dbus_bindings.so', 'dbus_bindings.py', 'sipconfig.py', 'xdg', 'dbus', '_dbus_bindings.so', 'dbus_bindings.py',
'_dbus_glib_bindings.so'] '_dbus_glib_bindings.so']

View File

@ -386,7 +386,7 @@ class Py2App(object):
@flush @flush
def add_poppler(self): def add_poppler(self):
info('\nAdding poppler') info('\nAdding poppler')
for x in ('libpoppler.25.dylib',): for x in ('libpoppler.26.dylib',):
self.install_dylib(os.path.join(SW, 'lib', x)) self.install_dylib(os.path.join(SW, 'lib', x))
for x in ('pdftohtml', 'pdftoppm', 'pdfinfo'): for x in ('pdftohtml', 'pdftoppm', 'pdfinfo'):
self.install_dylib(os.path.join(SW, 'bin', x), False) self.install_dylib(os.path.join(SW, 'bin', x), False)
@ -483,10 +483,6 @@ class Py2App(object):
shutil.rmtree(tdir) shutil.rmtree(tdir)
shutil.rmtree(os.path.join(self.site_packages, 'calibre', 'plugins')) shutil.rmtree(os.path.join(self.site_packages, 'calibre', 'plugins'))
self.remove_bytecode(join(self.resources_dir, 'Python', 'site-packages')) self.remove_bytecode(join(self.resources_dir, 'Python', 'site-packages'))
# Create dummy IPython README_STARTUP
with open(join(self.site_packages,
'IPython/config/profile/README_STARTUP'), 'wb') as f:
f.write('\n')
@flush @flush
def add_modules_from_dir(self, src): def add_modules_from_dir(self, src):

View File

@ -23,6 +23,7 @@ SW = r'C:\cygwin\home\kovid\sw'
IMAGEMAGICK = os.path.join(SW, 'build', 'ImageMagick-6.7.6', IMAGEMAGICK = os.path.join(SW, 'build', 'ImageMagick-6.7.6',
'VisualMagick', 'bin') 'VisualMagick', 'bin')
CRT = r'C:\Microsoft.VC90.CRT' CRT = r'C:\Microsoft.VC90.CRT'
SIGNTOOL = r'C:\Program Files\Microsoft SDKs\Windows\v6.0A\bin\signtool.exe'
VERSION = re.sub('[a-z]\d+', '', __version__) VERSION = re.sub('[a-z]\d+', '', __version__)
WINVER = VERSION+'.0' WINVER = VERSION+'.0'

View File

@ -30,7 +30,7 @@ If there are no windows binaries already compiled for the version of python you
Run the following command to install python dependencies:: Run the following command to install python dependencies::
easy_install --always-unzip -U ipython mechanize pyreadline python-dateutil dnspython cssutils clientform pycrypto easy_install --always-unzip -U mechanize pyreadline python-dateutil dnspython cssutils clientform pycrypto
Install BeautifulSoup 3.0.x manually into site-packages (3.1.x parses broken HTML very poorly) Install BeautifulSoup 3.0.x manually into site-packages (3.1.x parses broken HTML very poorly)

View File

@ -4,7 +4,7 @@ __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net' __copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
__appname__ = u'calibre' __appname__ = u'calibre'
numeric_version = (0, 8, 59) numeric_version = (0, 8, 60)
__version__ = u'.'.join(map(unicode, numeric_version)) __version__ = u'.'.join(map(unicode, numeric_version))
__author__ = u"Kovid Goyal <kovid@kovidgoyal.net>" __author__ = u"Kovid Goyal <kovid@kovidgoyal.net>"

View File

@ -81,6 +81,9 @@ class DBPrefs(dict): # {{{
def to_raw(self, val): def to_raw(self, val):
return json.dumps(val, indent=2, default=to_json) return json.dumps(val, indent=2, default=to_json)
def has_setting(self, key):
return key in self
def __getitem__(self, key): def __getitem__(self, key):
try: try:
return dict.__getitem__(self, key) return dict.__getitem__(self, key)
@ -102,6 +105,53 @@ class DBPrefs(dict): # {{{
def set(self, key, val): def set(self, key, val):
self.__setitem__(key, val) self.__setitem__(key, val)
def get_namespaced(self, namespace, key, default=None):
key = u'namespaced:%s:%s'%(namespace, key)
try:
return dict.__getitem__(self, key)
except KeyError:
return default
def set_namespaced(self, namespace, key, val):
if u':' in key: raise KeyError('Colons are not allowed in keys')
if u':' in namespace: raise KeyError('Colons are not allowed in'
' the namespace')
key = u'namespaced:%s:%s'%(namespace, key)
self[key] = val
def write_serialized(self, library_path):
try:
to_filename = os.path.join(library_path, 'metadata_db_prefs_backup.json')
with open(to_filename, "wb") as f:
f.write(json.dumps(self, indent=2, default=to_json))
except:
import traceback
traceback.print_exc()
@classmethod
def read_serialized(cls, library_path, recreate_prefs=False):
try:
from_filename = os.path.join(library_path,
'metadata_db_prefs_backup.json')
with open(from_filename, "rb") as f:
d = json.load(f, object_hook=from_json)
if not recreate_prefs:
return d
cls.clear()
cls.db.conn.execute('DELETE FROM preferences')
for k,v in d.iteritems():
raw = cls.to_raw(v)
cls.db.conn.execute(
'INSERT INTO preferences (key,val) VALUES (?,?)', (k, raw))
cls.db.conn.commit()
cls.clear()
cls.update(d)
return d
except:
import traceback
traceback.print_exc()
raise
return None
# }}} # }}}
# Extra collators {{{ # Extra collators {{{
@ -350,6 +400,23 @@ class DB(object):
defs['gui_restriction'] = defs['cs_restriction'] = '' defs['gui_restriction'] = defs['cs_restriction'] = ''
defs['categories_using_hierarchy'] = [] defs['categories_using_hierarchy'] = []
defs['column_color_rules'] = [] defs['column_color_rules'] = []
defs['grouped_search_make_user_categories'] = []
defs['similar_authors_search_key'] = 'authors'
defs['similar_authors_match_kind'] = 'match_any'
defs['similar_publisher_search_key'] = 'publisher'
defs['similar_publisher_match_kind'] = 'match_any'
defs['similar_tags_search_key'] = 'tags'
defs['similar_tags_match_kind'] = 'match_all'
defs['similar_series_search_key'] = 'series'
defs['similar_series_match_kind'] = 'match_any'
defs['book_display_fields'] = [
('title', False), ('authors', True), ('formats', True),
('series', True), ('identifiers', True), ('tags', True),
('path', True), ('publisher', False), ('rating', False),
('author_sort', False), ('sort', False), ('timestamp', False),
('uuid', False), ('comments', True), ('id', False), ('pubdate', False),
('last_modified', False), ('size', False), ('languages', False),
]
# Migrate the bool tristate tweak # Migrate the bool tristate tweak
defs['bools_are_tristate'] = \ defs['bools_are_tristate'] = \

View File

@ -60,6 +60,11 @@ Run an embedded python interpreter.
'editing tools, and then rebuilds the file from the edited HTML. ' 'editing tools, and then rebuilds the file from the edited HTML. '
'Makes no additional changes to the HTML, unlike a full calibre ' 'Makes no additional changes to the HTML, unlike a full calibre '
'conversion).') 'conversion).')
parser.add_option('-s', '--shutdown-running-calibre', default=False,
action='store_true',
help=_('Cause a running calibre instance, if any, to be'
' shutdown. Note that if there are running jobs, they '
'will be silently aborted, so use with care.'))
parser.add_option('--test-build', help='Test binary modules in build', parser.add_option('--test-build', help='Test binary modules in build',
action='store_true', default=False) action='store_true', default=False)
@ -258,6 +263,9 @@ def main(args=sys.argv):
elif opts.test_build: elif opts.test_build:
from calibre.test_build import test from calibre.test_build import test
test() test()
elif opts.shutdown_running_calibre:
from calibre.gui2.main import shutdown_other
shutdown_other()
else: else:
from calibre import ipython from calibre import ipython
ipython() ipython()

View File

@ -41,6 +41,7 @@ class ANDROID(USBMS):
0xca9 : HTC_BCDS, 0xca9 : HTC_BCDS,
0xcac : HTC_BCDS, 0xcac : HTC_BCDS,
0xccf : HTC_BCDS, 0xccf : HTC_BCDS,
0xce5 : HTC_BCDS,
0x2910 : HTC_BCDS, 0x2910 : HTC_BCDS,
0xff9 : HTC_BCDS + [0x9999], 0xff9 : HTC_BCDS + [0x9999],
}, },
@ -210,7 +211,7 @@ class ANDROID(USBMS):
'XT910', 'BOOK_A10', 'USB_2.0_DRIVER', 'I9100T', 'P999DW', 'XT910', 'BOOK_A10', 'USB_2.0_DRIVER', 'I9100T', 'P999DW',
'KTABLET_PC', 'INGENIC', 'GT-I9001_CARD', 'USB_2.0_DRIVER', 'KTABLET_PC', 'INGENIC', 'GT-I9001_CARD', 'USB_2.0_DRIVER',
'GT-S5830L_CARD', 'UNIVERSE', 'XT875', 'PRO', '.KOBO_VOX', 'GT-S5830L_CARD', 'UNIVERSE', 'XT875', 'PRO', '.KOBO_VOX',
'THINKPAD_TABLET', 'SGH-T989'] 'THINKPAD_TABLET', 'SGH-T989', 'YP-G70']
WINDOWS_CARD_A_MEM = ['ANDROID_PHONE', 'GT-I9000_CARD', 'SGH-I897', WINDOWS_CARD_A_MEM = ['ANDROID_PHONE', 'GT-I9000_CARD', 'SGH-I897',
'FILE-STOR_GADGET', 'SGH-T959_CARD', 'SGH-T959', 'SAMSUNG_ANDROID', 'GT-P1000_CARD', 'FILE-STOR_GADGET', 'SGH-T959_CARD', 'SGH-T959', 'SAMSUNG_ANDROID', 'GT-P1000_CARD',
'A70S', 'A101IT', '7', 'INCREDIBLE', 'A7EB', 'SGH-T849_CARD', 'A70S', 'A101IT', '7', 'INCREDIBLE', 'A7EB', 'SGH-T849_CARD',

View File

@ -606,11 +606,11 @@ class KOBO(USBMS):
self.report_progress(1.0, _('Removing books from device...')) self.report_progress(1.0, _('Removing books from device...'))
from calibre.devices.errors import UserFeedback from calibre.devices.errors import UserFeedback
raise UserFeedback(_("Kobo database version unsupported - See details"), raise UserFeedback(_("Kobo database version unsupported - See details"),
_('Your Kobo is running an updated firmware/database version ' _('Your Kobo is running an updated firmware/database version. '
'As Calibre has not been updated, database editing is disabled. ' 'As Calibre has not been updated, database editing is disabled. '
'You can enable support for your Kobo in plugin preferences. ' 'You can enable support for your Kobo in plugin preferences. '
'Doing so may require you to perform a factory reset. ' 'Doing so may require you to perform a factory reset. '
'before selecting the "Attempt to support newer firmware" option ' 'Before selecting the "Attempt to support newer firmware" option '
'you should be familiar with restoring your Kobo to factory defaults.'), 'you should be familiar with restoring your Kobo to factory defaults.'),
UserFeedback.WARN) UserFeedback.WARN)

View File

@ -19,9 +19,9 @@ class TECLAST_K3(USBMS):
PRODUCT_ID = [0x3203] PRODUCT_ID = [0x3203]
BCD = [0x0000, 0x0100] BCD = [0x0000, 0x0100]
VENDOR_NAME = ['TECLAST', 'IMAGIN'] VENDOR_NAME = ['TECLAST', 'IMAGIN', 'RK28XX']
WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = ['DIGITAL_PLAYER', 'TL-K5', WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = ['DIGITAL_PLAYER', 'TL-K5',
'EREADER'] 'EREADER', 'USB-MSC']
MAIN_MEMORY_VOLUME_LABEL = 'K3 Main Memory' MAIN_MEMORY_VOLUME_LABEL = 'K3 Main Memory'
STORAGE_CARD_VOLUME_LABEL = 'K3 Storage Card' STORAGE_CARD_VOLUME_LABEL = 'K3 Storage Card'

View File

@ -127,12 +127,13 @@ class Device(DeviceConfig, DevicePlugin):
if not prefix: if not prefix:
return 0, 0 return 0, 0
prefix = prefix[:-1] prefix = prefix[:-1]
import win32file import win32file, winerror
try: try:
sectors_per_cluster, bytes_per_sector, free_clusters, total_clusters = \ sectors_per_cluster, bytes_per_sector, free_clusters, total_clusters = \
win32file.GetDiskFreeSpace(prefix) win32file.GetDiskFreeSpace(prefix)
except Exception as err: except Exception as err:
if getattr(err, 'args', [None])[0] == 21: # Disk not ready if getattr(err, 'args', [None])[0] == winerror.ERROR_NOT_READY:
# Disk not ready
time.sleep(3) time.sleep(3)
sectors_per_cluster, bytes_per_sector, free_clusters, total_clusters = \ sectors_per_cluster, bytes_per_sector, free_clusters, total_clusters = \
win32file.GetDiskFreeSpace(prefix) win32file.GetDiskFreeSpace(prefix)

View File

@ -10,10 +10,15 @@ __docformat__ = 'restructuredtext en'
import re, codecs import re, codecs
ENCODING_PATS = [ ENCODING_PATS = [
# XML declaration
re.compile(r'<\?[^<>]+encoding\s*=\s*[\'"](.*?)[\'"][^<>]*>', re.compile(r'<\?[^<>]+encoding\s*=\s*[\'"](.*?)[\'"][^<>]*>',
re.IGNORECASE), re.IGNORECASE),
# HTML 4 Pragma directive
re.compile(r'''<meta\s+?[^<>]*?content\s*=\s*['"][^'"]*?charset=([-_a-z0-9]+)[^'"]*?['"][^<>]*>''', re.compile(r'''<meta\s+?[^<>]*?content\s*=\s*['"][^'"]*?charset=([-_a-z0-9]+)[^'"]*?['"][^<>]*>''',
re.IGNORECASE), re.IGNORECASE),
# HTML 5 charset
re.compile(r'''<meta\s+charset=['"]([-_a-z0-9]+)['"][^<>]*>''',
re.IGNORECASE),
] ]
ENTITY_PATTERN = re.compile(r'&(\S+?);') ENTITY_PATTERN = re.compile(r'&(\S+?);')

View File

@ -37,6 +37,11 @@ class HTMLZOutput(OutputFormatPlugin):
'external: Use an external CSS file that is linked in the document.\n' 'external: Use an external CSS file that is linked in the document.\n'
'inline: Place the CSS in the head section of the document.' 'inline: Place the CSS in the head section of the document.'
)), )),
OptionRecommendation(name='htmlz_title_filename',
recommended_value=False, level=OptionRecommendation.LOW,
help=_('If set this option causes the file name of the html file'
' inside the htmlz archive to be based on the book title.')
),
]) ])
def convert(self, oeb_book, output_path, input_plugin, opts, log): def convert(self, oeb_book, output_path, input_plugin, opts, log):
@ -44,6 +49,7 @@ class HTMLZOutput(OutputFormatPlugin):
from calibre.ebooks.oeb.base import OEB_IMAGES, SVG_MIME from calibre.ebooks.oeb.base import OEB_IMAGES, SVG_MIME
from calibre.ebooks.metadata.opf2 import OPF, metadata_to_opf from calibre.ebooks.metadata.opf2 import OPF, metadata_to_opf
from calibre.utils.zipfile import ZipFile from calibre.utils.zipfile import ZipFile
from calibre.utils.filenames import ascii_filename
# HTML # HTML
if opts.htmlz_css_type == 'inline': if opts.htmlz_css_type == 'inline':
@ -59,7 +65,10 @@ class HTMLZOutput(OutputFormatPlugin):
htmlizer = OEB2HTMLizer(log) htmlizer = OEB2HTMLizer(log)
html = htmlizer.oeb2html(oeb_book, opts) html = htmlizer.oeb2html(oeb_book, opts)
with open(os.path.join(tdir, u'index.html'), 'wb') as tf: fname = u'index'
if opts.htmlz_title_filename:
fname = ascii_filename(unicode(oeb_book.metadata.title[0]))
with open(os.path.join(tdir, fname+u'.html'), 'wb') as tf:
tf.write(html) tf.write(html)
# CSS # CSS

View File

@ -9,6 +9,8 @@ __docformat__ = 'restructuredtext en'
import re, os import re, os
from calibre.ebooks.chardet import strip_encoding_declarations
def update_internal_links(mobi8_reader): def update_internal_links(mobi8_reader):
# need to update all links that are internal which # need to update all links that are internal which
# are based on positions within the xhtml files **BEFORE** # are based on positions within the xhtml files **BEFORE**
@ -324,6 +326,8 @@ def expand_mobi8_markup(mobi8_reader, resource_map, log):
for i, part in enumerate(parts): for i, part in enumerate(parts):
pi = mobi8_reader.partinfo[i] pi = mobi8_reader.partinfo[i]
with open(os.path.join(pi.type, pi.filename), 'wb') as f: with open(os.path.join(pi.type, pi.filename), 'wb') as f:
part = strip_encoding_declarations(part)
part = part.replace('<head>', '<head><meta charset="UTF-8"/>', 1)
f.write(part.encode('utf-8')) f.write(part.encode('utf-8'))
spine.append(f.name) spine.append(f.name)

View File

@ -89,14 +89,6 @@ gprefs.defaults['tags_browser_partition_method'] = 'first letter'
gprefs.defaults['tags_browser_collapse_at'] = 100 gprefs.defaults['tags_browser_collapse_at'] = 100
gprefs.defaults['tag_browser_dont_collapse'] = [] gprefs.defaults['tag_browser_dont_collapse'] = []
gprefs.defaults['edit_metadata_single_layout'] = 'default' gprefs.defaults['edit_metadata_single_layout'] = 'default'
gprefs.defaults['book_display_fields'] = [
('title', False), ('authors', True), ('formats', True),
('series', True), ('identifiers', True), ('tags', True),
('path', True), ('publisher', False), ('rating', False),
('author_sort', False), ('sort', False), ('timestamp', False),
('uuid', False), ('comments', True), ('id', False), ('pubdate', False),
('last_modified', False), ('size', False), ('languages', False),
]
gprefs.defaults['default_author_link'] = 'http://en.wikipedia.org/w/index.php?search={author}' gprefs.defaults['default_author_link'] = 'http://en.wikipedia.org/w/index.php?search={author}'
gprefs.defaults['preserve_date_on_ctl'] = True gprefs.defaults['preserve_date_on_ctl'] = True
gprefs.defaults['cb_fullscreen'] = False gprefs.defaults['cb_fullscreen'] = False

View File

@ -74,9 +74,10 @@ class ShareConnMenu(QMenu): # {{{
action=self.toggle_server_action, group=gr) action=self.toggle_server_action, group=gr)
def server_state_changed(self, running): def server_state_changed(self, running):
from calibre.utils.mdns import get_external_ip
text = _('Start Content Server') text = _('Start Content Server')
if running: if running:
text = _('Stop Content Server') text = _('Stop Content Server') + ' [%s]'%get_external_ip()
self.toggle_server_action.setText(text) self.toggle_server_action.setText(text)
def build_email_entries(self, sync_menu): def build_email_entries(self, sync_menu):

View File

@ -84,7 +84,17 @@ def render_html(mi, css, vertical, widget, all_fields=False): # {{{
return ans return ans
def get_field_list(fm, use_defaults=False): def get_field_list(fm, use_defaults=False):
src = gprefs.defaults if use_defaults else gprefs from calibre.gui2.ui import get_gui
db = get_gui().current_db
if use_defaults:
src = db.prefs.defaults
else:
old_val = gprefs.get('book_display_fields', None)
if old_val is not None and not db.prefs.has_setting(
'book_display_fields'):
src = gprefs
else:
src = db.prefs
fieldlist = list(src['book_display_fields']) fieldlist = list(src['book_display_fields'])
names = frozenset([x[0] for x in fieldlist]) names = frozenset([x[0] for x in fieldlist])
for field in fm.displayable_field_keys(): for field in fm.displayable_field_keys():

View File

@ -5,6 +5,10 @@ __license__ = 'GPL v3'
__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>' __copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
'''
WARNING: The code in this module is deprecated. Use complete2.py instead. This
code remains here for legacy plugin support.
'''
from PyQt4.Qt import (QLineEdit, QAbstractListModel, Qt, from PyQt4.Qt import (QLineEdit, QAbstractListModel, Qt,
QApplication, QCompleter) QApplication, QCompleter)

View File

@ -9,8 +9,9 @@ __docformat__ = 'restructuredtext en'
import weakref import weakref
import sip
from PyQt4.Qt import (QLineEdit, QAbstractListModel, Qt, pyqtSignal, QObject, from PyQt4.Qt import (QLineEdit, QAbstractListModel, Qt, pyqtSignal, QObject,
QApplication, QListView, QPoint) QApplication, QListView, QPoint, QModelIndex)
from calibre.utils.icu import sort_key, primary_startswith from calibre.utils.icu import sort_key, primary_startswith
from calibre.gui2 import NONE from calibre.gui2 import NONE
@ -82,9 +83,12 @@ class Completer(QListView): # {{{
self.entered.connect(self.item_entered) self.entered.connect(self.item_entered)
self.activated.connect(self.item_chosen) self.activated.connect(self.item_chosen)
self.pressed.connect(self.item_chosen) self.pressed.connect(self.item_chosen)
self.eat_focus_out = True
self.installEventFilter(self) self.installEventFilter(self)
def hide(self):
self.setCurrentIndex(QModelIndex())
QListView.hide(self)
def item_chosen(self, index): def item_chosen(self, index):
if not self.isVisible(): return if not self.isVisible(): return
self.hide() self.hide()
@ -109,7 +113,7 @@ class Completer(QListView): # {{{
if c.isValid(): if c.isValid():
r = c.row() r = c.row()
else: else:
r = 0 r = self.model().rowCount() if previous else -1
r = r + (-1 if previous else 1) r = r + (-1 if previous else 1)
index = self.model().index(r % self.model().rowCount()) index = self.model().index(r % self.model().rowCount())
self.setCurrentIndex(index) self.setCurrentIndex(index)
@ -120,7 +124,7 @@ class Completer(QListView): # {{{
if index is not None and index.isValid(): if index is not None and index.isValid():
self.setCurrentIndex(index) self.setCurrentIndex(index)
def popup(self): def popup(self, select_first=True):
p = self p = self
m = p.model() m = p.model()
widget = self.completer_widget() widget = self.completer_widget()
@ -153,7 +157,8 @@ class Completer(QListView): # {{{
p.setGeometry(pos.x(), pos.y(), w, h) p.setGeometry(pos.x(), pos.y(), w, h)
if (not self.currentIndex().isValid() and self.model().rowCount() > 0): if (select_first and not self.currentIndex().isValid() and
self.model().rowCount() > 0):
self.setCurrentIndex(self.model().index(0)) self.setCurrentIndex(self.model().index(0))
if not p.isVisible(): if not p.isVisible():
@ -162,12 +167,9 @@ class Completer(QListView): # {{{
def eventFilter(self, obj, e): def eventFilter(self, obj, e):
'Redirect key presses from the popup to the widget' 'Redirect key presses from the popup to the widget'
widget = self.completer_widget() widget = self.completer_widget()
if widget is None: if widget is None or sip.isdeleted(widget):
return False return False
etype = e.type() etype = e.type()
if self.eat_focus_out and widget is obj and etype == e.FocusOut:
if self.isVisible():
return True
if obj is not self: if obj is not self:
return QObject.eventFilter(self, obj, e) return QObject.eventFilter(self, obj, e)
@ -181,8 +183,14 @@ class Completer(QListView): # {{{
self.hide() self.hide()
e.accept() e.accept()
return True return True
if key in (Qt.Key_Enter, Qt.Key_Return):
if not self.currentIndex().isValid():
self.hide()
e.accept()
return True
return False
if key in (Qt.Key_End, Qt.Key_Home, Qt.Key_Up, Qt.Key_Down, if key in (Qt.Key_End, Qt.Key_Home, Qt.Key_Up, Qt.Key_Down,
Qt.Key_PageUp, Qt.Key_PageDown, Qt.Key_Enter, Qt.Key_Return): Qt.Key_PageUp, Qt.Key_PageDown):
# Let the list view handle these keys # Let the list view handle these keys
return False return False
if key in (Qt.Key_Tab, Qt.Key_Backtab): if key in (Qt.Key_Tab, Qt.Key_Backtab):
@ -190,9 +198,9 @@ class Completer(QListView): # {{{
e.accept() e.accept()
return True return True
# Send to widget # Send to widget
self.eat_focus_out = False widget.eat_focus_out = False
widget.keyPressEvent(e) widget.keyPressEvent(e)
self.eat_focus_out = True widget.eat_focus_out = True
if not widget.hasFocus(): if not widget.hasFocus():
# Widget lost focus hide the popup # Widget lost focus hide the popup
self.hide() self.hide()
@ -206,7 +214,6 @@ class Completer(QListView): # {{{
return False return False
# }}} # }}}
class LineEdit(QLineEdit, LineEditECM): class LineEdit(QLineEdit, LineEditECM):
''' '''
A line edit that completes on multiple items separated by a A line edit that completes on multiple items separated by a
@ -233,7 +240,6 @@ class LineEdit(QLineEdit, LineEditECM):
type=Qt.QueuedConnection) type=Qt.QueuedConnection)
self.mcompleter.relayout_needed.connect(self.relayout) self.mcompleter.relayout_needed.connect(self.relayout)
self.mcompleter.setFocusProxy(completer_widget) self.mcompleter.setFocusProxy(completer_widget)
completer_widget.installEventFilter(self.mcompleter)
self.textEdited.connect(self.text_edited) self.textEdited.connect(self.text_edited)
self.no_popup = False self.no_popup = False
@ -260,7 +266,7 @@ class LineEdit(QLineEdit, LineEditECM):
# }}} # }}}
def complete(self, show_all=False): def complete(self, show_all=False, select_first=True):
orig = None orig = None
if show_all: if show_all:
orig = self.mcompleter.model().current_prefix orig = self.mcompleter.model().current_prefix
@ -268,7 +274,7 @@ class LineEdit(QLineEdit, LineEditECM):
if not self.mcompleter.model().current_items: if not self.mcompleter.model().current_items:
self.mcompleter.hide() self.mcompleter.hide()
return return
self.mcompleter.popup() self.mcompleter.popup(select_first=select_first)
self.mcompleter.scroll_to(orig) self.mcompleter.scroll_to(orig)
def relayout(self): def relayout(self):
@ -277,7 +283,8 @@ class LineEdit(QLineEdit, LineEditECM):
def text_edited(self, *args): def text_edited(self, *args):
if self.no_popup: return if self.no_popup: return
self.update_completions() self.update_completions()
self.complete() select_first = len(self.mcompleter.model().current_prefix) > 0
self.complete(select_first=select_first)
def update_completions(self): def update_completions(self):
' Update the list of completions ' ' Update the list of completions '
@ -329,6 +336,8 @@ class EditWithComplete(EnComboBox):
EnComboBox.__init__(self, *args) EnComboBox.__init__(self, *args)
self.setLineEdit(LineEdit(self, completer_widget=self)) self.setLineEdit(LineEdit(self, completer_widget=self))
self.setCompleter(None) self.setCompleter(None)
self.eat_focus_out = True
self.installEventFilter(self)
# Interface {{{ # Interface {{{
def showPopup(self): def showPopup(self):
@ -377,6 +386,21 @@ class EditWithComplete(EnComboBox):
def textChanged(self): def textChanged(self):
return self.lineEdit().textChanged return self.lineEdit().textChanged
def clear(self):
self.lineEdit().clear()
EnComboBox.clear(self)
def eventFilter(self, obj, e):
try:
c = self.lineEdit().mcompleter
except AttributeError:
return False
etype = e.type()
if self.eat_focus_out and self is obj and etype == e.FocusOut:
if c.isVisible():
return True
return EnComboBox.eventFilter(self, obj, e)
if __name__ == '__main__': if __name__ == '__main__':
from PyQt4.Qt import QDialog, QVBoxLayout from PyQt4.Qt import QDialog, QVBoxLayout
app = QApplication([]) app = QApplication([])

View File

@ -17,7 +17,8 @@ class PluginWidget(Widget, Ui_Form):
ICON = I('mimetypes/html.png') ICON = I('mimetypes/html.png')
def __init__(self, parent, get_option, get_help, db=None, book_id=None): def __init__(self, parent, get_option, get_help, db=None, book_id=None):
Widget.__init__(self, parent, ['htmlz_css_type', 'htmlz_class_style']) Widget.__init__(self, parent, ['htmlz_css_type', 'htmlz_class_style',
'htmlz_title_filename'])
self.db, self.book_id = db, book_id self.db, self.book_id = db, book_id
for x in get_option('htmlz_css_type').option.choices: for x in get_option('htmlz_css_type').option.choices:
self.opt_htmlz_css_type.addItem(x) self.opt_htmlz_css_type.addItem(x)

View File

@ -14,7 +14,7 @@
<string>Form</string> <string>Form</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout"> <layout class="QGridLayout" name="gridLayout">
<item row="2" column="0"> <item row="3" column="0">
<spacer name="verticalSpacer"> <spacer name="verticalSpacer">
<property name="orientation"> <property name="orientation">
<enum>Qt::Vertical</enum> <enum>Qt::Vertical</enum>
@ -27,6 +27,9 @@
</property> </property>
</spacer> </spacer>
</item> </item>
<item row="1" column="1">
<widget class="QComboBox" name="opt_htmlz_class_style"/>
</item>
<item row="0" column="0"> <item row="0" column="0">
<widget class="QLabel" name="label"> <widget class="QLabel" name="label">
<property name="text"> <property name="text">
@ -51,8 +54,12 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="1"> <item row="2" column="0" colspan="2">
<widget class="QComboBox" name="opt_htmlz_class_style"/> <widget class="QCheckBox" name="opt_htmlz_title_filename">
<property name="text">
<string>Use book &amp;title as the filename for the HTML file inside the archive</string>
</property>
</widget>
</item> </item>
</layout> </layout>
</widget> </widget>

View File

@ -313,21 +313,39 @@ def cant_start(msg=_('If you are sure it is not running')+', ',
raise SystemExit(1) raise SystemExit(1)
def communicate(opts, args): def build_pipe(print_error=True):
t = RC() t = RC(print_error=print_error)
t.start() t.start()
time.sleep(3) t.join(3.0)
if not t.done: if t.is_alive():
if iswindows():
cant_start()
else:
f = os.path.expanduser('~/.calibre_calibre GUI.lock') f = os.path.expanduser('~/.calibre_calibre GUI.lock')
cant_start(what=_('try deleting the file')+': '+f) cant_start(what=_('try deleting the file')+': '+f)
raise SystemExit(1) raise SystemExit(1)
return t
if opts.shutdown_running_calibre: def shutdown_other(rc=None):
t.conn.send('shutdown:') if rc is None:
rc = build_pipe(print_error=False)
if rc.conn is None:
prints(_('No running calibre found'))
return # No running instance found
from calibre.utils.lock import singleinstance from calibre.utils.lock import singleinstance
rc.conn.send('shutdown:')
prints(_('Shutdown command sent, waiting for shutdown...')) prints(_('Shutdown command sent, waiting for shutdown...'))
while not singleinstance('calibre GUI'): for i in xrange(50):
if singleinstance('calibre GUI'):
return
time.sleep(0.1) time.sleep(0.1)
prints(_('Failed to shutdown running calibre instance'))
raise SystemExit(1)
def communicate(opts, args):
t = build_pipe()
if opts.shutdown_running_calibre:
shutdown_other(t)
else: else:
if len(args) > 1: if len(args) > 1:
args[1] = os.path.abspath(args[1]) args[1] = os.path.abspath(args[1])
@ -335,7 +353,6 @@ def communicate(opts, args):
t.conn.close() t.conn.close()
raise SystemExit(0) raise SystemExit(0)
def main(args=sys.argv): def main(args=sys.argv):
gui_debug = None gui_debug = None
if args[0] == '__CALIBRE_GUI_DEBUG__': if args[0] == '__CALIBRE_GUI_DEBUG__':

View File

@ -75,7 +75,7 @@ class DisplayedFields(QAbstractListModel): # {{{
def commit(self): def commit(self):
if self.changed: if self.changed:
gprefs['book_display_fields'] = self.fields self.db.prefs['book_display_fields'] = self.fields
def move(self, idx, delta): def move(self, idx, delta):
row = idx.row() + delta row = idx.row() + delta

View File

@ -184,7 +184,6 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
self.opt_grouped_search_make_user_categories.update_items_cache(terms) self.opt_grouped_search_make_user_categories.update_items_cache(terms)
self.gst_names.blockSignals(True) self.gst_names.blockSignals(True)
self.gst_names.clear() self.gst_names.clear()
print (1111, self.gst_names)
self.gst_names.addItem('', '') self.gst_names.addItem('', '')
for t in terms: for t in terms:
self.gst_names.addItem(t, t) self.gst_names.addItem(t, t)

View File

@ -6,7 +6,6 @@ __license__ = 'GPL 3'
__copyright__ = '2011, John Schember <john@nachtimwald.com>' __copyright__ = '2011, John Schember <john@nachtimwald.com>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
import random
import urllib import urllib
from contextlib import closing from contextlib import closing

View File

@ -11,7 +11,6 @@ import cPickle, os
from PyQt4.Qt import QDialog, QProgressDialog, QString, QTimer from PyQt4.Qt import QDialog, QProgressDialog, QString, QTimer
from calibre.constants import DEBUG
from calibre.ptempfile import PersistentTemporaryFile from calibre.ptempfile import PersistentTemporaryFile
from calibre.gui2 import warning_dialog, question_dialog from calibre.gui2 import warning_dialog, question_dialog
from calibre.gui2.convert.single import NoSupportedInputFormats from calibre.gui2.convert.single import NoSupportedInputFormats

View File

@ -51,6 +51,8 @@ def config(defaults=None):
help=_('The amount by which to change the font size when clicking' help=_('The amount by which to change the font size when clicking'
' the font larger/smaller buttons. Should be a number between ' ' the font larger/smaller buttons. Should be a number between '
'0 and 1.')) '0 and 1.'))
c.add_opt('fullscreen_clock', default=False, action='store_true',
help=_('Show a clock in fullscreen mode.'))
fonts = c.add_group('FONTS', _('Font options')) fonts = c.add_group('FONTS', _('Font options'))
fonts('serif_family', default='Times New Roman' if iswindows else 'Liberation Serif', fonts('serif_family', default='Times New Roman' if iswindows else 'Liberation Serif',
@ -117,6 +119,7 @@ class ConfigDialog(QDialog, Ui_Dialog):
self.hyphenate.setVisible(False) self.hyphenate.setVisible(False)
self.hyphenate_default_lang.setVisible(False) self.hyphenate_default_lang.setVisible(False)
self.hyphenate_label.setVisible(False) self.hyphenate_label.setVisible(False)
self.opt_fullscreen_clock.setChecked(opts.fullscreen_clock)
def accept(self, *args): def accept(self, *args):
if self.shortcut_config.is_editing: if self.shortcut_config.is_editing:
@ -148,6 +151,7 @@ class ConfigDialog(QDialog, Ui_Dialog):
str(self.hyphenate_default_lang.itemData(idx).toString())) str(self.hyphenate_default_lang.itemData(idx).toString()))
c.set('line_scrolling_stops_on_pagebreaks', c.set('line_scrolling_stops_on_pagebreaks',
self.opt_line_scrolling_stops_on_pagebreaks.isChecked()) self.opt_line_scrolling_stops_on_pagebreaks.isChecked())
c.set('fullscreen_clock', self.opt_fullscreen_clock.isChecked())
return QDialog.accept(self, *args) return QDialog.accept(self, *args)

View File

@ -6,7 +6,7 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>479</width> <width>839</width>
<height>630</height> <height>630</height>
</rect> </rect>
</property> </property>
@ -167,20 +167,6 @@
</item> </item>
<item> <item>
<layout class="QGridLayout" name="gridLayout_2"> <layout class="QGridLayout" name="gridLayout_2">
<item row="8" column="0" colspan="2">
<widget class="QCheckBox" name="opt_remember_window_size">
<property name="text">
<string>Remember last used &amp;window size and layout</string>
</property>
</widget>
</item>
<item row="9" column="0" colspan="2">
<widget class="QCheckBox" name="opt_remember_current_page">
<property name="text">
<string>Remember the &amp;current page when quitting</string>
</property>
</widget>
</item>
<item row="5" column="0" colspan="2"> <item row="5" column="0" colspan="2">
<widget class="QCheckBox" name="hyphenate"> <widget class="QCheckBox" name="hyphenate">
<property name="text"> <property name="text">
@ -205,13 +191,6 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="7" column="0" colspan="2">
<widget class="QCheckBox" name="opt_fit_images">
<property name="text">
<string>&amp;Resize images larger than the viewer window (needs restart)</string>
</property>
</widget>
</item>
<item row="4" column="0"> <item row="4" column="0">
<widget class="QLabel" name="label_11"> <widget class="QLabel" name="label_11">
<property name="text"> <property name="text">
@ -247,13 +226,6 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="10" column="0" colspan="2">
<widget class="QCheckBox" name="opt_wheel_flips_pages">
<property name="text">
<string>Mouse &amp;wheel flips pages</string>
</property>
</widget>
</item>
<item row="3" column="1"> <item row="3" column="1">
<widget class="QSpinBox" name="max_fs_width"> <widget class="QSpinBox" name="max_fs_width">
<property name="toolTip"> <property name="toolTip">
@ -301,13 +273,48 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="11" column="0" colspan="2"> <item row="7" column="0">
<widget class="QCheckBox" name="opt_fit_images">
<property name="text">
<string>&amp;Resize images larger than the viewer window (needs restart)</string>
</property>
</widget>
</item>
<item row="8" column="0">
<widget class="QCheckBox" name="opt_remember_window_size">
<property name="text">
<string>Remember last used &amp;window size and layout</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QCheckBox" name="opt_wheel_flips_pages">
<property name="text">
<string>Mouse &amp;wheel flips pages</string>
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="QCheckBox" name="opt_remember_current_page">
<property name="text">
<string>Remember the &amp;current page when quitting</string>
</property>
</widget>
</item>
<item row="9" column="0">
<widget class="QCheckBox" name="opt_line_scrolling_stops_on_pagebreaks"> <widget class="QCheckBox" name="opt_line_scrolling_stops_on_pagebreaks">
<property name="text"> <property name="text">
<string>Line &amp;scrolling stops at page breaks</string> <string>Line &amp;scrolling stops at page breaks</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="9" column="1">
<widget class="QCheckBox" name="opt_fullscreen_clock">
<property name="text">
<string>Show &amp;clock in full screen mode</string>
</property>
</widget>
</item>
</layout> </layout>
</item> </item>
</layout> </layout>

View File

@ -134,6 +134,7 @@ class Document(QWebPage): # {{{
screen_width = QApplication.desktop().screenGeometry().width() screen_width = QApplication.desktop().screenGeometry().width()
# Leave some space for the scrollbar and some border # Leave some space for the scrollbar and some border
self.max_fs_width = min(opts.max_fs_width, screen_width-50) self.max_fs_width = min(opts.max_fs_width, screen_width-50)
self.fullscreen_clock = opts.fullscreen_clock
def fit_images(self): def fit_images(self):
if self.do_fit_images and not self.in_paged_mode: if self.do_fit_images and not self.in_paged_mode:
@ -193,6 +194,14 @@ class Document(QWebPage): # {{{
self.read_anchor_positions(use_cache=False) self.read_anchor_positions(use_cache=False)
self.first_load = False self.first_load = False
def colors(self):
self.javascript('''
bs = getComputedStyle(document.body);
py_bridge.value = [bs.backgroundColor, bs.color]
''')
ans = self.bridge_value
return (ans if isinstance(ans, list) else ['white', 'black'])
def read_anchor_positions(self, use_cache=True): def read_anchor_positions(self, use_cache=True):
self.bridge_value = tuple(self.index_anchors) self.bridge_value = tuple(self.index_anchors)
self.javascript(u''' self.javascript(u'''

View File

@ -6,9 +6,10 @@ from functools import partial
from threading import Thread from threading import Thread
from PyQt4.Qt import (QApplication, Qt, QIcon, QTimer, QByteArray, QSize, from PyQt4.Qt import (QApplication, Qt, QIcon, QTimer, QByteArray, QSize,
QDoubleSpinBox, QLabel, QTextBrowser, QPropertyAnimation, QPainter, QTime, QDoubleSpinBox, QLabel, QTextBrowser, QPropertyAnimation,
QBrush, QColor, pyqtSignal, QUrl, QRegExpValidator, QRegExp, QLineEdit, QPainter, QBrush, QColor, pyqtSignal, QUrl, QRegExpValidator, QRegExp,
QToolButton, QMenu, QInputDialog, QAction, QKeySequence, QModelIndex) QLineEdit, QToolButton, QMenu, QInputDialog, QAction, QKeySequence,
QModelIndex)
from calibre.gui2.viewer.main_ui import Ui_EbookViewer from calibre.gui2.viewer.main_ui import Ui_EbookViewer
from calibre.gui2.viewer.printing import Printing from calibre.gui2.viewer.printing import Printing
@ -288,6 +289,23 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
self.addAction(self.toggle_toolbar_action) self.addAction(self.toggle_toolbar_action)
self.full_screen_label_anim = QPropertyAnimation( self.full_screen_label_anim = QPropertyAnimation(
self.full_screen_label, 'size') self.full_screen_label, 'size')
self.clock_label = QLabel('99:99', self)
self.clock_label.setVisible(False)
self.clock_label.setFocusPolicy(Qt.NoFocus)
self.clock_label_style = '''
QLabel {
text-align: right;
border-width: 1px;
border-style: solid;
border-radius: 8px;
background-color: %s;
color: %s;
font-family: monospace;
font-size: larger;
padding: 5px;
}'''
self.clock_timer = QTimer(self)
self.clock_timer.timeout.connect(self.update_clock)
self.esc_full_screen_action = a = QAction(self) self.esc_full_screen_action = a = QAction(self)
self.addAction(a) self.addAction(a)
a.setShortcut(Qt.Key_Escape) a.setShortcut(Qt.Key_Escape)
@ -454,9 +472,29 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
a.start() a.start()
QTimer.singleShot(2750, self.full_screen_label.hide) QTimer.singleShot(2750, self.full_screen_label.hide)
self.view.document.switch_to_fullscreen_mode() self.view.document.switch_to_fullscreen_mode()
if self.view.document.fullscreen_clock:
self.show_clock()
def show_clock(self):
self.clock_label.setVisible(True)
self.clock_label.setText('99:99 AA')
self.clock_timer.start(1000)
self.clock_label.setStyleSheet(self.clock_label_style%
tuple(self.view.document.colors()))
self.clock_label.resize(self.clock_label.sizeHint())
sw = QApplication.desktop().screenGeometry(self.view)
self.clock_label.move(sw.width() - self.vertical_scrollbar.width() - 15
- self.clock_label.width(), sw.height() -
self.clock_label.height()-10)
self.update_clock()
def update_clock(self):
self.clock_label.setText(QTime.currentTime().toString('h:mm a'))
def showNormal(self): def showNormal(self):
self.view.document.page_position.save() self.view.document.page_position.save()
self.clock_label.setVisible(False)
self.clock_timer.stop()
self.window_mode_changed = 'normal' self.window_mode_changed = 'normal'
self.esc_full_screen_action.setEnabled(False) self.esc_full_screen_action.setEnabled(False)
self.tool_bar.setVisible(True) self.tool_bar.setVisible(True)

View File

@ -251,6 +251,14 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
defs['similar_tags_match_kind'] = 'match_all' defs['similar_tags_match_kind'] = 'match_all'
defs['similar_series_search_key'] = 'series' defs['similar_series_search_key'] = 'series'
defs['similar_series_match_kind'] = 'match_any' defs['similar_series_match_kind'] = 'match_any'
defs['book_display_fields'] = [
('title', False), ('authors', True), ('formats', True),
('series', True), ('identifiers', True), ('tags', True),
('path', True), ('publisher', False), ('rating', False),
('author_sort', False), ('sort', False), ('timestamp', False),
('uuid', False), ('comments', True), ('id', False), ('pubdate', False),
('last_modified', False), ('size', False), ('languages', False),
]
# Migrate the bool tristate tweak # Migrate the bool tristate tweak
defs['bools_are_tristate'] = \ defs['bools_are_tristate'] = \

View File

@ -34,6 +34,9 @@ class DBPrefs(dict):
def to_raw(self, val): def to_raw(self, val):
return json.dumps(val, indent=2, default=to_json) return json.dumps(val, indent=2, default=to_json)
def has_setting(self, key):
return key in self
def __getitem__(self, key): def __getitem__(self, key):
try: try:
return dict.__getitem__(self, key) return dict.__getitem__(self, key)

View File

@ -76,15 +76,15 @@ def test_qt():
print ('Qt OK!') print ('Qt OK!')
def test_imaging(): def test_imaging():
from calibre.utils.magick.draw import create_canvas, Image from calibre.ebooks import calibre_cover
im = create_canvas(20, 20, '#ffffff') data = calibre_cover('test', 'ok')
jpg = im.export('jpg') if len(data) > 1000:
Image().load(jpg)
im.export('png')
print ('ImageMagick OK!') print ('ImageMagick OK!')
else:
raise RuntimeError('ImageMagick choked!')
from PIL import Image from PIL import Image
i = Image.open(cStringIO.StringIO(jpg)) i = Image.open(cStringIO.StringIO(data))
if i.size != (20, 20): if i.size < (20, 20):
raise RuntimeError('PIL choked!') raise RuntimeError('PIL choked!')
print ('PIL OK!') print ('PIL OK!')
@ -94,6 +94,12 @@ def test_unrar():
raise RuntimeError('Failed to load libunrar') raise RuntimeError('Failed to load libunrar')
print ('Unrar OK!') print ('Unrar OK!')
def test_icu():
from calibre.utils.icu import _icu_not_ok
if _icu_not_ok:
raise RuntimeError('ICU module not loaded/valid')
print ('ICU OK!')
def test(): def test():
test_plugins() test_plugins()
test_lxml() test_lxml()
@ -102,6 +108,7 @@ def test():
test_qt() test_qt()
test_imaging() test_imaging()
test_unrar() test_unrar()
test_icu()
if iswindows: if iswindows:
test_win32() test_win32()
test_winutil() test_winutil()

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More