This commit is contained in:
GRiker 2012-06-15 04:15:02 -06:00
commit f7b93c1554
130 changed files with 42010 additions and 39095 deletions

View File

@ -20,6 +20,57 @@
# - title:
- version: 0.8.56
date: 2012-06-15
new features:
- title: "Make the new calibre style default on Windows and OS X."
type: major
description: "This change gives a more 'modern' feel to the calibre user interface with focus highlighting, gradients, rounded corners, etc. In case you prefer the old look, you can restore under Preferences->Look & Feel->User interface style"
- title: "Get Books: Add the new SONY Reader store"
- title: "Read metadata from .docx (Microsoft Word) files"
- title: "Allow customizing the behavior of the searching for similar books by right clicking the book. You can now tell calibre to search different columns than the traditional author/series/publisher/tags/etc. in Preferences->Searching"
- title: "Add option to restore alternating row colors to the Tag Browser under Preferences->Look & Feel->Tag Browser"
- title: "Update to Qt 4.8.2 on windows compiled with link time code generation for a small performance boost"
bug fixes:
- title: "Get Books: Update plugins to handle website changes at ebooks.com, project gutenberg, and virtualo"
- title: "AZW3 Output: Fix TOC at start option not working"
- title: "AZW3 Output: Close self closing script/style/title/head tags explicitly as they cause problems in webkit based renderers like the Kindle Fire and calibre's viewers."
- title: "Fix the current_library_name() template function not updating after a library switch"
- title: "AZW3 Output: Handle the case of a link pointing to the last line of text in the document."
tickets: [1011330]
- title: "Fix regression in 0.8.55 that broke highlighting of items matching a search in the Tag Browser"
tickets: [1011030]
- title: "News download: Handle query only relative URLs"
improved recipes:
- Christian Science Monitor
- Neue Zurcher Zeitung
- Birmignham Post
- Metro UK
- New Musical Express
- The Independent
- The Daily Mirror
- Vreme
- Smithsonian Magazine
new recipes:
- title: NZZ Webpaper
author: Bernd Leinfelder
- version: 0.8.55
date: 2012-06-08

View File

@ -172,7 +172,7 @@ You can see the ``prefs`` object being used in main.py:
:pyobject: DemoDialog.config
The different types of plugins
The plugin API
--------------------------------
As you may have noticed above, a plugin in |app| is a class. There are different classes for the different types of plugins in |app|.

View File

@ -5,11 +5,11 @@ class AdvancedUserRecipe1306097511(BasicNewsRecipe):
timefmt = ''
__author__ = 'Dave Asbury'
cover_url = 'http://1.bp.blogspot.com/_GwWyq5eGw9M/S9BHPHxW55I/AAAAAAAAB6Q/iGCWl0egGzg/s320/Birmingham+post+Lite+front.JPG'
oldest_article = 1
max_articles_per_feed = 20
oldest_article = 2
max_articles_per_feed = 12
remove_empty_feeds = True
remove_javascript = True
auto_cleanup = True
#auto_cleanup = True
language = 'en_GB'
@ -17,9 +17,12 @@ class AdvancedUserRecipe1306097511(BasicNewsRecipe):
keep_only_tags = [
#dict(name='h1',attrs={'id' : 'article-headline'}),
#dict(attrs={'class':['article-meta-author','article-meta-date','article main','art-o art-align-center otm-1 ']}),
#dict(name='p')
dict(name='h1',attrs={'id' : 'article-headline'}),
dict(attrs={'class':['article-meta-author','article-meta-date','article main','art-o art-align-center otm-1 ']}),
dict(name='div',attrs={'class' : 'article-image full'}),
dict(attrs={'clas' : 'art-o art-align-center otm-1 '}),
dict(name='div',attrs={'class' : 'article main'}),
#dict(name='p')
#dict(attrs={'id' : 'three-col'})
]
remove_tags = [
@ -28,7 +31,7 @@ class AdvancedUserRecipe1306097511(BasicNewsRecipe):
]
feeds = [
#(u'News',u'http://www.birminghampost.net/news/rss.xml'),
(u'Local News', u'http://www.birminghampost.net/news/west-midlands-news/rss.xml'),
(u'West Mids. News', u'http://www.birminghampost.net/news/west-midlands-news/rss.xml'),
(u'UK News', u'http://www.birminghampost.net/news/uk-news/rss.xml'),
(u'Sports',u'http://www.birminghampost.net/midlands-birmingham-sport/rss.xml'),
(u'Bloggs & Comments',u'http://www.birminghampost.net/comment/rss.xml')

View File

@ -4,6 +4,7 @@ __copyright__ = '2012, Darko Miletic <darko.miletic at gmail.com>'
www.csmonitor.com
'''
import re
from calibre.web.feeds.news import BasicNewsRecipe
class CSMonitor(BasicNewsRecipe):
@ -40,13 +41,15 @@ class CSMonitor(BasicNewsRecipe):
remove_tags = [
dict(name=['meta','link','iframe','object','embed'])
,dict(attrs={'class':['podStoryRel','bottom-rel','hide']})
,dict(attrs={'class':re.compile('(^|| )podStoryRel($|| )', re.DOTALL)})
,dict(attrs={'class':['bottom-rel','hide']})
,dict(attrs={'id':['pgallerycarousel_enlarge','pgallerycarousel_related']})
]
keep_only_tags = [
dict(name='h1', attrs={'class':'head'})
,dict(name='h2', attrs={'class':'subhead'})
,dict(attrs={'class':['sByline','podStoryGal','ui-body-header','sBody']})
,dict(attrs={'class':['sByline','thePhoto','ui-body-header']})
,dict(attrs={'class':re.compile('(^|| )sBody($|| )', re.DOTALL)})
]
remove_attributes=['xmlns:fb']
@ -74,10 +77,10 @@ class CSMonitor(BasicNewsRecipe):
if nexttag:
nurl = 'http://www.csmonitor.com' + nexttag['href']
soup2 = self.index_to_soup(nurl)
texttag = soup2.find(attrs={'class':'sBody'})
texttag = soup2.find(attrs={'class':re.compile('(^|| )sBody($|| )', re.DOTALL)})
if texttag:
appendtag = soup.find(attrs={'class':'sBody'})
for citem in texttag.findAll(attrs={'class':['podStoryRel','bottom-rel','hide']}):
appendtag = soup.find(attrs={'class':re.compile('(^|| )sBody($|| )', re.DOTALL)})
for citem in texttag.findAll(attrs={'class':[re.compile('(^|| )podStoryRel($|| )', re.DOTALL),'bottom-rel','hide']}):
citem.extract()
self.append_page(soup2)
texttag.extract()

View File

@ -7,7 +7,7 @@ class AdvancedUserRecipe1306061239(BasicNewsRecipe):
description = 'News as provided by The Daily Mirror -UK'
__author__ = 'Dave Asbury'
# last updated 28/4/12
# last updated 8/6/12
language = 'en_GB'
#cover_url = 'http://yookeo.com/screens/m/i/mirror.co.uk.jpg'
@ -28,7 +28,7 @@ class AdvancedUserRecipe1306061239(BasicNewsRecipe):
dict(name='div',attrs={'class' : 'lead-text'}),
dict(name='div',attrs={'class' : 'styleGroup clearfix'}),
dict(name='div',attrs={'class' : 'widget relatedContents pictures widget-editable viziwyg-section-245 inpage-widget-158123'}),
dict(name='figure',attrs={'class' : 'clearfix'}),
# dict(name='figure',attrs={'class' : 'clearfix'}),
dict(name='div',attrs={'class' :'body '}),
#dict(attrs={'class' : ['article-attr','byline append-1','published']}),
@ -37,6 +37,7 @@ class AdvancedUserRecipe1306061239(BasicNewsRecipe):
remove_tags = [
dict(attrs={'class' : ['article sa-teaser type-opinion','image-gallery','gallery-caption']}),
dict(attrs={'class' : 'comment'}),
dict(name='title'),
dict(name='ul',attrs={'class' : 'clearfix breadcrumbs '}),
@ -89,6 +90,3 @@ class AdvancedUserRecipe1306061239(BasicNewsRecipe):
#cover_url = cov2
#cover_url = 'http://www.thesun.co.uk/img/global/new-masthead-logo.png'
return cover_url

View File

@ -15,6 +15,10 @@ class TheIndependentNew(BasicNewsRecipe):
#Flag to enable/disable image fetching (not business)
_FETCH_IMAGES = True
#Set max gallery images here (respects _FETCH_IMAGES)
# -1 for infinite
_MAX_GALLERY_IMAGES = -1
#used for converting rating to stars
_STAR_URL = 'http://www.independent.co.uk/skins/ind/images/rating_star.png'
@ -41,6 +45,7 @@ class TheIndependentNew(BasicNewsRecipe):
dict(attrs={'id' : ['RelatedArtTag','renderBiography']}),
dict(attrs={'class' : ['autoplay','openBiogPopup']}),
dict(name='img',attrs={'alt' : ['Get Adobe Flash player']}),
dict(name='img',attrs={'alt' : ['view gallery']}),
dict(attrs={'style' : re.compile('.*')}),
]
@ -171,20 +176,38 @@ class TheIndependentNew(BasicNewsRecipe):
for item in element.findAll('a',attrs={'href' : re.compile('.*')}):
if item.img is not None:
#use full size image
images = []
img = item.findNext('img')
if not '?action=gallery' in item['href']:
img['src'] = item['href']
#insert caption if available
if img.get('title') and (len(img['title']) > 1):
tag = Tag(soup,'h3')
text = ''
try:
text = img['data-title']
except:
pass
if img.get('title') and (len(img['title']) > 1):
text = NavigableString(img['title'])
tag.insert(0,text)
images.append((img, tag))
else:
gallery_images, remove_link = self._get_gallery_images(item['href'])
images = images + gallery_images
if remove_link:
gal_link = soup.find('a',attrs={'id' : 'view-gallery'})
if gal_link:
gal_link.extract()
img.extract()
for (img, title) in images:
#insert caption if available
if title:
#picture before text
img.extract()
item.insert(0,img)
item.insert(1,tag)
item.insert(1,title)
# remove link
item.name = "div"
@ -324,6 +347,44 @@ class TheIndependentNew(BasicNewsRecipe):
return soup
def _get_gallery_images(self,url):
gallery_soup = self.index_to_soup(url)
images = []
remove_link = True
total = 1
try:
counter = gallery_soup.find('div',attrs={'id' : ['counter']})
total = counter.contents[0].split('/')
total = int(total[1].rstrip())
except:
total = 1
if self._MAX_GALLERY_IMAGES >= 0 and total > self._MAX_GALLERY_IMAGES:
total = self._MAX_GALLERY_IMAGES
remove_link = False
for i in range(1, total +1):
image, title = self._get_image_from_gallery(gallery_soup)
if image:
images.append((image,title))
next = url + '&ino=' + str(i + 1)
gallery_soup = self.index_to_soup(next)
images.reverse()
return images, remove_link
def _get_image_from_gallery(self,soup):
try:
container = soup.find('div',attrs={'id' : ['main-image']})
image = container.find('img')
if image:
title = soup.find('div',attrs={'id' : ['image-title']})
return image, title
except:
print 'error fetching gallery image'
return None
def _recurisvely_linearise_tag_tree(
self,
item,

View File

@ -4,6 +4,7 @@ class AdvancedUserRecipe1306097511(BasicNewsRecipe):
description = 'News as provide by The Metro -UK'
#timefmt = ''
__author__ = 'Dave Asbury'
#last update 9/6/12
cover_url = 'http://profile.ak.fbcdn.net/hprofile-ak-snc4/276636_117118184990145_2132092232_n.jpg'
#no_stylesheets = True
oldest_article = 1
@ -11,7 +12,7 @@ class AdvancedUserRecipe1306097511(BasicNewsRecipe):
remove_empty_feeds = True
remove_javascript = True
auto_cleanup = True
encoding = 'UTF-8'
language = 'en_GB'
masthead_url = 'http://e-edition.metro.co.uk/images/metro_logo.gif'

View File

@ -1,23 +1,47 @@
from calibre.web.feeds.news import BasicNewsRecipe
from calibre import browser
class AdvancedUserRecipe1306061239(BasicNewsRecipe):
title = u'New Musical Express Magazine'
__author__ = "scissors"
language = 'en'
description = 'Author D.Asbury. UK Rock & Pop Mag. '
__author__ = 'Dave Asbury'
# last updated 9/6/12
remove_empty_feeds = True
remove_javascript = True
no_stylesheets = True
oldest_article = 7
max_articles_per_feed = 100
max_articles_per_feed = 20
#auto_cleanup = True
language = 'en_GB'
def get_cover_url(self):
soup = self.index_to_soup('http://www.magazinesdirect.com/categories/mens/tv-and-music/')
cov = soup.find(attrs={'title' : 'NME magazine subscriptions'})
cov2 = 'http://www.magazinesdirect.com'+cov['src']
print '***cov = ',cov2,' ***'
cover_url = str(cov2)
# print '**** Cov url =*', cover_url,'***'
#print '**** Cov url =*','http://www.magazinesdirect.com/article_images/articledir_3138/1569221/1_largelisting.jpg','***'
br = browser()
br.set_handle_redirect(False)
try:
br.open_novisit(cov2)
cover_url = str(cov2)
except:
cover_url = 'http://tawanda3000.files.wordpress.com/2011/02/nme-logo.jpg'
return cover_url
masthead_url = 'http://tawanda3000.files.wordpress.com/2011/02/nme-logo.jpg'
remove_tags = [
dict( attrs={'class':'clear_icons'}),
dict( attrs={'class':'share_links'}),
dict( attrs={'id':'right_panel'}),
dict( attrs={'class':'today box'})
dict( attrs={'class':'today box'}),
]
]
keep_only_tags = [
@ -28,7 +52,9 @@ class AdvancedUserRecipe1306061239(BasicNewsRecipe):
dict(attrs={'class' : 'bPosts'}),
dict(attrs={'class' : 'text'}),
dict(attrs={'id' : 'article_gallery'}),
#dict(attrs={'class' : 'image'}),
dict(attrs={'class' : 'article_text'})
]
@ -36,7 +62,8 @@ class AdvancedUserRecipe1306061239(BasicNewsRecipe):
feeds = [
(u'NME News', u'http://feeds2.feedburner.com/nmecom/rss/newsxml'),
(u'Reviews', u'http://feeds2.feedburner.com/nme/SdML'),
(u'Blogs', u'http://www.nme.com/blog/index.php?blog=140&tempskin=_rss2'),
#(u'Reviews', u'http://feeds2.feedburner.com/nme/SdML'),
(u'Reviews',u'http://feed43.com/4138608576351646.xml'),
(u'Bloggs',u'http://feed43.com/3326754333186048.xml'),
]

View File

@ -1,6 +1,6 @@
__license__ = 'GPL v3'
__copyright__ = '2009-2010, Darko Miletic <darko.miletic at gmail.com>'
__copyright__ = '2009-2010, Darko Miletic <darko.miletic at gmail.com>, 2012 Bernd Leinfelder <skoll1975@gmail.com>'
'''
www.nzz.ch
@ -10,7 +10,7 @@ from calibre.web.feeds.recipes import BasicNewsRecipe
class Nzz(BasicNewsRecipe):
title = 'NZZ Online'
__author__ = 'Darko Miletic'
__author__ = 'Darko Miletic, Bernd Leinfelder'
description = 'Laufend aktualisierte Nachrichten, Analysen und Hintergruende zu Politik, Wirtschaft, Kultur und Sport'
publisher = 'NZZ AG'
category = 'news, politics, nachrichten, Switzerland'
@ -41,31 +41,31 @@ class Nzz(BasicNewsRecipe):
,'publisher' : publisher
}
keep_only_tags = [dict(name='div', attrs={'class':'zone'})]
remove_tags_before = dict(name='p', attrs={'class':'dachzeile'})
remove_tags_after=dict(name='p', attrs={'class':'fussnote'})
remove_attributes=['width','height','lang']
remove_tags_before = dict(id='main')
remove_tags_after = dict(id='articleBodyText')
remove_tags = [
dict(name=['object','link','base','meta','iframe'])
,dict(attrs={'id':'content_rectangle_1'})
,dict(attrs={'class':['weiterfuehrendeLinks','fussnote','video']})
,dict(id='social-media-floater')
,dict(name='div',attrs={'class':['box']})
]
feeds = [
(u'International' , u'http://www.nzz.ch/nachrichten/international?rss=true')
,(u'Schweiz' , u'http://www.nzz.ch/nachrichten/schweiz?rss=true')
,(u'Wirtschaft' , u'http://www.nzz.ch/nachrichten/wirtschaft/aktuell?rss=true')
,(u'Finanzmaerkte' , u'http://www.nzz.ch/finanzen/nachrichten?rss=true')
,(u'Zuerich' , u'http://www.nzz.ch/nachrichten/zuerich?rss=true')
,(u'Sport' , u'http://www.nzz.ch/nachrichten/sport?rss=true')
,(u'Panorama' , u'http://www.nzz.ch/nachrichten/panorama?rss=true')
,(u'Kultur' , u'http://www.nzz.ch/nachrichten/kultur/aktuell?rss=true')
,(u'Wissenschaft' , u'http://www.nzz.ch/nachrichten/wissenschaft?rss=true')
,(u'Medien' , u'http://www.nzz.ch/nachrichten/medien?rss=true')
,(u'Reisen' , u'http://www.nzz.ch/magazin/reisen?rss=true')
(u'International' , u'http://www.nzz.ch/aktuell/international.rss')
,(u'Schweiz' , u'http://www.nzz.ch/aktuell/schweiz.rss')
,(u'Wirtschaft' , u'http://www.nzz.ch/aktuell/wirtschaft/uebersicht.rss')
,(u'Finanzmaerkte' , u'http://www.nzz.ch/finanzen/uebersicht/finanznachrichten.rss')
,(u'Zuerich' , u'http://www.nzz.ch/aktuell/zuerich/uebersicht.rss')
,(u'Sport' , u'http://www.nzz.ch/aktuell/sport/uebersicht.rss')
,(u'Panorama' , u'http://www.nzz.ch/aktuell/panorama.rss')
,(u'Kultur' , u'http://www.nzz.ch/aktuell/feuilleton/uebersicht.rss')
,(u'Wissenschaft' , u'http://www.nzz.ch/wissen/uebersicht.rss')
,(u'Reisen' , u'http://www.nzz.ch/lebensart/reisen-freizeit.rss')
,(u'Auto Mobil' , u'http://www.nzz.ch/lebensart/auto-mobil.rss')
,(u'Digital' , u'http://www.nzz.ch/lebensart/digital.rss')
,(u'Stil' , u'http://www.nzz.ch/lebensart/stil.rss')
,(u'Wein-Keller' , u'http://www.nzz.ch/lebensart/wein-keller.rss')
]
def preprocess_html(self, soup):
for item in soup.findAll(style=True):
del item['style']
return self.adeify_images(soup)

View File

@ -0,0 +1,90 @@
from calibre import strftime
__license__ = 'GPL v3'
__copyright__ = '2012, Bernd Leinfelder <skoll1975 at gmail.com> '
'''
webpaper.nzz.ch
'''
from calibre.web.feeds.recipes import BasicNewsRecipe
class Nzz(BasicNewsRecipe):
title = 'NZZ Webpaper'
__author__ = 'Bernd Leinfelder'
description = 'Neue Zuercher Zeitung Webpaper - Erfordert NZZ Digital Abonnement'
timefmt = ' [%a, %d %b, %Y]'
publisher = 'NZZ AG'
needs_subscription = True
category = 'news, politics, nachrichten, Switzerland'
oldest_article = 2
max_articles_per_feed = 100
no_stylesheets = True
encoding = 'utf-8'
use_embedded_content = False
language = 'de'
extra_css = 'h1 {font: sans-serif large;}\n.byline {font:monospace;}'
conversion_options = {
'comments' : description
,'tags' : category
,'language' : language
,'publisher' : publisher
}
remove_tags = [dict(name='footer')]
remove_tags_before = dict(name='article')
remove_tags_after= dict(name='footer')
def parse_index(self):
baseref = 'https://webpaper.nzz.ch'
soup = self.index_to_soup(baseref)
articles = {}
key = None
ans = []
issuelist = soup.find(id="issueSelectorList")
feeds = issuelist.findAll("a")
for f in feeds:
section = f.string
sectionref = baseref + f['href']
# print "section is "+section +" and ref is "+sectionref
ans.append(section)
articlesoup = self.index_to_soup(sectionref)
articlesoup = articlesoup.findAll('article','article')
for a in articlesoup:
artlink = a.find('a')
arthref = baseref + artlink['href']
arthead = a.find('h2')
artcaption = arthead.string
pubdate = strftime('%a, %d %b')
if not artcaption is None:
# print " found article named "+artcaption+" at "+arthref
if not articles.has_key(section):
articles[section] = []
articles[section].append(
dict(title=artcaption, url=arthref, date=pubdate, description='', content=''))
ans = [(key, articles[key]) for key in ans if articles.has_key(key)]
return ans
def get_browser(self):
br = BasicNewsRecipe.get_browser()
if self.username is not None and self.password is not None:
br.open('https://webpaper.nzz.ch/login')
br.select_form(nr=0)
br['_username'] = self.username
br['_password'] = self.password
br.submit()
return br

View File

@ -1,26 +1,42 @@
import re
from calibre.web.feeds.news import BasicNewsRecipe
from calibre.ebooks.BeautifulSoup import BeautifulSoup
class SmithsonianMagazine(BasicNewsRecipe):
title = u'Smithsonian Magazine'
language = 'en'
__author__ = 'Krittika Goyal'
__author__ = 'Krittika Goyal and TerminalVeracity'
oldest_article = 31#days
max_articles_per_feed = 50
use_embedded_content = False
#encoding = 'latin1'
recursions = 1
cover_url = 'http://sphotos.xx.fbcdn.net/hphotos-snc7/431147_10150602715983253_764313347_n.jpg'
match_regexps = ['&page=[2-9]$']
preprocess_regexps = [
(re.compile(r'for more of Smithsonian\'s coverage on history, science and nature.', re.DOTALL), lambda m: '')
]
extra_css = """
h1{font-size: large; margin: .2em 0}
h2{font-size: medium; margin: .2em 0}
h3{font-size: medium; margin: .2em 0}
#byLine{margin: .2em 0}
.articleImageCaptionwide{font-style: italic}
.wp-caption-text{font-style: italic}
img{display: block}
"""
remove_stylesheets = True
#remove_tags_before = dict(name='h1', attrs={'class':'heading'})
remove_tags_after = dict(name='p', attrs={'id':'articlePaginationWrapper'})
remove_tags_after = dict(name='div', attrs={'class':['post','articlePaginationWrapper']})
remove_tags = [
dict(name='iframe'),
dict(name='div', attrs={'class':'article_sidebar_border'}),
dict(name='div', attrs={'id':['article_sidebar_border', 'most-popular_large', 'most-popular-body_large']}),
##dict(name='ul', attrs={'class':'article-tools'}),
dict(name='div', attrs={'class':['article_sidebar_border','viewMorePhotos','addtoany_share_save_container','meta','social','OUTBRAIN','related-articles-inpage']}),
dict(name='div', attrs={'id':['article_sidebar_border', 'most-popular_large', 'most-popular-body_large','comment_section','article-related']}),
dict(name='ul', attrs={'class':'cat-breadcrumb col three last'}),
dict(name='h4', attrs={'id':'related-topics'}),
dict(name='table'),
dict(name='a', attrs={'href':['/subArticleBottomWeb','/subArticleTopWeb','/subArticleTopMag','/subArticleBottomMag']}),
dict(name='a', attrs={'name':'comments_shaded'}),
]
@ -39,15 +55,7 @@ class SmithsonianMagazine(BasicNewsRecipe):
def preprocess_html(self, soup):
story = soup.find(name='div', attrs={'id':'article-body'})
##td = heading.findParent(name='td')
##td.extract()
soup = BeautifulSoup('<html><head><title>t</title></head><body></body></html>')
body = soup.find(name='body')
body.insert(0, story)
return soup
#def postprocess_html(self, soup, first):
#for p in soup.findAll(id='articlePaginationWrapper'): p.extract()
#if not first:
#for div in soup.findAll(id='article-head'): div.extract()
#return soup

View File

@ -1,5 +1,5 @@
__license__ = 'GPL v3'
__copyright__ = '2008-2009, Darko Miletic <darko.miletic at gmail.com>'
__copyright__ = '2008-2012, Darko Miletic <darko.miletic at gmail.com>'
'''
vreme.com
'''
@ -24,7 +24,17 @@ class Vreme(BasicNewsRecipe):
language = 'sr'
publication_type = 'magazine'
masthead_url = 'http://www.vreme.com/g/vreme-logo.gif'
extra_css = ' @font-face {font-family: "serif1";src:url(res:///opt/sony/ebook/FONT/tt0011m_.ttf)} body{font-family: serif1, serif} .article_description{font-family: serif1, serif} @font-face {font-family: "sans1";src:url(res:///opt/sony/ebook/FONT/tt0003m_.ttf)} .heading1{font-family: sans1, sans-serif; font-size: x-large; font-weight: bold} .heading2{font-family: sans1, sans-serif; font-size: large; font-weight: bold} .toc-heading{font-family: sans1, sans-serif; font-size: small} .column-heading2{font-family: sans1, sans-serif; font-size: large} .column-heading1{font-family: sans1, sans-serif; font-size: x-large} .column-normal{font-family: sans1, sans-serif; font-size: medium} .large{font-family: sans1, sans-serif; font-size: large} '
extra_css = """
@font-face {font-family: "serif1";src:url(res:///opt/sony/ebook/FONT/tt0011m_.ttf)}
@font-face {font-family: "sans1";src:url(res:///opt/sony/ebook/FONT/tt0003m_.ttf)}
body{font-family: serif1, serif}
.article_description{font-family: serif1, serif}
.heading1{font-family: sans1, sans-serif; font-size: x-large; font-weight: bold} .heading2{font-family: sans1, sans-serif; font-size: large; font-weight: bold} .toc-heading{font-family: sans1, sans-serif; font-size: small}
.column-heading2{font-family: sans1, sans-serif; font-size: large}
.column-heading1{font-family: sans1, sans-serif; font-size: x-large}
.column-normal{font-family: sans1, sans-serif; font-size: medium}
.large{font-family: sans1, sans-serif; font-size: large}
"""
conversion_options = {
'comment' : description
@ -58,8 +68,11 @@ class Vreme(BasicNewsRecipe):
for item in soup.findAll(['h3','h4']):
description = u''
title_prefix = u''
feed_link = item.find('a')
if feed_link and feed_link.has_key('href') and feed_link['href'].startswith('/cms/view.php'):
feed_link = item.find('a', href=True)
if feed_link and (feed_link['href'].startswith('cms/view.php') or feed_link['href'].startswith('/cms/view.php')):
if feed_link['href'].startswith('cms/view.php'):
url = self.INDEX + '/' + feed_link['href']
else:
url = self.INDEX + feed_link['href']
title = title_prefix + self.tag_to_string(feed_link)
date = strftime(self.timefmt)

View File

@ -442,12 +442,6 @@ metadata_edit_custom_column_order = []
# calibre.
public_smtp_relay_delay = 301
#: Remove the bright yellow lines at the edges of the book list
# Control whether the bright yellow lines at the edges of book list are drawn
# when a section of the user interface is hidden. Changes will take effect
# after a restart of calibre.
draw_hidden_section_indicators = True
#: The maximum width and height for covers saved in the calibre library
# All covers in the calibre library will be resized, preserving aspect ratio,
# to fit within this size. This is to prevent slowdowns caused by extremely

View File

@ -15,7 +15,7 @@ function show_reference_panel(ref) {
panel = $("#calibre_reference_panel");
}
$("> p", panel).text(ref);
panel.css({top:(window.pageYOffset+20)+"px"});
panel.css({top:(window.pageYOffset+20)+"px", left:(window.pageXOffset+20)+"px"});
panel.fadeIn(500);
}

View File

@ -482,6 +482,10 @@ class Py2App(object):
shutil.rmtree(tdir)
shutil.rmtree(os.path.join(self.site_packages, 'calibre', 'plugins'))
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
def add_modules_from_dir(self, src):
@ -551,6 +555,15 @@ class Py2App(object):
if dest2.endswith('.so'):
self.fix_dependencies_in_lib(dest2)
self.remove_bytecode(join(self.resources_dir, 'Python', 'lib'))
confdir = join(self.resources_dir, 'Python',
'lib/python%s/config'%self.version_info)
os.makedirs(confdir)
shutil.copy2(join(src, 'config/Makefile'), confdir)
incdir = join(self.resources_dir, 'Python',
'include/python'+self.version_info)
os.makedirs(incdir)
shutil.copy2(join(src.replace('/lib/', '/include/'), 'pyconfig.h'),
incdir)
@flush
def remove_bytecode(self, dest):

View File

@ -14,7 +14,7 @@ from setup.build_environment import msvc, MT, RC
from setup.installer.windows.wix import WixMixIn
OPENSSL_DIR = r'Q:\openssl'
QT_DIR = 'Q:\\Qt\\4.8.1'
QT_DIR = 'Q:\\Qt\\4.8.2'
QT_DLLS = ['Core', 'Gui', 'Network', 'Svg', 'WebKit', 'Xml', 'XmlPatterns']
QTCURVE = r'C:\plugins\styles'
LIBUNRAR = 'C:\\Program Files\\UnrarDLL\\unrar.dll'

View File

@ -97,7 +97,7 @@ Now, run configure and make::
-no-plugin-manifests is needed so that loading the plugins does not fail looking for the CRT assembly
configure -opensource -release -qt-zlib -qt-libmng -qt-libpng -qt-libtiff -qt-libjpeg -release -platform win32-msvc2008 -no-qt3support -webkit -xmlpatterns -no-phonon -no-style-plastique -no-style-cleanlooks -no-style-motif -no-style-cde -no-declarative -no-scripttools -no-audio-backend -no-multimedia -no-dbus -no-openvg -no-opengl -no-qt3support -confirm-license -nomake examples -nomake demos -nomake docs -no-plugin-manifests -openssl -I Q:\openssl\include -L Q:\openssl\lib && nmake
configure -opensource -release -ltcg -qt-zlib -qt-libmng -qt-libpng -qt-libtiff -qt-libjpeg -release -platform win32-msvc2008 -no-qt3support -webkit -xmlpatterns -no-phonon -no-style-plastique -no-style-cleanlooks -no-style-motif -no-style-cde -no-declarative -no-scripttools -no-audio-backend -no-multimedia -no-dbus -no-openvg -no-opengl -no-qt3support -confirm-license -nomake examples -nomake demos -nomake docs -no-plugin-manifests -openssl -I Q:\openssl\include -L Q:\openssl\lib && nmake
Add the path to the bin folder inside the Qt dir to your system PATH.

View File

@ -18,14 +18,14 @@ msgstr ""
"Report-Msgid-Bugs-To: Debian iso-codes team <pkg-isocodes-"
"devel@lists.alioth.debian.org>\n"
"POT-Creation-Date: 2011-11-25 14:01+0000\n"
"PO-Revision-Date: 2012-05-29 09:12+0000\n"
"Last-Translator: Moritz Höwer <moritzhoewermail@gmx.de>\n"
"PO-Revision-Date: 2012-06-10 11:16+0000\n"
"Last-Translator: SimonFS <simonschuette@arcor.de>\n"
"Language-Team: German <debian-l10n-german@lists.debian.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2012-05-30 05:20+0000\n"
"X-Generator: Launchpad (build 15316)\n"
"X-Launchpad-Export-Date: 2012-06-11 04:46+0000\n"
"X-Generator: Launchpad (build 15376)\n"
"Language: de\n"
#. name for aaa
@ -139,7 +139,7 @@ msgstr ""
#. name for abe
msgid "Abnaki; Western"
msgstr ""
msgstr "Abnaki; Westlich"
#. name for abf
msgid "Abai Sungai"

View File

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

View File

@ -1177,6 +1177,16 @@ class StoreAmazonKindleStore(StoreBase):
formats = ['KINDLE']
affiliate = True
class StoreSonyStore(StoreBase):
name = 'SONY Reader Store'
description = u'SONY Reader books.'
author = 'Kovid Goyal'
actual_plugin = 'calibre.gui2.store.stores.sony_plugin:SonyStore'
headquarters = 'US'
formats = ['SONY']
affiliate = False
class StoreAmazonDEKindleStore(StoreBase):
name = 'Amazon DE Kindle'
author = 'Charles Haley'
@ -1623,7 +1633,7 @@ plugins += [
StoreAmazonITKindleStore,
StoreAmazonUKKindleStore,
StoreBaenWebScriptionStore,
StoreBNStore,
StoreBNStore, StoreSonyStore,
StoreBeamEBooksDEStore,
StoreBeWriteStore,
StoreBiblioStore,

View File

@ -72,6 +72,7 @@ class ANDROID(USBMS):
# Sony Ericsson
0xfce : {
0xd12e : [0x0100],
0xe156 : [0x226],
0xe15d : [0x226],
0xe14f : [0x0226],
0x614f : [0x0226, 0x100],

View File

@ -53,6 +53,7 @@ class KF8Writer(object):
self.log('\tGenerating KF8 markup...')
self.dup_data()
self.cleanup_markup()
self.replace_resource_links()
self.extract_css_into_flows()
self.extract_svg_into_flows()
@ -89,6 +90,15 @@ class KF8Writer(object):
def data(self, item):
return self._data_cache.get(item.href, item.data)
def cleanup_markup(self):
for item in self.oeb.spine:
root = self.data(item)
# Remove empty script tags as they are pointless
for tag in XPath('//h:script')(root):
if not tag.text and not tag.get('src', False):
tag.getparent().remove(tag)
def replace_resource_links(self):
''' Replace links to resources (raster images/fonts) with pointers to
the MOBI record containing the resource. The pointers are of the form:

View File

@ -33,7 +33,8 @@ aid_able_tags = {'a', 'abbr', 'address', 'article', 'aside', 'audio', 'b',
'video'}
_self_closing_pat = re.compile(bytes(
r'<(?P<tag>%s)(?=[\s/])(?P<arg>[^>]*)/>'%('|'.join(aid_able_tags))),
r'<(?P<tag>%s)(?=[\s/])(?P<arg>[^>]*)/>'%('|'.join(aid_able_tags|{'script',
'style', 'title', 'head'}))),
re.IGNORECASE)
def close_self_closing_tags(raw):
@ -118,6 +119,7 @@ class Skeleton(object):
def render(self, root):
raw = tostring(root, xml_declaration=True)
raw = raw.replace(b'<html', bytes('<html xmlns="%s"'%XHTML_NS), 1)
raw = close_self_closing_tags(raw)
return raw
def calculate_metrics(self, root):
@ -372,6 +374,11 @@ class Chunker(object):
# the chunk immediately after
pos_fid = (chunk.sequence_number, 0, offset)
break
if chunk is self.chunk_table[-1]:
# This can happen for aids very close to the end of the the
# end of the text (https://bugs.launchpad.net/bugs/1011330)
pos_fid = (chunk.sequence_number, offset-chunk.insert_pos,
offset)
if pos_fid is None:
raise ValueError('Could not find chunk for aid: %r'%
match.group(1))

View File

@ -73,7 +73,7 @@ class TOCAdder(object):
id, href = oeb.manifest.generate('contents', 'contents.xhtml')
item = self.generated_item = oeb.manifest.add(id, href, XHTML_MIME,
data=root)
if opts.mobi_toc_at_start == 'end':
if self.at_start:
oeb.spine.insert(0, item, linear=True)
else:
oeb.spine.add(item, linear=False)

View File

@ -106,7 +106,8 @@ gprefs.defaults['auto_add_path'] = None
gprefs.defaults['auto_add_check_for_duplicates'] = False
gprefs.defaults['blocked_auto_formats'] = []
gprefs.defaults['auto_add_auto_convert'] = True
gprefs.defaults['widget_style'] = 'system'
gprefs.defaults['ui_style'] = 'calibre' if iswindows or isosx else 'system'
gprefs.defaults['tag_browser_old_look'] = False
# }}}
NONE = QVariant() #: Null value to return from the data function of item models
@ -782,7 +783,7 @@ class Application(QApplication):
font.setStretch(s)
QApplication.setFont(font)
if force_calibre_style or gprefs['widget_style'] != 'system':
if force_calibre_style or gprefs['ui_style'] != 'system':
self.load_calibre_style()
else:
st = self.style()

View File

@ -26,37 +26,56 @@ class SimilarBooksAction(InterfaceAction):
(_('Books in this series'), 'books_in_series.png', 'series',
_('Alt+Shift+S')),
(_('Books by this publisher'), 'publisher.png', 'publisher', _('Alt+P')),
(_('Books with the same tags'), 'tags.png', 'tag', _('Alt+T')),]:
(_('Books with the same tags'), 'tags.png', 'tags', _('Alt+T')),]:
ac = self.create_action(spec=(text, icon, None, shortcut),
attr=target)
m.addAction(ac)
ac.triggered.connect(partial(self.show_similar_books, target))
self.qaction.setMenu(m)
def show_similar_books(self, type, *args):
search, join = [], ' '
def show_similar_books(self, typ, *args):
idx = self.gui.library_view.currentIndex()
if not idx.isValid():
return
db = idx.model().db
row = idx.row()
if type == 'series':
series = idx.model().db.series(row)
if series:
search = ['series:"'+series+'"']
elif type == 'publisher':
publisher = idx.model().db.publisher(row)
if publisher:
search = ['publisher:"'+publisher+'"']
elif type == 'tag':
tags = idx.model().db.tags(row)
if tags:
search = ['tag:"='+t+'"' for t in tags.split(',')]
elif type in ('author', 'authors'):
authors = idx.model().db.authors(row)
if authors:
search = ['author:"='+a.strip().replace('|', ',')+'"' \
for a in authors.split(',')]
# Get the parameters for this search
col = db.prefs['similar_' + typ + '_search_key']
match = db.prefs['similar_' + typ + '_match_kind']
if match == 'match_all':
join = ' and '
else:
join = ' or '
# Get all the data for the current record
mi = db.get_metadata(row)
# Get the definitive field name to use for this search. If the field
# is a grouped search term, the function returns the list of fields that
# are to be searched, otherwise it returns the field name.
loc = db.field_metadata.search_term_to_field_key(icu_lower(col))
if isinstance(loc, list):
# Grouped search terms are a list of fields. Get all the values,
# pruning duplicates
val = set()
for f in loc:
v = mi.get(f, None)
if not v:
continue
if isinstance(v, list):
val.update(v)
else:
val.add(v)
else:
# Get the value of the requested field. Can be a list or a simple val
val = mi.get(col, None)
if not val:
return
if not isinstance(val, (list, set)):
val = [val]
search = [col + ':"='+t+'"' for t in val]
if search:
self.gui.search.set_search_string(join.join(search),
store_in_history=True)

View File

@ -25,11 +25,11 @@ class StoreAction(InterfaceAction):
self.qaction.triggered.connect(self.do_search)
self.store_menu = self.qaction.menu()
cm = partial(self.create_menu_action, self.store_menu)
for x, t in [('author', _('author')), ('title', _('title')),
('book', _('book'))]:
for x, t in [('author', _('this author')), ('title', _('this title')),
('book', _('this book'))]:
func = getattr(self, 'search_%s'%('author_title' if x == 'book'
else x))
ac = cm(x, _('Search for this %s')%t, triggered=func)
ac = cm(x, _('Search for %s')%t, triggered=func)
setattr(self, 'action_search_by_'+x, ac)
self.store_menu.addSeparator()
self.store_list_menu = self.store_menu.addMenu(_('Stores'))

View File

@ -238,10 +238,11 @@ class LayoutMixin(object): # {{{
# }}}
self.status_bar = StatusBar(self)
stylename = unicode(self.style().objectName())
for x in button_order:
button = getattr(self, x+'_splitter').button
button.setIconSize(QSize(24, 24))
if isosx:
if isosx and stylename != u'Calibre':
button.setStyleSheet('''
QToolButton { background: none; border:none; padding: 0px; }
QToolButton:checked { background: rgba(0, 0, 0, 25%); }

View File

@ -183,10 +183,17 @@ Author matching is exact.</string>
</item>
<item row="5" column="0">
<widget class="QGroupBox" name="groupBox">
<property name="title">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>Ignore files with the following extensions when automatically adding </string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QListWidget" name="opt_blocked_auto_formats">
<property name="alternatingRowColors">

View File

@ -101,9 +101,10 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
r('gui_layout', config, restart_required=True, choices=
[(_('Wide'), 'wide'), (_('Narrow'), 'narrow')])
r('widget_style', gprefs, restart_required=True, choices=
r('ui_style', gprefs, restart_required=True, choices=
[(_('System default'), 'system'), (_('Calibre style'),
'calibre')])
r('tag_browser_old_look', gprefs, restart_required=True)
r('cover_flow_queue_length', config, restart_required=True)

View File

@ -187,12 +187,12 @@
<string>User interface &amp;style (needs restart):</string>
</property>
<property name="buddy">
<cstring>opt_widget_style</cstring>
<cstring>opt_ui_style</cstring>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="opt_widget_style"/>
<widget class="QComboBox" name="opt_ui_style"/>
</item>
</layout>
</widget>
@ -312,6 +312,18 @@ Manage Authors. You can use the values {author} and
<string>Tag Browser</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_10">
<item row="3" column="2" colspan="3">
<widget class="MultiCompleteLineEdit" name="opt_categories_using_hierarchy">
<property name="toolTip">
<string>A comma-separated list of categories in which items containing
periods are displayed in the tag browser trees. For example, if
this box contains 'tags' then tags of the form 'Mystery.English'
and 'Mystery.Thriller' will be displayed with English and Thriller
both under 'Mystery'. If 'tags' is not in this box,
then the tags will be displayed each on their own line.</string>
</property>
</widget>
</item>
<item row="0" column="0" colspan="2">
<widget class="QLabel" name="label_9">
<property name="text">
@ -354,6 +366,19 @@ up into subcategories. If the partition method is set to disable, this value is
</property>
</widget>
</item>
<item row="5" column="0" colspan="5">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>690</width>
<height>252</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="2">
<widget class="QLabel" name="label_8111">
<property name="text">
@ -396,27 +421,9 @@ a few top-level elements.</string>
</widget>
</item>
<item row="4" column="0" colspan="5">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>690</width>
<height>252</height>
</size>
</property>
</spacer>
</item>
<item row="3" column="2" colspan="3">
<widget class="MultiCompleteLineEdit" name="opt_categories_using_hierarchy">
<property name="toolTip">
<string>A comma-separated list of categories in which items containing
periods are displayed in the tag browser trees. For example, if
this box contains 'tags' then tags of the form 'Mystery.English'
and 'Mystery.Thriller' will be displayed with English and Thriller
both under 'Mystery'. If 'tags' is not in this box,
then the tags will be displayed each on their own line.</string>
<widget class="QCheckBox" name="opt_tag_browser_old_look">
<property name="text">
<string>Use &amp;alternating row colors in the Tag Browser</string>
</property>
</widget>
</item>

View File

@ -5,6 +5,7 @@ __license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import textwrap
from calibre.gui2.preferences import ConfigWidgetBase, test_widget, Setting
from calibre.gui2.preferences.misc_ui import Ui_Form
@ -31,6 +32,11 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
r('worker_limit', config, restart_required=True, setting=WorkersSetting)
r('enforce_cpu_limit', config, restart_required=True)
r('worker_max_time', gprefs)
self.opt_worker_limit.setToolTip(textwrap.fill(
_('The maximum number of jobs that will run simultaneously in '
'the background. This refers to CPU intensive tasks like '
' conversion. Lower this number'
' if you want calibre to use less CPU.')))
self.device_detection_button.clicked.connect(self.debug_device_detection)
self.button_open_config_dir.clicked.connect(self.open_config_dir)
self.user_defined_device_button.clicked.connect(self.user_defined_device)

View File

@ -12,6 +12,7 @@ from calibre.gui2.preferences import ConfigWidgetBase, test_widget, \
from calibre.gui2.preferences.search_ui import Ui_Form
from calibre.gui2 import config, error_dialog
from calibre.utils.config import prefs
from calibre.utils.icu import sort_key
class ConfigWidget(ConfigWidgetBase, Ui_Form):
@ -56,7 +57,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
"can be useful to check for duplicates, to find which column contains "
"a particular item, or to have hierarchical categories (categories "
"that contain categories)."))
self.gst = db.prefs.get('grouped_search_terms', {})
self.gst = db.prefs.get('grouped_search_terms', {}).copy()
self.orig_gst_keys = self.gst.keys()
fl = []
@ -70,6 +71,18 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
self.gst_value.update_items_cache(fl)
self.fill_gst_box(select=None)
self.category_fields = fl
ml = [(_('Match any'), 'match_any'), (_('Match all'), 'match_all')]
r('similar_authors_match_kind', db.prefs, choices=ml)
r('similar_tags_match_kind', db.prefs, choices=ml)
r('similar_series_match_kind', db.prefs, choices=ml)
r('similar_publisher_match_kind', db.prefs, choices=ml)
self.set_similar_fields(initial=True)
self.similar_authors_search_key.currentIndexChanged[int].connect(self.something_changed)
self.similar_tags_search_key.currentIndexChanged[int].connect(self.something_changed)
self.similar_series_search_key.currentIndexChanged[int].connect(self.something_changed)
self.similar_publisher_search_key.currentIndexChanged[int].connect(self.something_changed)
self.gst_delete_button.setEnabled(False)
self.gst_save_button.setEnabled(False)
self.gst_names.currentIndexChanged[int].connect(self.gst_index_changed)
@ -86,6 +99,34 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
self.opt_grouped_search_make_user_categories.editingFinished.connect(
self.muc_box_changed)
def set_similar_fields(self, initial=False):
self.set_similar('similar_authors_search_key', initial=initial)
self.set_similar('similar_tags_search_key', initial=initial)
self.set_similar('similar_series_search_key', initial=initial)
self.set_similar('similar_publisher_search_key', initial=initial)
def set_similar(self, name, initial=False):
field = getattr(self, name)
if not initial:
val = field.currentText()
else:
val = self.db.prefs[name]
field.blockSignals(True)
field.clear()
choices = []
choices.extend(self.category_fields)
choices.extend(sorted(self.gst.keys(), key=sort_key))
field.addItems(choices)
dex = field.findText(val)
if dex >= 0:
field.setCurrentIndex(dex)
else:
field.setCurrentIndex(0)
field.blockSignals(False)
def something_changed(self, dex):
self.changed_signal.emit()
def muc_box_changed(self):
self.muc_changed = True
@ -121,6 +162,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
self.gst_changed = True
self.gst[name] = val
self.fill_gst_box(select=name)
self.set_similar_fields(initial=False)
self.changed_signal.emit()
def gst_delete_clicked(self):
@ -133,9 +175,10 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
self.fill_gst_box(select='')
self.changed_signal.emit()
self.gst_changed = True
self.set_similar_fields(initial=False)
def fill_gst_box(self, select=None):
terms = sorted(self.gst.keys())
terms = sorted(self.gst.keys(), key=sort_key)
self.opt_grouped_search_make_user_categories.update_items_cache(terms)
self.gst_names.blockSignals(True)
self.gst_names.clear()
@ -168,6 +211,14 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
if self.gst_changed:
self.db.prefs.set('grouped_search_terms', self.gst)
self.db.field_metadata.add_grouped_search_terms(self.gst)
self.db.prefs.set('similar_authors_search_key',
unicode(self.similar_authors_search_key.currentText()))
self.db.prefs.set('similar_tags_search_key',
unicode(self.similar_tags_search_key.currentText()))
self.db.prefs.set('similar_series_search_key',
unicode(self.similar_series_search_key.currentText()))
self.db.prefs.set('similar_publisher_search_key',
unicode(self.similar_publisher_search_key.currentText()))
return ConfigWidgetBase.commit(self)
def refresh_gui(self, gui):

View File

@ -201,6 +201,101 @@ to be shown as user categories</string>
</layout>
</widget>
</item>
<item row="5" column="0">
<widget class="QGroupBox" name="groupBox22">
<property name="title">
<string>What to search when searching similar books</string>
</property>
<layout class="QGridLayout" name="gridLayout_22">
<item row="0" column="0" colspan="6">
<widget class="QLabel" name="label">
<property name="text">
<string>&lt;p&gt;When you search for similar books by right clicking the
book and selecting "Similar books...",
calibre constructs a search using the column lookup names specified below.
By changing the lookup name to a grouped search term you can
search multiple columns at once.&lt;/p&gt;</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_221">
<property name="text">
<string>Similar authors: </string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="similar_authors_search_key">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>10</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QComboBox" name="opt_similar_authors_match_kind">
</widget>
</item>
<item row="1" column="3">
<widget class="QLabel" name="label_222">
<property name="text">
<string>Similar series: </string>
</property>
</widget>
</item>
<item row="1" column="4">
<widget class="QComboBox" name="similar_series_search_key">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>10</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="1" column="5">
<widget class="QComboBox" name="opt_similar_series_match_kind">
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_223">
<property name="text">
<string>Similar tags: </string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="similar_tags_search_key">
</widget>
</item>
<item row="2" column="2">
<widget class="QComboBox" name="opt_similar_tags_match_kind">
</widget>
</item>
<item row="2" column="3">
<widget class="QLabel" name="label_224">
<property name="text">
<string>Similar publishers: </string>
</property>
</widget>
</item>
<item row="2" column="4">
<widget class="QComboBox" name="similar_publisher_search_key">
</widget>
</item>
<item row="2" column="5">
<widget class="QComboBox" name="opt_similar_publisher_match_kind">
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<customwidgets>

View File

@ -388,3 +388,14 @@ class SearchDialog(QDialog, Ui_Dialog):
self.do_search()
return QDialog.exec_(self)
if __name__ == '__main__':
from calibre.gui2 import Application
from calibre.gui2.preferences.main import init_gui
import sys
app = Application([])
app
gui = init_gui()
s = SearchDialog(gui, query=' '.join(sys.argv[1:]))
s.exec_()

View File

@ -8,7 +8,7 @@ __docformat__ = 'restructuredtext en'
import random
import re
import urllib2
import urllib
from contextlib import closing
from lxml import html
@ -47,7 +47,7 @@ class EbookscomStore(BasicStoreConfig, StorePlugin):
d.exec_()
def search(self, query, max_results=10, timeout=60):
url = 'http://www.ebooks.com/SearchApp/SearchResults.net?term=' + urllib2.quote(query)
url = 'http://www.ebooks.com/SearchApp/SearchResults.net?term=' + urllib.quote_plus(query)
br = browser()
@ -66,15 +66,12 @@ class EbookscomStore(BasicStoreConfig, StorePlugin):
cover_url = ''.join(data.xpath('.//div[@class="img"]//img/@src'))
title = ''
author = ''
header_parts = data.xpath('.//div[@class="descr"]/h4//a//text()')
if header_parts:
title = header_parts[0]
header_parts = header_parts[1:]
if header_parts:
author = ', '.join(header_parts)
title = ''.join(data.xpath(
'descendant::span[@class="book-title"]/a/text()')).strip()
author = ''.join(data.xpath(
'descendant::span[@class="author"]/a/text()')).strip()
if not title or not author:
continue
counter -= 1

View File

@ -45,7 +45,7 @@ class GutenbergStore(BasicStoreConfig, StorePlugin):
counter = max_results
with closing(br.open(url, timeout=timeout)) as f:
doc = html.fromstring(f.read())
for data in doc.xpath('//ol[@class="results"]//li[contains(@class, "icon_title") and not(contains(@class, "toplink"))]'):
for data in doc.xpath('//ol[@class="results"]/li[@class="booklink"]'):
if counter <= 0:
break

View File

@ -0,0 +1,84 @@
#!/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'
import urllib
from contextlib import closing
from lxml import html, etree
from PyQt4.Qt import QUrl
from calibre import browser, url_slash_cleaner
from calibre.gui2 import open_url
from calibre.gui2.store import StorePlugin
from calibre.gui2.store.basic_config import BasicStoreConfig
from calibre.gui2.store.search_result import SearchResult
from calibre.gui2.store.web_store_dialog import WebStoreDialog
class SonyStore(BasicStoreConfig, StorePlugin):
def open(self, parent=None, detail_item=None, external=False):
if detail_item:
if external or self.config.get('open_external', False):
open_url(QUrl(url_slash_cleaner(detail_item)))
else:
d = WebStoreDialog(self.gui, 'http://ebookstore.sony.com', parent, detail_item)
d.setWindowTitle(self.name)
d.set_tags(self.config.get('tags', ''))
d.exec_()
def search(self, query, max_results=10, timeout=60):
url = 'http://ebookstore.sony.com/search?keyword=%s'%urllib.quote_plus(
query)
br = browser()
counter = max_results
with closing(br.open(url, timeout=timeout)) as f:
doc = html.fromstring(f.read())
for item in doc.xpath('//div[contains(@class, "searchResult")]/'
'descendant::li[contains(@class, "hreview")]'):
if counter <= 0:
break
curr = ''.join(item.xpath('descendant::div[@class="pricing"]/descendant::*[@class="currency"]/@title')).strip()
amt = ''.join(item.xpath('descendant::div[@class="pricing"]/descendant::*[@class="amount"]/text()')).strip()
s = SearchResult()
s.price = (curr+' '+amt) if (curr and amt) else _('Not Available')
title = item.xpath('descendant::h3[@class="item"]')
if not title: continue
title = etree.tostring(title[0], method='text',
encoding=unicode)
if not title: continue
s.title = title.strip()
s.author = ''.join(item.xpath(
'descendant::li[contains(@class, "author")]/'
'a[@class="fn"]/text()')).strip()
if not s.author: continue
detail_url = ''.join(item.xpath('descendant::h3[@class="item"]'
'/descendant::a[@class="fn" and @href]/@href'))
if not detail_url: continue
s.detail_item = detail_url
counter -= 1
cover_url = ''.join(item.xpath(
'descendant::li[@class="coverart"]/'
'descendant::img[@src]/@src'))
if cover_url:
if cover_url.startswith('//'):
cover_url = 'http:' + cover_url
elif cover_url.startswith('/'):
cover_url = 'http://ebookstore.sony.com'+cover_url
s.cover_url = url_slash_cleaner(cover_url)
s.drm = SearchResult.DRM_UNKNOWN
s.formats = 'Sony'
yield s

View File

@ -40,39 +40,35 @@ class VirtualoStore(BasicStoreConfig, StorePlugin):
url = 'http://virtualo.pl/?q=' + urllib.quote(query) + '&f=format_id:4,6,3'
br = browser()
drm_pattern = re.compile("ADE")
no_drm_pattern = re.compile("Znak wodny")
counter = max_results
with closing(br.open(url, timeout=timeout)) as f:
doc = html.fromstring(f.read())
for data in doc.xpath('//div[@id="product_list"]/div/div[@class="column"]'):
for data in doc.xpath('//div[@id="content"]//div[@class="list_box list_box_border"]'):
if counter <= 0:
break
id = ''.join(data.xpath('.//table/tr[1]/td[1]/a/@href'))
id = ''.join(data.xpath('.//div[@class="list_middle_left"]//a/@href'))
if not id:
continue
price = ''.join(data.xpath('.//span[@class="price"]/text() | .//span[@class="price abbr"]/text()'))
cover_url = ''.join(data.xpath('.//table/tr[1]/td[1]/a/img/@src'))
title = ''.join(data.xpath('.//div[@class="title"]/a/text()'))
title = re.sub(r'\ WM', '', title)
author = ', '.join(data.xpath('.//div[@class="authors"]/a/text()'))
formats = ', '.join(data.xpath('.//span[@class="format"]/a/text()'))
formats = re.sub(r'(, )?ONLINE(, )?', '', formats)
drm = drm_pattern.search(formats)
formats = re.sub(r'(, )?ADE(, )?', '', formats)
formats = re.sub(r'\ WM', '', formats)
cover_url = ''.join(data.xpath('.//div[@class="list_middle_left"]//a/img/@src'))
title = ''.join(data.xpath('.//div[@class="list_title list_text_left"]/a/text()'))
author = ', '.join(data.xpath('.//div[@class="list_authors list_text_left"]/a/text()'))
formats = [ form.split('_')[-1].replace('.png', '') for form in data.xpath('.//div[@style="width:55%;float:left;text-align:left;height:18px;"]//img/@src')]
nodrm = no_drm_pattern.search(''.join(data.xpath('.//div[@style="width:45%;float:right;text-align:right;height:18px;"]/div/div/text()')))
counter -= 1
s = SearchResult()
s.cover_url = cover_url.split('.jpg')[0] + '.jpg'
s.title = title.strip() + ' ' + formats
s.title = title.strip()
s.author = author.strip()
s.price = price + ''
s.detail_item = 'http://virtualo.pl' + id.strip().split('http://')[0]
s.formats = formats.upper().strip()
s.drm = SearchResult.DRM_LOCKED if drm else SearchResult.DRM_UNLOCKED
s.formats = ', '.join(formats).upper()
s.drm = SearchResult.DRM_UNLOCKED if nodrm else SearchResult.DRM_UNKNOWN
yield s

View File

@ -22,16 +22,26 @@ from calibre.utils.icu import sort_key
class TagDelegate(QStyledItemDelegate): # {{{
def __init__(self, *args, **kwargs):
QStyledItemDelegate.__init__(self, *args, **kwargs)
self.old_look = gprefs['tag_browser_old_look']
def paint(self, painter, option, index):
item = index.data(Qt.UserRole).toPyObject()
QStyledItemDelegate.paint(self, painter, option, index)
widget = self.parent()
style = QApplication.style() if widget is None else widget.style()
self.initStyleOption(option, index)
if item.boxed:
r = style.subElementRect(style.SE_ItemViewItemFocusRect, option,
widget)
painter.save()
painter.drawLine(r.bottomLeft(), r.bottomRight())
painter.restore()
if item.type != TagTreeItem.TAG:
return
if (item.tag.state == 0 and config['show_avg_rating'] and
item.tag.avg_rating is not None):
self.initStyleOption(option, index)
widget = self.parent()
style = QApplication.style() if widget is None else widget.style()
r = style.subElementRect(style.SE_ItemViewItemDecoration,
option, widget)
icon = option.icon
@ -40,7 +50,12 @@ class TagDelegate(QStyledItemDelegate): # {{{
nr = r.adjusted(0, 0, 0, 0)
nr.setBottom(r.bottom()-int(r.height()*(rating/5.0)))
painter.setClipRect(nr)
painter.fillRect(r, widget.palette().window())
bg = option.palette.window()
if self.old_look:
bg = (option.palette.alternateBase() if
option.features&option.Alternate else
option.palette.base())
painter.fillRect(r, bg)
style.proxy().drawPrimitive(style.PE_PanelItemViewItem, option,
painter, widget)
painter.setOpacity(0.3)
@ -48,6 +63,7 @@ class TagDelegate(QStyledItemDelegate): # {{{
icon.On)
painter.restore()
# }}}
class TagsView(QTreeView): # {{{
@ -101,13 +117,14 @@ class TagsView(QTreeView): # {{{
self._model.user_categories_edited.connect(self.user_categories_edited,
type=Qt.QueuedConnection)
self._model.drag_drop_finished.connect(self.drag_drop_finished)
self.setStyleSheet('''
stylish_tb = '''
QTreeView {
background-color: palette(window);
color: palette(text);
color: palette(window-text);
border: none;
}
'''
self.setStyleSheet('''
QTreeView::item {
border: 1px solid transparent;
padding-top:0.9ex;
@ -119,7 +136,9 @@ class TagsView(QTreeView): # {{{
border: 1px solid #bfcde4;
border-radius: 6px;
}
''')
''' + ('' if gprefs['tag_browser_old_look'] else stylish_tb))
if gprefs['tag_browser_old_look']:
self.setAlternatingRowColors(True)
@property
def hidden_categories(self):

View File

@ -532,6 +532,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
if self.content_server is not None:
self.content_server.set_database(db)
self.library_path = newloc
prefs['library_path'] = self.library_path
self.book_on_device(None, reset=True)
db.set_book_on_device_func(self.book_on_device)
self.library_view.set_database(db)
@ -541,7 +542,6 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
self.search.clear()
self.saved_search.clear()
self.book_details.reset_info()
prefs['library_path'] = self.library_path
#self.library_view.model().count_changed()
db = self.library_view.model().db
self.iactions['Choose Library'].count_changed(db.count())

View File

@ -20,7 +20,7 @@ class TOCView(QTreeView):
self.setStyleSheet('''
QTreeView {
background-color: palette(window);
color: palette(text);
color: palette(window-text);
border: none;
}
QTreeView::item {

View File

@ -21,7 +21,7 @@ from calibre.gui2 import (NONE, error_dialog, pixmap_to_data, gprefs,
from calibre.gui2.filename_pattern_ui import Ui_Form
from calibre import fit_image
from calibre.ebooks import BOOK_EXTENSIONS
from calibre.utils.config import prefs, XMLConfig, tweaks
from calibre.utils.config import prefs, XMLConfig
from calibre.gui2.progress_indicator import ProgressIndicator as _ProgressIndicator
from calibre.gui2.dnd import (dnd_has_image, dnd_get_image, dnd_get_files,
IMAGE_EXTENSIONS, dnd_has_extension, DownloadDialog)
@ -1000,13 +1000,6 @@ class SplitterHandle(QSplitterHandle):
if oh != self.highlight:
self.update()
def paintEvent(self, ev):
QSplitterHandle.paintEvent(self, ev)
if self.highlight and tweaks['draw_hidden_section_indicators']:
painter = QPainter(self)
painter.setClipRect(ev.rect())
painter.fillRect(self.rect(), Qt.yellow)
def mouseDoubleClickEvent(self, ev):
self.double_clicked.emit(self)

View File

@ -574,6 +574,9 @@ def command_set_metadata(args, dbpath):
if len(args) > 2:
opf = args[2]
if not os.path.exists(opf):
prints(_('The OPF file %s does not exist')%opf, file=sys.stderr)
return 1
do_set_metadata(db, book_id, opf)
if opts.field:

View File

@ -236,6 +236,14 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
defs['categories_using_hierarchy'] = []
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'
# Migrate the bool tristate tweak
defs['bools_are_tristate'] = \

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