0.8.5 update

This commit is contained in:
GRiker 2011-06-10 12:30:29 -06:00
commit 60c0f68578
87 changed files with 58780 additions and 32001 deletions

View File

@ -19,6 +19,67 @@
# new recipes: # new recipes:
# - title: # - title:
- version: 0.8.5
date: 2011-06-10
new features:
- title: "A new 'portable' calibre build, useful if you like to carry around calibre and its library on a USB key"
type: major
description: "For details, see: http://calibre-ebook.com/download_portable"
- title: "E-book viewer: Remember the last used font size multiplier."
tickets: [774343]
- title: "Preliminary support for the Kobo Touch. Drivers for the ZTE v9 tablet, Samsung S2, Notion Ink Adam and PocketBook 360+"
- title: "When downloading metadata merge rather than replace tags"
- title: "Edit metadata dialog: When pasting in an ISBN, if not valid ISBN if present on the clipboard popup a box for the user to enter the ISBN"
- title: "Windows build: Add code to load .pyd python extensions from a zip file. This allows many more files in the calibre installation to be zipped up, speeding up the installer."
- title: "Add an action to remove all formats from the selected books to the remove books button"
bug fixes:
- title: "Various minor bug fixes to the column coloring code"
- title: "Fix the not() template function"
- title: "Nook Color/TSR: When sending books to the storage card place them in the My Files/Books subdirectory. Also do not upload cover thumbnails as users report that the NC/TSR don't use them."
tickets: [792842]
- title: "Get Books: Update plugins for Amazon and B&N stores to handle website changes. Enable some stores by default on first run. Add Zixo store"
tickets: [792762]
- title: "Comic Input: Replace the # character in filenames as it can cause problem with conversion/vieweing."
tickets: [792723]
- title: "When writing files to zipfile, reset timestamp if it doesn't fit in 1980's vintage storage structures"
- title: "Amazon metadata plugin: Fix parsing of published date from amazon.de when it has februar in it"
improved recipes:
- Ambito
- GoComics
- Le Monde Diplomatique
- Max Planck
- express.de
new recipes:
- title: Ambito Financiero
author: Darko Miletic
- title: Stiin Tas Technica
author: Silviu Cotoara
- title: "Metro News NL"
author: DrMerry
- title: "Brigitte.de, Polizeipresse DE and Heise Online"
author: schuster
- version: 0.8.4 - version: 0.8.4
date: 2011-06-03 date: 2011-06-03

View File

@ -1,7 +1,5 @@
#!/usr/bin/env python
__license__ = 'GPL v3' __license__ = 'GPL v3'
__copyright__ = '2008-2009, Darko Miletic <darko.miletic at gmail.com>' __copyright__ = '2008-2011, Darko Miletic <darko.miletic at gmail.com>'
''' '''
ambito.com ambito.com
''' '''
@ -11,51 +9,56 @@ from calibre.web.feeds.news import BasicNewsRecipe
class Ambito(BasicNewsRecipe): class Ambito(BasicNewsRecipe):
title = 'Ambito.com' title = 'Ambito.com'
__author__ = 'Darko Miletic' __author__ = 'Darko Miletic'
description = 'Informacion Libre las 24 horas' description = 'Ambito.com con noticias del Diario Ambito Financiero de Buenos Aires'
publisher = 'Ambito.com' publisher = 'Editorial Nefir S.A.'
category = 'news, politics, Argentina' category = 'news, politics, economy, finances, Argentina'
oldest_article = 2 oldest_article = 2
max_articles_per_feed = 100
no_stylesheets = True no_stylesheets = True
encoding = 'iso-8859-1' encoding = 'cp1252'
cover_url = 'http://www.ambito.com/img/logo_.jpg' masthead_url = 'http://www.ambito.com/img/logo_.jpg'
remove_javascript = True
use_embedded_content = False use_embedded_content = False
language = 'es_AR'
publication_type = 'newsportal'
extra_css = """
body{font-family: "Trebuchet MS",Verdana,sans-serif}
.volanta{font-size: small}
.t2_portada{font-size: xx-large; font-family: Georgia,serif; color: #026698}
"""
html2lrf_options = [
'--comment', description
, '--category', category
, '--publisher', publisher
]
html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"' conversion_options = {
'comment' : description
, 'tags' : category
, 'publisher' : publisher
, 'language' : language
}
keep_only_tags = [dict(name='div', attrs={'align':'justify'})] keep_only_tags = [dict(name='div', attrs={'align':'justify'})]
remove_tags = [dict(name=['object','link','embed','iframe','meta','link','table','img'])]
remove_tags = [dict(name=['object','link'])] remove_attributes = ['align']
feeds = [ feeds = [
(u'Principales Noticias', u'http://www.ambito.com/rss/noticiasp.asp' ) (u'Principales Noticias', u'http://www.ambito.com/rss/noticiasp.asp' )
,(u'Economia' , u'http://www.ambito.com/rss/noticias.asp?S=Econom%EDa' ) ,(u'Economia' , u'http://www.ambito.com/rss/noticias.asp?S=Econom%EDa' )
,(u'Politica' , u'http://www.ambito.com/rss/noticias.asp?S=Pol%EDtica' ) ,(u'Politica' , u'http://www.ambito.com/rss/noticias.asp?S=Pol%EDtica' )
,(u'Informacion General' , u'http://www.ambito.com/rss/noticias.asp?S=Informaci%F3n%20General') ,(u'Informacion General' , u'http://www.ambito.com/rss/noticias.asp?S=Informaci%F3n%20General')
,(u'Agro' , u'http://www.ambito.com/rss/noticias.asp?S=Agro' ) ,(u'Campo' , u'http://www.ambito.com/rss/noticias.asp?S=Agro' )
,(u'Internacionales' , u'http://www.ambito.com/rss/noticias.asp?S=Internacionales' ) ,(u'Internacionales' , u'http://www.ambito.com/rss/noticias.asp?S=Internacionales' )
,(u'Deportes' , u'http://www.ambito.com/rss/noticias.asp?S=Deportes' ) ,(u'Deportes' , u'http://www.ambito.com/rss/noticias.asp?S=Deportes' )
,(u'Espectaculos' , u'http://www.ambito.com/rss/noticias.asp?S=Espect%E1culos' ) ,(u'Espectaculos' , u'http://www.ambito.com/rss/noticias.asp?S=Espect%E1culos' )
,(u'Tecnologia' , u'http://www.ambito.com/rss/noticias.asp?S=Tecnologia' ) ,(u'Tecnologia' , u'http://www.ambito.com/rss/noticias.asp?S=Tecnolog%EDa' )
,(u'Salud' , u'http://www.ambito.com/rss/noticias.asp?S=Salud' )
,(u'Ambito Nacional' , u'http://www.ambito.com/rss/noticias.asp?S=Ambito%20Nacional' ) ,(u'Ambito Nacional' , u'http://www.ambito.com/rss/noticias.asp?S=Ambito%20Nacional' )
] ]
def print_version(self, url): def print_version(self, url):
return url.replace('http://www.ambito.com/noticia.asp?','http://www.ambito.com/noticias/imprimir.asp?') return url.replace('/noticia.asp?','/noticias/imprimir.asp?')
def preprocess_html(self, soup): def preprocess_html(self, soup):
mtag = '<meta http-equiv="Content-Language" content="es-AR"/>'
soup.head.insert(0,mtag)
for item in soup.findAll(style=True): for item in soup.findAll(style=True):
del item['style'] del item['style']
for item in soup.findAll('a'):
str = item.string
if str is None:
str = self.tag_to_string(item)
item.replaceWith(str)
return soup return soup
language = 'es_AR'

View File

@ -0,0 +1,87 @@
__license__ = 'GPL v3'
__copyright__ = '2011, Darko Miletic <darko.miletic at gmail.com>'
'''
ambito.com/diario
'''
import time
from calibre import strftime
from calibre.web.feeds.news import BasicNewsRecipe
class Ambito_Financiero(BasicNewsRecipe):
title = 'Ambito Financiero'
__author__ = 'Darko Miletic'
description = 'Informacion Libre las 24 horas'
publisher = 'Editorial Nefir S.A.'
category = 'news, politics, economy, Argentina'
no_stylesheets = True
encoding = 'cp1252'
masthead_url = 'http://www.ambito.com/diario/img/logo_af.gif'
publication_type = 'newspaper'
needs_subscription = 'optional'
use_embedded_content = False
language = 'es_AR'
PREFIX = 'http://www.ambito.com'
INDEX = PREFIX + '/diario/index.asp'
LOGIN = PREFIX + '/diario/login/entrada.asp'
extra_css = """
body{font-family: "Trebuchet MS",Verdana,sans-serif}
.volanta{font-size: small}
.t2_portada{font-size: xx-large; font-family: Georgia,serif; color: #026698}
"""
conversion_options = {
'comment' : description
, 'tags' : category
, 'publisher' : publisher
, 'language' : language
}
keep_only_tags = [dict(name='div', attrs={'align':'justify'})]
remove_tags = [dict(name=['object','link','embed','iframe','meta','link','table','img'])]
remove_attributes = ['align']
def get_browser(self):
br = BasicNewsRecipe.get_browser()
br.open(self.INDEX)
if self.username is not None and self.password is not None:
br.open(self.LOGIN)
br.select_form(name='frmlogin')
br['USER_NAME'] = self.username
br['USER_PASS'] = self.password
br.submit()
return br
def print_version(self, url):
return url.replace('/diario/noticia.asp?','/noticias/imprimir.asp?')
def preprocess_html(self, soup):
for item in soup.findAll(style=True):
del item['style']
for item in soup.findAll('a'):
str = item.string
if str is None:
str = self.tag_to_string(item)
item.replaceWith(str)
return soup
def parse_index(self):
soup = self.index_to_soup(self.INDEX)
cover_item = soup.find('img',attrs={'class':'fotodespliegue'})
if cover_item:
self.cover_url = self.PREFIX + cover_item['src']
articles = []
checker = []
for feed_link in soup.findAll('a', attrs={'class':['t0_portada','t2_portada','bajada']}):
url = self.PREFIX + feed_link['href']
title = self.tag_to_string(feed_link)
date = strftime("%a, %d %b %Y %H:%M:%S +0000",time.gmtime())
if url not in checker:
checker.append(url)
articles.append({
'title' :title
,'date' :date
,'url' :url
,'description':u''
})
return [(self.title, articles)]

Binary file not shown.

After

Width:  |  Height:  |  Size: 508 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 703 B

View File

@ -0,0 +1,56 @@
# -*- coding: utf-8 -*-
#!/usr/bin/env python
__license__ = 'GPL v3'
__copyright__ = u'2011, Silviu Cotoar\u0103'
'''
stiintasitehnica.com
'''
from calibre.web.feeds.news import BasicNewsRecipe
class Stiintasitehnica(BasicNewsRecipe):
title = u'\u0218tiin\u021b\u0103 \u015fi Tehnic\u0103'
__author__ = u'Silviu Cotoar\u0103'
description = u'\u0218tiin\u021b\u0103 \u015fi Tehnic\u0103'
publisher = u'\u0218tiin\u021b\u0103 \u015fi Tehnic\u0103'
oldest_article = 50
language = 'ro'
max_articles_per_feed = 100
no_stylesheets = True
use_embedded_content = False
category = u'Ziare,Reviste,Stiinta,Tehnica'
encoding = 'utf-8'
cover_url = 'http://www.stiintasitehnica.com/images/logo.jpg'
conversion_options = {
'comments' : description
,'tags' : category
,'language' : language
,'publisher' : publisher
}
keep_only_tags = [
dict(name='div', attrs={'id':'mainColumn2'})
]
remove_tags = [
dict(name='span', attrs={'class':['redEar']})
, dict(name='table', attrs={'class':['connect_widget_interactive_area']})
, dict(name='div', attrs={'class':['panel-overlay']})
, dict(name='div', attrs={'id':['pointer']})
, dict(name='img', attrs={'class':['nav-next', 'nav-prev']})
, dict(name='table', attrs={'class':['connect_widget_interactive_area']})
, dict(name='hr', attrs={'class':['dotted']})
]
remove_tags_after = [
dict(name='hr', attrs={'class':['dotted']})
]
feeds = [
(u'Feeds', u'http://www.stiintasitehnica.com/rss/stiri.xml')
]
def preprocess_html(self, soup):
return self.adeify_images(soup)

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, 4) numeric_version = (0, 8, 5)
__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

@ -355,11 +355,17 @@ def remove_plugin(plugin_or_name):
name = getattr(plugin_or_name, 'name', plugin_or_name) name = getattr(plugin_or_name, 'name', plugin_or_name)
plugins = config['plugins'] plugins = config['plugins']
removed = False removed = False
if name in plugins.keys(): if name in plugins:
removed = True removed = True
zfp = plugins[name] try:
if os.path.exists(zfp): zfp = os.path.join(plugin_dir, name+'.zip')
os.remove(zfp) if os.path.exists(zfp):
os.remove(zfp)
zfp = plugins[name]
if os.path.exists(zfp):
os.remove(zfp)
except:
pass
plugins.pop(name) plugins.pop(name)
config['plugins'] = plugins config['plugins'] = plugins
initialize_plugins() initialize_plugins()
@ -495,8 +501,15 @@ def initialize_plugins():
builtin_names] builtin_names]
for p in conflicts: for p in conflicts:
remove_plugin(p) remove_plugin(p)
for zfp in list(config['plugins'].itervalues()) + builtin_plugins: external_plugins = config['plugins']
for zfp in list(external_plugins) + builtin_plugins:
try: try:
if not isinstance(zfp, type):
# We have a plugin name
pname = zfp
zfp = os.path.join(plugin_dir, zfp+'.zip')
if not os.path.exists(zfp):
zfp = external_plugins[pname]
try: try:
plugin = load_plugin(zfp) if not isinstance(zfp, type) else zfp plugin = load_plugin(zfp) if not isinstance(zfp, type) else zfp
except PluginNotFound: except PluginNotFound:

View File

@ -21,7 +21,7 @@ class KOBO(USBMS):
name = 'Kobo Reader Device Interface' name = 'Kobo Reader Device Interface'
gui_name = 'Kobo Reader' gui_name = 'Kobo Reader'
description = _('Communicate with the Kobo Reader') description = _('Communicate with the Kobo Reader')
author = 'Timothy Legge and Kovid Goyal' author = 'Timothy Legge'
version = (1, 0, 9) version = (1, 0, 9)
dbversion = 0 dbversion = 0
@ -37,8 +37,8 @@ class KOBO(USBMS):
CAN_SET_METADATA = ['collections'] CAN_SET_METADATA = ['collections']
VENDOR_ID = [0x2237] VENDOR_ID = [0x2237]
PRODUCT_ID = [0x4161] PRODUCT_ID = [0x4161, 0x4163]
BCD = [0x0110, 0x0323] BCD = [0x0110, 0x0323, 0x0326]
VENDOR_NAME = ['KOBO_INC', 'KOBO'] VENDOR_NAME = ['KOBO_INC', 'KOBO']
WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = ['.KOBOEREADER', 'EREADER'] WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = ['.KOBOEREADER', 'EREADER']

View File

@ -77,7 +77,6 @@ class NOOK(USBMS):
with open('%s.jpg' % os.path.join(path, filename), 'wb') as coverfile: with open('%s.jpg' % os.path.join(path, filename), 'wb') as coverfile:
coverfile.write(coverdata) coverfile.write(coverdata)
def sanitize_path_components(self, components): def sanitize_path_components(self, components):
return [x.replace('#', '_') for x in components] return [x.replace('#', '_') for x in components]
@ -110,6 +109,11 @@ class NOOK_COLOR(NOOK):
def upload_cover(self, path, filename, metadata, filepath): def upload_cover(self, path, filename, metadata, filepath):
pass pass
def get_carda_ebook_dir(self, for_upload=False):
if for_upload:
return 'My Files/Books'
return ''
class NOOK_TSR(NOOK): class NOOK_TSR(NOOK):
gui_name = _('Nook Simple') gui_name = _('Nook Simple')
description = _('Communicate with the Nook TSR eBook reader.') description = _('Communicate with the Nook TSR eBook reader.')
@ -117,9 +121,15 @@ class NOOK_TSR(NOOK):
PRODUCT_ID = [0x003] PRODUCT_ID = [0x003]
BCD = [0x216] BCD = [0x216]
EBOOK_DIR_MAIN = EBOOK_DIR_CARD_A = 'My Files/Books' EBOOK_DIR_MAIN = 'My Files/Books'
WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = 'EBOOK_DISK' WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = 'EBOOK_DISK'
def upload_cover(self, path, filename, metadata, filepath): def upload_cover(self, path, filename, metadata, filepath):
pass pass
def get_carda_ebook_dir(self, for_upload=False):
if for_upload:
return 'My Files/Books'
return ''

View File

@ -837,6 +837,9 @@ class Device(DeviceConfig, DevicePlugin):
def get_main_ebook_dir(self, for_upload=False): def get_main_ebook_dir(self, for_upload=False):
return self.EBOOK_DIR_MAIN return self.EBOOK_DIR_MAIN
def get_carda_ebook_dir(self, for_upload=False):
return self.EBOOK_DIR_CARD_A
def _sanity_check(self, on_card, files): def _sanity_check(self, on_card, files):
if on_card == 'carda' and not self._card_a_prefix: if on_card == 'carda' and not self._card_a_prefix:
raise ValueError(_('The reader has no storage card in this slot.')) raise ValueError(_('The reader has no storage card in this slot.'))
@ -847,7 +850,7 @@ class Device(DeviceConfig, DevicePlugin):
if on_card == 'carda': if on_card == 'carda':
path = os.path.join(self._card_a_prefix, path = os.path.join(self._card_a_prefix,
*(self.EBOOK_DIR_CARD_A.split('/'))) *(self.get_carda_ebook_dir(for_upload=True).split('/')))
elif on_card == 'cardb': elif on_card == 'cardb':
path = os.path.join(self._card_b_prefix, path = os.path.join(self._card_b_prefix,
*(self.EBOOK_DIR_CARD_B.split('/'))) *(self.EBOOK_DIR_CARD_B.split('/')))

View File

@ -132,7 +132,7 @@ class USBMS(CLI, Device):
self._card_b_prefix if oncard == 'cardb' \ self._card_b_prefix if oncard == 'cardb' \
else self._main_prefix else self._main_prefix
ebook_dirs = self.EBOOK_DIR_CARD_A if oncard == 'carda' else \ ebook_dirs = self.get_carda_ebook_dir() if oncard == 'carda' else \
self.EBOOK_DIR_CARD_B if oncard == 'cardb' else \ self.EBOOK_DIR_CARD_B if oncard == 'cardb' else \
self.get_main_ebook_dir() self.get_main_ebook_dir()

View File

@ -439,7 +439,8 @@ class EditMetadataAction(InterfaceAction):
view.reset() view.reset()
# Apply bulk metadata changes {{{ # Apply bulk metadata changes {{{
def apply_metadata_changes(self, id_map, title=None, msg='', callback=None): def apply_metadata_changes(self, id_map, title=None, msg='', callback=None,
merge_tags=True):
''' '''
Apply the metadata changes in id_map to the database synchronously Apply the metadata changes in id_map to the database synchronously
id_map must be a mapping of ids to Metadata objects. Set any fields you id_map must be a mapping of ids to Metadata objects. Set any fields you
@ -466,9 +467,9 @@ class EditMetadataAction(InterfaceAction):
cancelable=False) cancelable=False)
self.apply_pd.setModal(True) self.apply_pd.setModal(True)
self.apply_pd.show() self.apply_pd.show()
self._am_merge_tags = True
self.do_one_apply() self.do_one_apply()
def do_one_apply(self): def do_one_apply(self):
if self.apply_current_idx >= len(self.apply_id_map): if self.apply_current_idx >= len(self.apply_id_map):
return self.finalize_apply() return self.finalize_apply()
@ -484,6 +485,12 @@ class EditMetadataAction(InterfaceAction):
mi.identifiers = idents mi.identifiers = idents
if mi.is_null('series'): if mi.is_null('series'):
mi.series_index = None mi.series_index = None
if self._am_merge_tags:
old_tags = db.tags(i, index_is_id=True)
if old_tags:
tags = [x.strip() for x in old_tags.split(',')] + (
mi.tags if mi.tags else [])
mi.tags = list(set(tags))
db.set_metadata(i, mi, commit=False, set_title=set_title, db.set_metadata(i, mi, commit=False, set_title=set_title,
set_authors=set_authors, notify=False) set_authors=set_authors, notify=False)
self.applied_ids.append(i) self.applied_ids.append(i)

View File

@ -315,7 +315,7 @@ class MetadataSingleDialogBase(ResizableDialog):
show=True) show=True)
return return
def update_from_mi(self, mi, update_sorts=True): def update_from_mi(self, mi, update_sorts=True, merge_tags=True):
if not mi.is_null('title'): if not mi.is_null('title'):
self.title.current_val = mi.title self.title.current_val = mi.title
if update_sorts: if update_sorts:
@ -334,7 +334,11 @@ class MetadataSingleDialogBase(ResizableDialog):
if not mi.is_null('publisher'): if not mi.is_null('publisher'):
self.publisher.current_val = mi.publisher self.publisher.current_val = mi.publisher
if not mi.is_null('tags'): if not mi.is_null('tags'):
self.tags.current_val = mi.tags old_tags = self.tags.current_val
tags = mi.tags if mi.tags else []
if old_tags and merge_tags:
tags += old_tags
self.tags.current_val = tags
if not mi.is_null('identifiers'): if not mi.is_null('identifiers'):
current = self.identifiers.current_val current = self.identifiers.current_val
current.update(mi.identifiers) current.update(mi.identifiers)

View File

@ -8,10 +8,11 @@ import os, math, re, glob, sys
from base64 import b64encode from base64 import b64encode
from functools import partial from functools import partial
from PyQt4.Qt import QSize, QSizePolicy, QUrl, SIGNAL, Qt, QTimer, \ from PyQt4.Qt import (QSize, QSizePolicy, QUrl, SIGNAL, Qt, QTimer,
QPainter, QPalette, QBrush, QFontDatabase, QDialog, \ QPainter, QPalette, QBrush, QFontDatabase, QDialog,
QColor, QPoint, QImage, QRegion, QVariant, QIcon, \ QColor, QPoint, QImage, QRegion, QVariant, QIcon,
QFont, pyqtSignature, QAction, QByteArray, QMenu QFont, pyqtSignature, QAction, QByteArray, QMenu,
pyqtSignal)
from PyQt4.QtWebKit import QWebPage, QWebView, QWebSettings from PyQt4.QtWebKit import QWebPage, QWebView, QWebSettings
from calibre.utils.config import Config, StringConfig from calibre.utils.config import Config, StringConfig
@ -496,6 +497,7 @@ class EntityDeclarationProcessor(object): # {{{
class DocumentView(QWebView): # {{{ class DocumentView(QWebView): # {{{
magnification_changed = pyqtSignal(object)
DISABLED_BRUSH = QBrush(Qt.lightGray, Qt.Dense5Pattern) DISABLED_BRUSH = QBrush(Qt.lightGray, Qt.Dense5Pattern)
def __init__(self, *args): def __init__(self, *args):
@ -908,15 +910,22 @@ class DocumentView(QWebView): # {{{
if notify and self.manager is not None and self.document.ypos != old_pos: if notify and self.manager is not None and self.document.ypos != old_pos:
self.manager.scrolled(self.scroll_fraction) self.manager.scrolled(self.scroll_fraction)
@dynamic_property
def multiplier(self): def multiplier(self):
return self.document.mainFrame().textSizeMultiplier() def fget(self):
return self.document.mainFrame().textSizeMultiplier()
def fset(self, val):
self.document.mainFrame().setTextSizeMultiplier(val)
self.magnification_changed.emit(val)
return property(fget=fget, fset=fset)
def magnify_fonts(self): def magnify_fonts(self):
self.document.mainFrame().setTextSizeMultiplier(self.multiplier()+0.2) self.multiplier += 0.2
return self.document.scroll_fraction return self.document.scroll_fraction
def shrink_fonts(self): def shrink_fonts(self):
self.document.mainFrame().setTextSizeMultiplier(max(self.multiplier()-0.2, 0)) if self.multiplier >= 0.2:
self.multiplier -= 0.2
return self.document.scroll_fraction return self.document.scroll_fraction
def changeEvent(self, event): def changeEvent(self, event):

View File

@ -175,6 +175,7 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
def __init__(self, pathtoebook=None, debug_javascript=False): def __init__(self, pathtoebook=None, debug_javascript=False):
MainWindow.__init__(self, None) MainWindow.__init__(self, None)
self.setupUi(self) self.setupUi(self)
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
self.base_window_title = unicode(self.windowTitle()) self.base_window_title = unicode(self.windowTitle())
@ -345,6 +346,7 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
if self.toc.isVisible(): if self.toc.isVisible():
vprefs.set('viewer_splitter_state', vprefs.set('viewer_splitter_state',
bytearray(self.splitter.saveState())) bytearray(self.splitter.saveState()))
vprefs['multiplier'] = self.view.multiplier
def restore_state(self): def restore_state(self):
state = vprefs.get('viewer_toolbar_state', None) state = vprefs.get('viewer_toolbar_state', None)
@ -354,6 +356,9 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
self.restoreState(state, self.STATE_VERSION) self.restoreState(state, self.STATE_VERSION)
except: except:
pass pass
mult = vprefs.get('multiplier', None)
if mult:
self.view.multiplier = mult
def lookup(self, word): def lookup(self, word):
@ -476,16 +481,22 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
def font_size_larger(self, checked): def font_size_larger(self, checked):
frac = self.view.magnify_fonts() frac = self.view.magnify_fonts()
self.action_font_size_larger.setEnabled(self.view.multiplier() < 3) self.action_font_size_larger.setEnabled(self.view.multiplier < 3)
self.action_font_size_smaller.setEnabled(self.view.multiplier() > 0.2) self.action_font_size_smaller.setEnabled(self.view.multiplier > 0.2)
self.set_page_number(frac) self.set_page_number(frac)
def font_size_smaller(self, checked): def font_size_smaller(self, checked):
frac = self.view.shrink_fonts() frac = self.view.shrink_fonts()
self.action_font_size_larger.setEnabled(self.view.multiplier() < 3) self.action_font_size_larger.setEnabled(self.view.multiplier < 3)
self.action_font_size_smaller.setEnabled(self.view.multiplier() > 0.2) self.action_font_size_smaller.setEnabled(self.view.multiplier > 0.2)
self.set_page_number(frac) self.set_page_number(frac)
def magnification_changed(self, val):
tt = _('Make font size %s\nCurrent magnification: %.1f')
self.action_font_size_larger.setToolTip(
tt %(_('larger'), val))
self.action_font_size_smaller.setToolTip(
tt %(_('smaller'), val))
def find(self, text, repeat=False, backwards=False): def find(self, text, repeat=False, backwards=False):
if not text: if not text:

View File

@ -319,7 +319,7 @@ but it requires that the Kindle be rebooted *every time* it is disconnected from
changes to the collections to be recognized. As such, it is unlikely that changes to the collections to be recognized. As such, it is unlikely that
any |app| developers will ever feel motivated enough to support it. There is however, a |app| plugin any |app| developers will ever feel motivated enough to support it. There is however, a |app| plugin
that allows you to create collections on your Kindle from the |app| metadata. It is available that allows you to create collections on your Kindle from the |app| metadata. It is available
`here <http://www.mobileread.com/forums/showthread.php?t=118635>`_. `from here <http://www.mobileread.com/forums/showthread.php?t=118635>`_.
Library Management Library Management
------------------ ------------------
@ -570,7 +570,7 @@ For many reasons:
* *There is no need to update every week*. If you are happy with how |app| works turn off the update notification and be on your merry way. Check back to see if you want to update once a year or so. * *There is no need to update every week*. If you are happy with how |app| works turn off the update notification and be on your merry way. Check back to see if you want to update once a year or so.
* Pre downloading the updates for all users in the background would mean require about 80TB of bandwidth *every week*. That costs thousands of dollars a month. And |app| is currently growing at 300,000 new users every month. * Pre downloading the updates for all users in the background would mean require about 80TB of bandwidth *every week*. That costs thousands of dollars a month. And |app| is currently growing at 300,000 new users every month.
* If I implement a dialog that downloads the update and launches it, instead of going to the website as it does now, that would save the most ardent |app| updater, *at most five clicks a week*. There are far higher priority things to do in |app| development. * If I implement a dialog that downloads the update and launches it, instead of going to the website as it does now, that would save the most ardent |app| updater, *at most five clicks a week*. There are far higher priority things to do in |app| development.
* If you really, really hate downloading |app| every week but still want to be upto the latest, I encourage you to run from source, which makes updating trivial. Instructions are :ref:`here <develop>`. * If you really, really hate downloading |app| every week but still want to be up to the latest, I encourage you to run from source, which makes updating trivial. Instructions are :ref:`available here <develop>`.
How is |app| licensed? How is |app| licensed?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -99,12 +99,12 @@ def test():
test_lxml() test_lxml()
test_fontconfig() test_fontconfig()
test_sqlite() test_sqlite()
if iswindows:
test_winutil()
test_win32()
test_qt() test_qt()
test_imaging() test_imaging()
test_unrar() test_unrar()
if iswindows:
test_win32()
test_winutil()
if __name__ == '__main__': if __name__ == '__main__':
test() test()

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

17024
src/calibre/translations/az.po Normal file

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

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

View File

@ -202,6 +202,10 @@ def main(args=sys.argv):
if len(args) > 1: if len(args) > 1:
if len(args) < 4:
print ('You must specify the from address, to address and body text'
' on the command line')
return 1
msg = compose_mail(args[1], args[2], args[3], subject=opts.subject, msg = compose_mail(args[1], args[2], args[3], subject=opts.subject,
attachment=opts.attachment) attachment=opts.attachment)
from_, to = args[1:3] from_, to = args[1:3]