diff --git a/resources/content_server/browse/browse.html b/resources/content_server/browse/browse.html index ef312334d9..6d4c79c4c9 100644 --- a/resources/content_server/browse/browse.html +++ b/resources/content_server/browse/browse.html @@ -4,7 +4,7 @@ - ..:: calibre library ::.. {title} + ..:: calibre {library} ::.. {title} @@ -41,7 +41,7 @@

→ home ←

+ >→ {home} ←

 
diff --git a/resources/default_tweaks.py b/resources/default_tweaks.py index 0f570bab40..692349283f 100644 --- a/resources/default_tweaks.py +++ b/resources/default_tweaks.py @@ -211,3 +211,9 @@ generate_cover_title_font = None # Absolute path to a TTF font file to use as the font for the footer in the # default cover generate_cover_foot_font = None + + +# Behavior of doubleclick on the books list. Choices: +# open_viewer, do_nothing, edit_cell. Default: open_viewer. +# Example: doubleclick_on_library_view = 'do_nothing' +doubleclick_on_library_view = 'open_viewer' diff --git a/resources/images/news/avto-magazin.png b/resources/images/news/avto-magazin.png new file mode 100644 index 0000000000..4a416652f2 Binary files /dev/null and b/resources/images/news/avto-magazin.png differ diff --git a/resources/images/news/dnevnik.png b/resources/images/news/dnevnik.png new file mode 100644 index 0000000000..a7e3da4595 Binary files /dev/null and b/resources/images/news/dnevnik.png differ diff --git a/resources/images/news/siol.png b/resources/images/news/siol.png new file mode 100644 index 0000000000..e04283474b Binary files /dev/null and b/resources/images/news/siol.png differ diff --git a/resources/recipes/avto-magazin.recipe b/resources/recipes/avto-magazin.recipe new file mode 100644 index 0000000000..6464588acc --- /dev/null +++ b/resources/recipes/avto-magazin.recipe @@ -0,0 +1,46 @@ +__license__ = 'GPL v3' +__copyright__ = '2010, BlonG' +''' +avto-magazin.si +''' +from calibre.web.feeds.news import BasicNewsRecipe +class Dnevnik(BasicNewsRecipe): + title = u'Avto Magazin' + __author__ = u'BlonG' + description = u'Za avtomobilisti\xc4\x8dne frike, poznavalce in nedeljske \xc5\xa1oferje.' + oldest_article = 7 + max_articles_per_feed = 20 + labguage = 'sl' + no_stylesheets = True + use_embedded_content = False + + conversion_options = {'linearize_tables' : True} + + + cover_url = 'https://sites.google.com/site/javno2010/home/avto_magazin_cover.jpg' + + extra_css = ''' + h1{font-family:Arial,Helvetica,sans-serif; font-weight:bold;font-size:large;} + h2{font-family:Arial,Helvetica,sans-serif; font-weight:bold;font-size:large;} + p{font-family:Arial,Helvetica,sans-serif;font-size:small;} + body{font-family:Helvetica,Arial,sans-serif;font-size:small;} + ''' + + keep_only_tags = [ + dict(name='div', attrs={'id':'_iprom_inStream'}), +# dict(name='div', attrs={'class':'entry-content'}), + ] + + remove_tags = [ + dict(name='div', attrs={'id':'voteConfirmation'}), + dict(name='div', attrs={'id':'InsideVote'}), + dict(name='div', attrs={'class':'Zone234'}), + dict(name='div', attrs={'class':'Comments'}), + dict(name='div', attrs={'class':'sorodneNovice'}), + dict(name='div', attrs={'id':'footer'}), + ] + + + feeds = [ + (u'Novice', u'http://www.avto-magazin.si/rss/') + ] diff --git a/resources/recipes/danas.recipe b/resources/recipes/danas.recipe index 2849723c04..9002a6b505 100644 --- a/resources/recipes/danas.recipe +++ b/resources/recipes/danas.recipe @@ -25,7 +25,7 @@ class Danas(BasicNewsRecipe): remove_empty_feeds = True 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)} - .article_description,body,.lokacija{font-family: Tahoma,Arial,Helvetica,sans1,sans-serif} + .article,.articledescription,body,.lokacija,.feed{font-family: Tahoma,Arial,Helvetica,sans1,sans-serif} .nadNaslov,h1,.preamble{font-family: Georgia,"Times New Roman",Times,serif1,serif} .antrfileText{border-left: 2px solid #999999; margin-left: 0.8em; @@ -66,7 +66,7 @@ class Danas(BasicNewsRecipe): keep_only_tags = [dict(name='div', attrs={'id':'left'})] remove_tags = [ - dict(name='div', attrs={'class':['width_1_4','metaClanka','baner']}) + dict(name='div', attrs={'class':['width_1_4','metaClanka','baner','listaVesti','article_nav']}) ,dict(name='div', attrs={'id':'comments'}) ,dict(name=['object','link','iframe','meta']) ] diff --git a/resources/recipes/diario_sport.recipe b/resources/recipes/diario_sport.recipe new file mode 100644 index 0000000000..8c7181098b --- /dev/null +++ b/resources/recipes/diario_sport.recipe @@ -0,0 +1,42 @@ +from calibre.web.feeds.news import BasicNewsRecipe + +class DiarioSport(BasicNewsRecipe): + title = u'Diario Sport' + oldest_article = 2 + max_articles_per_feed = 75 + __author__ = 'Jefferson Frantz' + description = 'Todas las noticias del Barça y del mundo del deporte en general' + timefmt = ' [%d %b, %Y]' + language = 'es' + no_stylesheets = True + + feeds = [(u'Sport', u'http://feeds.feedburner.com/sport/ultimahora')] + + extra_css = ''' + h2{font-family: serif; font-size: small; font-weight: bold; color: #000000; text-align: justify} + ''' + + keep_only_tags = [dict(name='div', attrs={'id':['noticiasMedio']})] + + remove_tags = [ + dict(name=['object','link','script','ul']) + ,dict(name='div', attrs={'id':['scrAdSense','herramientas2','participacion','participacion2','bloque1resultados','bloque2resultados','cont_vinyetesAnt','tinta','noticiasSuperior','cintillopublicidad2']}) + ,dict(name='p', attrs={'class':['masinformacion','hora']}) + ,dict(name='a', attrs={'class':["'link'"]}) + ,dict(name='div', attrs={'class':['addthis_toolbox addthis_default_style','firma','pretitularnoticia']}) + ,dict(name='form', attrs={'id':['formularioDeBusquedaAvanzada']}) + ] + + def preprocess_html(self, soup): + for item in soup.findAll(style=True): + del item['style'] + return soup + + + def postprocess_html(self, soup, first_fetch): + img = soup.find('img',src='/img/videos/mascaravideo.png') + if not img is None: + img.extract() + + return soup + diff --git a/resources/recipes/dnevnik.recipe b/resources/recipes/dnevnik.recipe new file mode 100644 index 0000000000..75610e2e8f --- /dev/null +++ b/resources/recipes/dnevnik.recipe @@ -0,0 +1,63 @@ +__license__ = 'GPL v3' +__copyright__ = '2010, BlonG' +''' +dnevnik.si +''' +from calibre.web.feeds.news import BasicNewsRecipe +class Dnevnik(BasicNewsRecipe): + title = u'Dnevnik.si' + __author__ = u'BlonG' + description = u'''Dnevnik je \u010dasnik z ve\u010d kot polstoletno zgodovino. + Pod sloganom \xbb\u017divljenje ima besedo\xab na svojih straneh prina\u0161a + bralcem bogastvo informacij, komentarjev in kolumen in raznovrstnost + pogledov, zaznamovanih z odgovornostjo do posameznika in \u0161ir\u0161e + dru\u017ebe.''' + oldest_article = 3 + max_articles_per_feed = 20 + language = 'sl' + no_stylesheets = True + use_embedded_content = False + + cover_url = 'https://sites.google.com/site/javno2010/home/dnevnik_cover.jpg' + + extra_css = ''' + h1{font-family:Arial,Helvetica,sans-serif; font-weight:bold;font-size:large;} + h2{font-family:Arial,Helvetica,sans-serif; font-weight:normal;font-size:small;} + p{font-family:Arial,Helvetica,sans-serif;font-size:small;} + body{font-family:Helvetica,Arial,sans-serif;font-size:small;} + ''' + + keep_only_tags = [ + dict(name='div', attrs={'id':'_iprom_inStream'}), + dict(name='div', attrs={'class':'entry-content'}), + ] + + remove_tags = [ + dict(name='div', attrs={'class':'fb_article_top'}), + dict(name='div', attrs={'class':'related'}), + dict(name='div', attrs={'class':'fb_article_foot'}), + dict(name='div', attrs={'class':'spreading'}), + dict(name='dl', attrs={'class':'ad'}), + dict(name='p', attrs={'class':'report'}), + dict(name='div', attrs={'class':'hfeed comments'}), + dict(name='dl', attrs={'id':'entryPanel'}), + dict(name='dl', attrs={'class':'infopush ip_wide'}), + dict(name='div', attrs={'class':'sidebar'}), + dict(name='dl', attrs={'class':'bottom'}), + dict(name='div', attrs={'id':'footer'}), + ] + + + feeds = [ + (u'Slovenija', u'http://www.dnevnik.si/rss/?articleType=1&articleSection=13') + ,(u'Svet', u'http://www.dnevnik.si/rss/?articleType=1&articleSection=14') + ,(u'EU', u'http://www.dnevnik.si/rss/?articleType=1&articleSection=116') + ,(u'Poslovni dnevnik', u'http://www.dnevnik.si/rss/?articleType=1&articleSection=5') + ,(u'Kronika', u'http://www.dnevnik.si/rss/?articleType=1&articleSection=15') + ,(u'Kultura', u'http://www.dnevnik.si/rss/?articleType=1&articleSection=17') + ,(u'Zdravje', u'http://www.dnevnik.si/rss/?articleType=1&articleSection=18') + ,(u'Znanost in IT', u'http://www.dnevnik.si/rss/?articleType=1&articleSection=19') + ,(u'(Ne)verjetno', u'http://www.dnevnik.si/rss/?articleType=1&articleSection=20') + ,(u'E-strada', u'http://www.dnevnik.si/rss/?articleType=1&articleSection=21') + ,(u'Svet vozil', u'http://www.dnevnik.si/rss/?articleType=1&articleSection=22') + ] diff --git a/resources/recipes/hola.recipe b/resources/recipes/hola.recipe new file mode 100644 index 0000000000..1ea698228d --- /dev/null +++ b/resources/recipes/hola.recipe @@ -0,0 +1,38 @@ +#!/usr/bin/env python +__license__ = 'GPL v3' +__copyright__ = '2010, Brendan Sleight ' +''' +hola.com +''' + +from calibre.web.feeds.news import BasicNewsRecipe + +class Hackaday(BasicNewsRecipe): + title = u'Hola' + __author__ = 'bmsleight' + description = 'diario de actualidad, moda y belleza.' + oldest_article = 10 + max_articles_per_feed = 100 + no_stylesheets = True + language = 'es' + + use_embedded_content = False + + keep_only_tags = [ + dict(name='div', attrs={'id':'cuerpo'}) + ] + + feeds = [ + (u'Famosos' , u'http://www.hola.com/famosos/rss.xml' ), + (u'Realeza' , u'http://www.hola.com/realeza/rss.xml' ), + (u'Cine' , u'http://www.hola.com/cine/rss.xml' ), + (u'Música' , u'http://www.hola.com/musica/rss.xml' ), + (u'Moda y modelos' , u'http://www.hola.com/moda/portada/rss.xml' ), + (u'Belleza y salud', u'http://www.hola.com/belleza/portada/rss.xml' ), + (u'Niños' , u'http://www.hola.com/ninos/rss.xml' ), + (u'Todas las noticias', u'http://int2.hola.com/app/feeds/rss_hola.php'), + ] + + def get_article_url(self, article): + url = article.get('guid', None) + return url diff --git a/resources/recipes/mmc_rtv.recipe b/resources/recipes/mmc_rtv.recipe new file mode 100644 index 0000000000..a0d9adbe29 --- /dev/null +++ b/resources/recipes/mmc_rtv.recipe @@ -0,0 +1,57 @@ +__license__ = 'GPL v3' +__copyright__ = '2010, BlonG' +''' +www.rtvslo.si +''' +from calibre.web.feeds.news import BasicNewsRecipe + +class MMCRTV(BasicNewsRecipe): + title = u'MMC RTV Slovenija' + __author__ = u'BlonG' + description = u"Prvi interaktivni multimedijski portal, MMC RTV Slovenija" + oldest_article = 3 + max_articles_per_feed = 20 + language = 'sl' + no_stylesheets = True + use_embedded_content = False + + cover_url = 'https://sites.google.com/site/javno2010/home/rtv_slo_cover.jpg' + + extra_css = ''' + h1{font-family:Arial,Helvetica,sans-serif; font-weight:bold;font-size:large;} + h2{font-family:Arial,Helvetica,sans-serif; font-weight:normal;font-size:small;} + p{font-family:Arial,Helvetica,sans-serif;font-size:small;} + body{font-family:Helvetica,Arial,sans-serif;font-size:small;} + ''' + + def print_version(self, url): + split_url = url.split("/") + print_url = 'http://www.rtvslo.si/index.php?c_mod=news&op=print&id=' + split_url[-1] + return print_url + + keep_only_tags = [ + dict(name='div', attrs={'class':'title'}), + dict(name='div', attrs={'id':'newsbody'}), + dict(name='div', attrs={'id':'newsblocks'}), + ] +# remove_tags=[ +# 40 dict(name='div', attrs={'id':'newsblocks'}), +# ] + + feeds = [ + (u'Slovenija', u'http://www.rtvslo.si/feeds/01.xml'), + (u'Svet', u'http://www.rtvslo.si/feeds/02.xml'), + (u'Evropska unija', u'http://www.rtvslo.si/feeds/16.xml'), + (u'Gospodarstvo', u'http://www.rtvslo.si/feeds/04.xml'), + (u'\u010crna kronika', u'http://www.rtvslo.si/feeds/08.xml'), + (u'Okolje', u'http://www.rtvslo.si/feeds/12.xml'), + (u'Znanost in tehnologija', u'http://www.rtvslo.si/feeds/09.xml'), + (u'Zabava', u'http://www.rtvslo.si/feeds/06.xml'), + (u'Ture avanture', u'http://www.rtvslo.si/feeds/28.xml'), + ] + +# def preprocess_html(self, soup): +# newsblocks = soup.find('div',attrs = ['id':'newsblocks']) +# soup.find('div', attrs = {'id':'newsbody'}).insert(-1, newsblocks) +# return soup + diff --git a/resources/recipes/scprint.recipe b/resources/recipes/scprint.recipe new file mode 100644 index 0000000000..86b1bb951e --- /dev/null +++ b/resources/recipes/scprint.recipe @@ -0,0 +1,73 @@ +from calibre.web.feeds.news import BasicNewsRecipe, LoginFailed + +class SCPrintMagazine(BasicNewsRecipe): + title = u'SC Print Magazine' + __author__ = u'Tony Maro' + description = u'Last print version of the data security magazine' + INDEX = "http://www.scmagazineus.com/issuearchive/" + no_stylesheets = True + language = 'en' + keep_only_tags = [dict(id=['article','review'])] + remove_tags = [dict(id=['articlePrintTools','reviewBodyColumn'])] + LOG_IN = 'http://www.scmagazineus.com/login/' + tags = 'News,SC Magazine' + needs_subscription = True + + def parse_index(self): + articles = [] + issuelink = printsections = None + + soup = self.index_to_soup(self.INDEX) + sectit = soup.find('div', attrs={'class':'issueArchiveItem'}) + if sectit is not None: + linkt = sectit.find('a') + issuelink = linkt['href'] + imgt = sectit.find('img') + self.cover_url = imgt['src'] + + if issuelink is not None: + issue = self.index_to_soup(issuelink) + if issue is not None: + printsections = issue.findAll('div',attrs={'class':'PrintSection'}) + if printsections is not None: + for printsection in printsections: + onesection = [] + sectiontitle = printsection.find('h3').contents[0] + articlesec = printsection.findAll('div',attrs={'class':'IssueArchiveFormat'}) + if articlesec is not None: + ''' got articles ''' + for onearticle in articlesec: + ''' process one article ''' + arttitlet = onearticle.find('h3') + if arttitlet is not None: + mylink = arttitlet.find('a') + if mylink is not None: + if mylink.has_key('title'): + arttitle = mylink['title'] + else: + arttitle = 'unknown' + if mylink.has_key('href'): + artlink = mylink['href'] + artlink = artlink.replace("/article","/printarticle") + artlink = artlink.replace("/review","/printreview") + deck = onearticle.find('div',attrs={'class':'deck'}) + if deck is not None: + deck = deck.contents[0] + onesection.append({'title':arttitle, 'url':artlink, 'description':deck,'date':''}) + articles.append((sectiontitle, onesection)) + + return articles + + def get_browser(self): + br = BasicNewsRecipe.get_browser(self) + br.open(self.LOG_IN) + br.select_form(name='aspnetForm') + br['ctl00$ctl00$cphAllPageContent$cphMainContent$SubscriberEasyLoginView1$txtEmail'] = self.username + br['ctl00$ctl00$cphAllPageContent$cphMainContent$SubscriberEasyLoginView1$txtPassword'] = self.password + raw = br.submit("ctl00$ctl00$cphAllPageContent$cphMainContent$SubscriberEasyLoginView1$btnLogin").read() + if 'Logout' not in raw: + raise LoginFailed( + _('Failed to log in, check your username and password for' + ' the calibre Periodicals service.')) + return br + diff --git a/resources/recipes/siol.recipe b/resources/recipes/siol.recipe new file mode 100644 index 0000000000..1dd9c4bb55 --- /dev/null +++ b/resources/recipes/siol.recipe @@ -0,0 +1,55 @@ +# coding: utf-8 +__license__ = 'GPL v3' +__copyright__ = '2010, BlonG' +''' +www.siol.si +''' +from calibre.web.feeds.news import BasicNewsRecipe +class Siol(BasicNewsRecipe): + title = u'Siol.net' + __author__ = u'BlonG' + description = "Multimedijski portal z aktualnimi vsebinami, intervjuji, komentarji iz Slovenije in sveta, sportal, trendi, avtomoto, blogos" + oldest_article = 3 + language = 'sl' + max_articles_per_feed = 20 + no_stylesheets = True + use_embedded_content = False + + cover_url = 'https://sites.google.com/site/javno2010/home/siol_cover.jpg' + + extra_css = ''' + h1{font-family:Arial,Helvetica,sans-serif; font-weight:bold;font-size:large;} + h2{font-family:Arial,Helvetica,sans-serif; font-weight:bold;font-size:large;} + p{font-family:Arial,Helvetica,sans-serif;font-size:small;} + body{font-family:Helvetica,Arial,sans-serif;font-size:small;} + ''' + + html2lrf_options = ['--base-font-size', '10'] + + keep_only_tags = [ + dict(name='div', attrs={'id':'idContent'}), + ] + + remove_tags = [ + dict(name='span', attrs={'class':'com1'}), + dict(name='div', attrs={'class':'relation'}), + dict(name='p', attrs={'class':'path'}), + dict(name='div', attrs={'class':'clear_r'}), + dict(name='div', attrs={'id':'appendix'}), + dict(name='div', attrs={'id':'rail'}), + dict(name='div', attrs={'id':'div_comments'}), + dict(name='div', attrs={'class':'thumbs'}), + ] + + feeds = [ + (u'Slovenija', u'http://www.siol.net/rss.aspx?path=Slovenija') + ,(u'Lokalne novice', u'http://www.siol.net/rss.aspx?path=Slovenija/Lokalne_novice') + ,(u'EU', u'http://www.siol.net/rss.aspx?path=EU') + ,(u'Svet', u'http://www.siol.net/rss.aspx?path=Svet') + ,(u'Gospodarstvo', u'http://www.siol.net/rss.aspx?path=Gospodarstvo') + ,(u'Sportal', u'http://www.siol.net/rss.aspx?path=Sportal') + ,(u'Trendi', u'http://www.siol.net/rss.aspx?path=Trendi') + ,(u'Avtomoto', u'http://www.siol.net/rss.aspx?path=Avtomoto') + ,(u'Tehnologija', u'http://www.siol.net/rss.aspx?path=Tehnologija') + ,(u'TV / Film', u'http://www.siol.net/rss.aspx?path=TV') + ] diff --git a/resources/recipes/vedomosti.recipe b/resources/recipes/vedomosti.recipe new file mode 100644 index 0000000000..f9590f8c29 --- /dev/null +++ b/resources/recipes/vedomosti.recipe @@ -0,0 +1,195 @@ +#!/usr/bin/env python + +u''' +Ведомости +''' + +from calibre.web.feeds.feedparser import parse +from calibre.ebooks.BeautifulSoup import Tag +from calibre.web.feeds.news import BasicNewsRecipe + +class VedomostiRecipe(BasicNewsRecipe): + title = u'Ведомости' + __author__ = 'Nikolai Kotchetkov' + publisher = 'vedomosti.ru' + category = 'press, Russia' + description = u'Ежедневная деловая газета' + oldest_article = 3 + max_articles_per_feed = 100 + + masthead_url = u'http://motorro.com/imgdir/logos/ved_logo_black2_cropped.gif' + cover_url = u'http://motorro.com/imgdir/logos/ved_logo_black2_cropped.gif' + + #Add feed names if you want them to be sorted (feeds of this list appear first) + sortOrder = [u'_default', u'Первая полоса', u'Власть и деньги'] + + encoding = 'cp1251' + language = 'ru' + no_stylesheets = True + remove_javascript = True + recursions = 0 + + conversion_options = { + 'comment' : description + , 'tags' : category + , 'publisher' : publisher + , 'language' : language + } + + + keep_only_tags = [dict(name='td', attrs={'class' : ['second_content']})] + + remove_tags_after = [dict(name='div', attrs={'class' : 'article_text'})] + + remove_tags = [dict(name='div', attrs={'class' : ['sep', 'choice', 'articleRightTbl']})] + + feeds = [u'http://www.vedomosti.ru/newspaper/out/rss.xml'] + + #base URL for relative links + base_url = u'http://www.vedomosti.ru' + + extra_css = 'h1 {font-size: 1.5em; margin: 0em 0em 0em 0em; text-align: center;}'\ + 'h2 {font-size: 1.0em; margin: 0em 0em 0em 0em;}'\ + 'h3 {font-size: 0.8em; margin: 0em 0em 0em 0em;}'\ + '.article_date {font-size: 0.5em; color: gray; font-family: monospace; text-align:right;}'\ + '.article_authors {font-size: 0.5em; color: gray; font-family: monospace; text-align:right;}'\ + '.article_img {width:100%; text-align: center; padding: 3px 3px 3px 3px;}'\ + '.article_img_desc {width:100%; text-align: center; font-size: 0.5em; color: gray; font-family: monospace;}'\ + '.article_desc {font-size: 1em; font-style:italic;}' + + def parse_index(self): + try: + feedData = parse(self.feeds[0]) + if not feedData: + raise NotImplementedError + self.log("parse_index: Feed loaded successfully.") + if feedData.feed.has_key('title'): + self.title = feedData.feed.title + self.log("parse_index: Title updated to: ", self.title) + if feedData.feed.has_key('description'): + self.description = feedData.feed.description + self.log("parse_index: Description updated to: ", self.description) + + def get_virtual_feed_articles(feed): + if feeds.has_key(feed): + return feeds[feed][1] + self.log("Adding new feed: ", feed) + articles = [] + feeds[feed] = (feed, articles) + return articles + + feeds = {} + + #Iterate feed items and distribute articles using tags + for item in feedData.entries: + link = item.get('link', ''); + title = item.get('title', ''); + if '' == link or '' == title: + continue + article = {'title':title, 'url':link, 'description':item.get('description', ''), 'date':item.get('date', ''), 'content':''}; + if not item.has_key('tags'): + get_virtual_feed_articles('_default').append(article) + continue + for tag in item.tags: + addedToDefault = False + term = tag.get('term', '') + if '' == term: + if (not addedToDefault): + get_virtual_feed_articles('_default').append(article) + continue + get_virtual_feed_articles(term).append(article) + + #Get feed list + #Select sorted feeds first of all + result = [] + for feedName in self.sortOrder: + if (not feeds.has_key(feedName)): continue + result.append(feeds[feedName]) + del feeds[feedName] + result = result + feeds.values() + + return result + + except Exception, err: + self.log(err) + raise NotImplementedError + + def preprocess_html(self, soup): + return self.adeify_images(soup) + + def postprocess_html(self, soup, first_fetch): + #self.log('Original: ', soup.prettify()) + + #Find article + contents = soup.find('div', {'class':['article_text']}) + if not contents: + self.log('postprocess_html: article div not found!') + return soup + contents.extract() + + #Find title + title = soup.find('h1') + if title: + contents.insert(0, title) + + #Find article image + newstop = soup.find('div', {'class':['newstop']}) + if newstop: + img = newstop.find('img') + if img: + imgDiv = Tag(soup, 'div') + imgDiv['class'] = 'article_img' + + if img.has_key('width'): + del(img['width']) + if img.has_key('height'): + del(img['height']) + + #find description + element = img.parent.nextSibling + + img.extract() + imgDiv.insert(0, img) + + while element: + if not isinstance(element, Tag): + continue + nextElement = element.nextSibling + if 'p' == element.name: + element.extract() + element['class'] = 'article_img_desc' + imgDiv.insert(len(imgDiv.contents), element) + element = nextElement + + contents.insert(1, imgDiv) + + #find article abstract + abstract = soup.find('p', {'class':['subhead']}) + if abstract: + abstract['class'] = 'article_desc' + contents.insert(2, abstract) + + #Find article authors + authorsDiv = soup.find('div', {'class':['autors']}) + if authorsDiv: + authorsP = authorsDiv.find('p') + if authorsP: + authorsP['class'] = 'article_authors' + contents.insert(len(contents.contents), authorsP) + + #Fix urls that use relative path + urls = contents.findAll('a'); + if urls: + for url in urls: + if not url.has_key('href'): + continue + if '/' == url['href'][0]: + url['href'] = self.base_url + url['href'] + + body = soup.find('td', {'class':['second_content']}) + if body: + body.replaceWith(contents) + + self.log('Result: ', soup.prettify()) + return soup + diff --git a/setup/installer/__init__.py b/setup/installer/__init__.py index 9b1f80f54b..c25334dbe4 100644 --- a/setup/installer/__init__.py +++ b/setup/installer/__init__.py @@ -49,7 +49,6 @@ class Push(Command): print '\n\nPushing to:', host, '\n' threads.append(Thread(target=subprocess.check_call, args=(rcmd,))) threads[-1].start() - subprocess.check_call(rcmd) for thread in threads: thread.join() diff --git a/src/calibre/__init__.py b/src/calibre/__init__.py index 1226ab3188..a43b0126ff 100644 --- a/src/calibre/__init__.py +++ b/src/calibre/__init__.py @@ -444,6 +444,9 @@ xml_entity_to_unicode = partial(entity_to_unicode, result_exceptions = { def replace_entities(raw): return _ent_pat.sub(entity_to_unicode, raw) +def xml_replace_entities(raw): + return _ent_pat.sub(xml_entity_to_unicode, raw) + def prepare_string_for_xml(raw, attribute=False): raw = _ent_pat.sub(entity_to_unicode, raw) raw = raw.replace('&', '&').replace('<', '<').replace('>', '>') diff --git a/src/calibre/ebooks/metadata/epub.py b/src/calibre/ebooks/metadata/epub.py index 2f0271b785..e1712f3668 100644 --- a/src/calibre/ebooks/metadata/epub.py +++ b/src/calibre/ebooks/metadata/epub.py @@ -109,8 +109,9 @@ class OCFZipReader(OCFReader): raise EPubException("not a ZIP .epub OCF container") self.root = root if self.root is None: - if hasattr(stream, 'name'): - self.root = os.path.abspath(os.path.dirname(stream.name)) + name = getattr(stream, 'name', False) + if name: + self.root = os.path.abspath(os.path.dirname(name)) else: self.root = os.getcwdu() super(OCFZipReader, self).__init__() diff --git a/src/calibre/ebooks/oeb/transforms/structure.py b/src/calibre/ebooks/oeb/transforms/structure.py index 07235b4fb0..0db9b153df 100644 --- a/src/calibre/ebooks/oeb/transforms/structure.py +++ b/src/calibre/ebooks/oeb/transforms/structure.py @@ -133,7 +133,11 @@ class DetectStructure(object): def elem_to_link(self, item, elem, counter): - text = xml2text(elem) + text = xml2text(elem).strip() + if not text: + text = elem.get('title', '') + if not text: + text = elem.get('alt', '') text = text[:100].strip() id = elem.get('id', 'calibre_toc_%d'%counter) elem.set('id', id) diff --git a/src/calibre/gui2/book_details.py b/src/calibre/gui2/book_details.py index c75eb35202..e5711bb31a 100644 --- a/src/calibre/gui2/book_details.py +++ b/src/calibre/gui2/book_details.py @@ -213,11 +213,13 @@ class BookInfo(QWebView): f = QFontInfo(QApplication.font(self.parent())).pixelSize() p = unicode(QApplication.palette().color(QPalette.Normal, QPalette.Base).name()) + c = unicode(QApplication.palette().color(QPalette.Normal, + QPalette.Text).name()) templ = u'''\ @@ -225,7 +227,7 @@ class BookInfo(QWebView): %%s - '''%(p, f) + '''%(p, f, c) if self.vertical: if comments: rows += u'%s'%comments diff --git a/src/calibre/gui2/comments_editor.py b/src/calibre/gui2/comments_editor.py new file mode 100644 index 0000000000..8a19b657a9 --- /dev/null +++ b/src/calibre/gui2/comments_editor.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai + +__license__ = 'GPL v3' +__copyright__ = '2010, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + + +from lxml import html +from lxml.html import soupparser + +from PyQt4.Qt import QApplication, QFontInfo, QPalette +from PyQt4.QtWebKit import QWebView + +from calibre.ebooks.chardet import xml_to_unicode +from calibre import xml_replace_entities + +class EditorWidget(QWebView): + + @dynamic_property + def html(self): + + def fget(self): + ans = u'' + try: + raw = unicode(self.page().mainFrame().toHtml()) + raw = xml_to_unicode(raw, strip_encoding_pats=True, + resolve_entities=True)[0] + + try: + root = html.fromstring(raw) + except: + root = soupparser.fromstring(raw) + + elems = [] + for body in root.xpath('//body'): + elems += [html.tostring(x, encoding=unicode) for x in body if + x.tag != 'script'] + if len(elems) > 1: + ans = u'
%s
'%(u''.join(elems)) + else: + ans = u''.join(elems) + ans = xml_replace_entities(ans) + except: + import traceback + traceback.print_exc() + + return ans + + def fset(self, val): + self.setHtml(val) + f = QFontInfo(QApplication.font(self)).pixelSize() + b = unicode(QApplication.palette().color(QPalette.Normal, + QPalette.Base).name()) + c = unicode(QApplication.palette().color(QPalette.Normal, + QPalette.Text).name()) + style = 'font-size: %dpx; background-color: %s; color: %s' % (f, b, + c) + + for body in self.page().mainFrame().documentElement().findAll('body'): + body.setAttribute('style', style) + self.page().setContentEditable(True) + + return property(fget=fget, fset=fset) + +if __name__ == '__main__': + app = QApplication([]) + w = EditorWidget() + w.show() +# testing {{{ + + w.html = ''' +
+ +

From Publishers Weekly

+
+ Starred Review. Paul Dirac (1902–1984) shared the Nobel Prize for physics with Erwin Schrödinger in 1933, but whereas physicists regard Dirac as one of the giants of the 20th century, he isn't as well known outside the profession. This may be due to the lack of humorous quips attributed to Dirac, as compared with an Einstein or a Feynman. If he spoke at all, it was with one-word answers that made Calvin Coolidge look loquacious . Dirac adhered to Keats's admonition that Beauty is truth, truth beauty: if an equation was beautiful, it was probably correct, and vice versa. His most famous equation predicted the positron (now used in PET scans), which is the antiparticle of the electron, and antimatter in general. In 1955, Dirac came up with a primitive version of string theory, which today is the rock star branch of physics. Physicist Farmelo (It Must Be Beautiful) speculates that Dirac suffered from undiagnosed autism because his character quirks resembled autism's symptoms. Farmelo proves himself a wizard at explaining the arcane aspects of particle physics. His great affection for his odd but brilliant subject shows on every page, giving Dirac the biography any great scientist deserves. (Sept.)
Copyright © Reed Business Information, a division of Reed Elsevier Inc. All rights reserved. + +
+

Review

+
+
Kirkus *Starred Review*
“Paul Dirac was a giant of 20th-century physics, and this rich, satisfying biography does him justice…. [A] nuanced portrayal of an introverted eccentric who held his own in a small clique of revolutionary scientific geniuses.”

Peter Higgs, Times (UK)
“Fascinating reading… Graham Farmelo has done a splendid job of portraying Dirac and his world. The biography is a major achievement.”

Telegraph
“If Newton was the Shakespeare of British physics, Dirac was its Milton, the most fascinating and enigmatic of all our great scientists. And he now has a biography to match his talents: a wonderful book by Graham Farmelo. The story it tells is moving, sometimes comic, sometimes infinitely sad, and goes to the roots of what we mean by truth in science.”

New Statesman
“A marvelously rich and intimate study.”

Sunday Herald
“Farmelo’s splendid biography has enough scientific exposition for the biggest science fan and enough human interest for the rest of us. It creates a picture of a man who was a great theoretical scientist but also an awkward but oddly endearing human being…. This is a fine book: a fitting tribute to a significant and intriguing scientific figure.”

The Economist
“[A] sympathetic portrait….Of the small group of young men who developed quantum mechanics and revolutionized physics almost a century ago, he truly stands out. Paul Dirac was a strange man in a strange world. This biography, long overdue, is most welcome.”

Times Higher Education Supplement (UK)
“A page-turner about Dirac and quantum physics seems a contradiction in terms, but Graham Farmelo's new book, The Strangest Man, is an eminently readable account of the developments in physics throughout the 1920s, 1930s and 1940s and the life of one of the discipline's key scientists.”

New Scientist
“Enthralling… Regardless of whether Dirac was autistic or simply unpleasant, he is an icon of modern thought and Farmelo's book gives us a genuine insight into his life and times.”

John Gribbin, Literary Review
“Fascinating …[A] suberb book.”

Tom Stoppard
“In the group portrait of genius in 20th century physics, Paul Dirac is the stick figure. Who was he, and what did he do? For all non-physicists who have followed the greatest intellectual adventure of modern times, this is the missing book.”

Michael Frayn
“Graham Farmelo has found the subject he was born to write about, and brought it off triumphantly. Dirac was one of the great founding fathers of modern physics, a theoretician who explored the sub-atomic world through the power of pure mathematics. He was also a most extraordinary man - an extreme introvert, and perhaps autistic. Farmelo traces the outward events as authoritatively as the inward. His book is a monumental achievement – one of the great scientific biographies.”

Roger Highfield, Editor,New Scientist
“A must-read for anyone interested in the extraordinary power of pure thought. With this revelatory, moving and definitive biography, Graham Farmelo provides the first real glimpse inside the bizarre mind of Paul Dirac.”

Martin Rees, President of the Royal Society, Master of Trinity College, Professor of Cosmology and Astrophysics at the University of Cambridge and Astronomer Royal
“Paul Dirac, though a quiet and withdrawn character, made towering contributions to the greatest scientific revolution of the 20th century. In this sensitive and meticulously researched biography, Graham Farmelo does Dirac proud, and offers a wonderful insight into the European academic environment in which his creativity flourished."

Barnes & Noble Review
“Farmelo explains all the science relevant to understanding Dirac, and does it well; equally good is his careful and copious account of a personal life that was dogged by a sense of tragedy…. [I]f [Dirac] could read Farmelo’s absorbing and accessible account of his life he would see that it had magic in it, and triumph: the magic of revelations about the deep nature of reality, and the triumph of having moved human understanding several steps further towards the light.”

Newark Star-Ledger
“[An] excellently researched biography…. [T]his book is a major step toward making a staggeringly brilliant, remote man seem likeable.”

Los Angeles Times
“Graham Farmelo has managed to haul Dirac onstage in an affectionate and meticulously researched book that illuminates both his era and his science…. Farmelo is very good at portraying this locked-in, asocial creature, often with an eerie use of the future-perfect tense…, which has the virtue of putting the reader in the same room with people who are long gone.”

SeedMagazine.com
“[A] tour de force filled with insight and revelation. The Strangest Man offers an unprecedented and gripping view of Dirac not only as a scientist, but also as a human being.”

New York Times Book Review
“This biography is a gift. It is both wonderfully written (certainly not a given in the category Accessible Biographies of Mathematical Physicists) and a thought-provoking meditation on human achievement, limitations and the relations between the two…. [T]he most satisfying and memorable biography I have read in years.”

Time Magazine
“Paul Dirac won a Nobel Prize for Physics at 31. He was one of quantum mechanics’ founding fathers, an Einstein-level genius. He was also virtually incapable of having normal social interactions. Graham Farmelo’s biography explains Dirac’s mysterious life and work.”

Library Journal
“Farmelo did not pick the easiest biography to write – its subject lived a largely solitary life in deep thought. But Dirac was also beset with tragedy… and in that respect, the author proposes some novel insights into what shaped the man. This would be a strong addition to a bibliography of magnificent 20th-century physicist biographies, including Walter Issacson’s Einstein, Kai Bird and Martin J. Sherwin’s American Prometheus: The Triumph and Tragedy of J. Robert Oppenheimer, and James Gleick’s Genius: The Life and Science of Richard Feynman.”

American Journal of Physics
“[A] very moving biography…. It would have been easy to simply fill the biography with Dirac stories of which there is a cornucopia, many of which are actually true. But Farmelo does much more than that. He has met and spoken with people who knew Dirac including the surviving members of his family. He has been to where Dirac lived and worked and he understands the physics. What has emerged is a 558 page biography, which is a model of the genre. Dirac was so private and emotionally self-contained that one wonders if anyone really knew him. Farmelo’s book is as close as we are likely to come."

American Scientist
“[A] highly readable and sympathetic biography of the taciturn British physicist who can be said, with little exaggeration, to have invented modern theoretical physics. The book is a real achievement, alternately gripping and illuminating.”

Natural History
“Farmelo’s eloquent and empathetic examination of Dirac’s life raises this book above the level of workmanlike popularization. Using personal interviews, scientific archives, and newly released documents and letters, he’s managed – as much as anyone could – to dispel the impression of the physicist as a real-life Mr. Spock, the half Vulcan of Star Trek.”

Science
“[A] consummate and seamless biography…. Farmelo has succeeded masterfully in the difficult genre of writing a great scientist’s life for a general audience.”

Physics Today
“[An] excellent biography of a hero of physics…. [I]n The Strangest Man, we are treated to a fascinating, thoroughly researched, and well-written account of one of the most important figures of modern physics.”

Nature
“As this excellent biography by Graham Farmelo shows, Dirac’s contributions to science were profound and far-ranging; modern ideas that have their origins in quantum electrodynamics are inspired by his insight…. The effortless writing style shows that it is possible to describe profound ideas without compromising scientific integrity or readability."

Freeman Dyson, New York Review of Books
“In Farmelo’s book we see Dirac as a character in a human drama, carrying his full share of tragedy as well as triumph.”

American Journal of Physics
“Farmelo’s exhaustively researched biography…not only traces the life of its title figure but portrays the unfolding of quantum mechanics with cinematic scope…. He repeatedly zooms his storyteller’s lens in and out between intimate close-ups and grand scenes, all the while attempting to make the physics comprehensible to the general readership without trivializing it. In his telling, the front-line scientists are a competitive troupe of explorers, jockeying for renown – only the uncharted territory is in the mind and the map is mathematical…. We read works like Farmelo’s for enlightenment, for inspiration, and for the reminder that science is a quintessentially human endeavor, with all...

+ +
+
+
+ +

From Publishers Weekly

+
+ Starred Review. Paul Dirac (1902–1984) shared the Nobel Prize for physics with Erwin Schrödinger in 1933, but whereas physicists regard Dirac as one of the giants of the 20th century, he isn't as well known outside the profession. This may be due to the lack of humorous quips attributed to Dirac, as compared with an Einstein or a Feynman. If he spoke at all, it was with one-word answers that made Calvin Coolidge look loquacious . Dirac adhered to Keats's admonition that Beauty is truth, truth beauty: if an equation was beautiful, it was probably correct, and vice versa. His most famous equation predicted the positron (now used in PET scans), which is the antiparticle of the electron, and antimatter in general. In 1955, Dirac came up with a primitive version of string theory, which today is the rock star branch of physics. Physicist Farmelo (It Must Be Beautiful) speculates that Dirac suffered from undiagnosed autism because his character quirks resembled autism's symptoms. Farmelo proves himself a wizard at explaining the arcane aspects of particle physics. His great affection for his odd but brilliant subject shows on every page, giving Dirac the biography any great scientist deserves. (Sept.)
Copyright © Reed Business Information, a division of Reed Elsevier Inc. All rights reserved. + +
+

Review

+
+
Kirkus *Starred Review*
“Paul Dirac was a giant of 20th-century physics, and this rich, satisfying biography does him justice…. [A] nuanced portrayal of an introverted eccentric who held his own in a small clique of revolutionary scientific geniuses.”

Peter Higgs, Times (UK)
“Fascinating reading… Graham Farmelo has done a splendid job of portraying Dirac and his world. The biography is a major achievement.”

Telegraph
“If Newton was the Shakespeare of British physics, Dirac was its Milton, the most fascinating and enigmatic of all our great scientists. And he now has a biography to match his talents: a wonderful book by Graham Farmelo. The story it tells is moving, sometimes comic, sometimes infinitely sad, and goes to the roots of what we mean by truth in science.”

New Statesman
“A marvelously rich and intimate study.”

Sunday Herald
“Farmelo’s splendid biography has enough scientific exposition for the biggest science fan and enough human interest for the rest of us. It creates a picture of a man who was a great theoretical scientist but also an awkward but oddly endearing human being…. This is a fine book: a fitting tribute to a significant and intriguing scientific figure.”

The Economist
“[A] sympathetic portrait….Of the small group of young men who developed quantum mechanics and revolutionized physics almost a century ago, he truly stands out. Paul Dirac was a strange man in a strange world. This biography, long overdue, is most welcome.”

Times Higher Education Supplement (UK)
“A page-turner about Dirac and quantum physics seems a contradiction in terms, but Graham Farmelo's new book, The Strangest Man, is an eminently readable account of the developments in physics throughout the 1920s, 1930s and 1940s and the life of one of the discipline's key scientists.”

New Scientist
“Enthralling… Regardless of whether Dirac was autistic or simply unpleasant, he is an icon of modern thought and Farmelo's book gives us a genuine insight into his life and times.”

John Gribbin, Literary Review
“Fascinating …[A] suberb book.”

Tom Stoppard
“In the group portrait of genius in 20th century physics, Paul Dirac is the stick figure. Who was he, and what did he do? For all non-physicists who have followed the greatest intellectual adventure of modern times, this is the missing book.”

Michael Frayn
“Graham Farmelo has found the subject he was born to write about, and brought it off triumphantly. Dirac was one of the great founding fathers of modern physics, a theoretician who explored the sub-atomic world through the power of pure mathematics. He was also a most extraordinary man - an extreme introvert, and perhaps autistic. Farmelo traces the outward events as authoritatively as the inward. His book is a monumental achievement – one of the great scientific biographies.”

Roger Highfield, Editor,New Scientist
“A must-read for anyone interested in the extraordinary power of pure thought. With this revelatory, moving and definitive biography, Graham Farmelo provides the first real glimpse inside the bizarre mind of Paul Dirac.”

Martin Rees, President of the Royal Society, Master of Trinity College, Professor of Cosmology and Astrophysics at the University of Cambridge and Astronomer Royal
“Paul Dirac, though a quiet and withdrawn character, made towering contributions to the greatest scientific revolution of the 20th century. In this sensitive and meticulously researched biography, Graham Farmelo does Dirac proud, and offers a wonderful insight into the European academic environment in which his creativity flourished."

Barnes & Noble Review
“Farmelo explains all the science relevant to understanding Dirac, and does it well; equally good is his careful and copious account of a personal life that was dogged by a sense of tragedy…. [I]f [Dirac] could read Farmelo’s absorbing and accessible account of his life he would see that it had magic in it, and triumph: the magic of revelations about the deep nature of reality, and the triumph of having moved human understanding several steps further towards the light.”

Newark Star-Ledger
“[An] excellently researched biography…. [T]his book is a major step toward making a staggeringly brilliant, remote man seem likeable.”

Los Angeles Times
“Graham Farmelo has managed to haul Dirac onstage in an affectionate and meticulously researched book that illuminates both his era and his science…. Farmelo is very good at portraying this locked-in, asocial creature, often with an eerie use of the future-perfect tense…, which has the virtue of putting the reader in the same room with people who are long gone.”

SeedMagazine.com
“[A] tour de force filled with insight and revelation. The Strangest Man offers an unprecedented and gripping view of Dirac not only as a scientist, but also as a human being.”

New York Times Book Review
“This biography is a gift. It is both wonderfully written (certainly not a given in the category Accessible Biographies of Mathematical Physicists) and a thought-provoking meditation on human achievement, limitations and the relations between the two…. [T]he most satisfying and memorable biography I have read in years.”

Time Magazine
“Paul Dirac won a Nobel Prize for Physics at 31. He was one of quantum mechanics’ founding fathers, an Einstein-level genius. He was also virtually incapable of having normal social interactions. Graham Farmelo’s biography explains Dirac’s mysterious life and work.”

Library Journal
“Farmelo did not pick the easiest biography to write – its subject lived a largely solitary life in deep thought. But Dirac was also beset with tragedy… and in that respect, the author proposes some novel insights into what shaped the man. This would be a strong addition to a bibliography of magnificent 20th-century physicist biographies, including Walter Issacson’s Einstein, Kai Bird and Martin J. Sherwin’s American Prometheus: The Triumph and Tragedy of J. Robert Oppenheimer, and James Gleick’s Genius: The Life and Science of Richard Feynman.”

American Journal of Physics
“[A] very moving biography…. It would have been easy to simply fill the biography with Dirac stories of which there is a cornucopia, many of which are actually true. But Farmelo does much more than that. He has met and spoken with people who knew Dirac including the surviving members of his family. He has been to where Dirac lived and worked and he understands the physics. What has emerged is a 558 page biography, which is a model of the genre. Dirac was so private and emotionally self-contained that one wonders if anyone really knew him. Farmelo’s book is as close as we are likely to come."

American Scientist
“[A] highly readable and sympathetic biography of the taciturn British physicist who can be said, with little exaggeration, to have invented modern theoretical physics. The book is a real achievement, alternately gripping and illuminating.”

Natural History
“Farmelo’s eloquent and empathetic examination of Dirac’s life raises this book above the level of workmanlike popularization. Using personal interviews, scientific archives, and newly released documents and letters, he’s managed – as much as anyone could – to dispel the impression of the physicist as a real-life Mr. Spock, the half Vulcan of Star Trek.”

Science
“[A] consummate and seamless biography…. Farmelo has succeeded masterfully in the difficult genre of writing a great scientist’s life for a general audience.”

Physics Today
“[An] excellent biography of a hero of physics…. [I]n The Strangest Man, we are treated to a fascinating, thoroughly researched, and well-written account of one of the most important figures of modern physics.”

Nature
“As this excellent biography by Graham Farmelo shows, Dirac’s contributions to science were profound and far-ranging; modern ideas that have their origins in quantum electrodynamics are inspired by his insight…. The effortless writing style shows that it is possible to describe profound ideas without compromising scientific integrity or readability."

Freeman Dyson, New York Review of Books
“In Farmelo’s book we see Dirac as a character in a human drama, carrying his full share of tragedy as well as triumph.”

American Journal of Physics
“Farmelo’s exhaustively researched biography…not only traces the life of its title figure but portrays the unfolding of quantum mechanics with cinematic scope…. He repeatedly zooms his storyteller’s lens in and out between intimate close-ups and grand scenes, all the while attempting to make the physics comprehensible to the general readership without trivializing it. In his telling, the front-line scientists are a competitive troupe of explorers, jockeying for renown – only the uncharted territory is in the mind and the map is mathematical…. We read works like Farmelo’s for enlightenment, for inspiration, and for the reminder that science is a quintessentially human endeavor, with all...

+ +
+
+ '''.decode('utf-8') + app.exec_() + #print w.html.encode('utf-8') + +# }}} + print w.html diff --git a/src/calibre/gui2/dialogs/book_info.py b/src/calibre/gui2/dialogs/book_info.py index 70c70ddf96..4cbe0ace7f 100644 --- a/src/calibre/gui2/dialogs/book_info.py +++ b/src/calibre/gui2/dialogs/book_info.py @@ -5,8 +5,8 @@ __docformat__ = 'restructuredtext en' import textwrap, os, re -from PyQt4.QtCore import QCoreApplication, SIGNAL, QModelIndex, QTimer, Qt -from PyQt4.QtGui import QDialog, QPixmap, QGraphicsScene, QIcon +from PyQt4.Qt import QCoreApplication, SIGNAL, QModelIndex, QTimer, Qt, \ + QDialog, QPixmap, QGraphicsScene, QIcon, QSize from calibre.gui2.dialogs.book_info_ui import Ui_BookInfo from calibre.gui2 import dynamic, open_local_file @@ -20,6 +20,8 @@ class BookInfo(QDialog, Ui_BookInfo): Ui_BookInfo.__init__(self) self.setupUi(self) self.cover_pixmap = None + self.comments.sizeHint = self.comments_size_hint + desktop = QCoreApplication.instance().desktop() screen_height = desktop.availableGeometry().height() - 100 self.resize(self.size().width(), screen_height) @@ -37,12 +39,16 @@ class BookInfo(QDialog, Ui_BookInfo): self.fit_cover.stateChanged.connect(self.toggle_cover_fit) self.cover.resizeEvent = self.cover_view_resized + def comments_size_hint(self): + return QSize(350, 350) + def toggle_cover_fit(self, state): dynamic.set('book_info_dialog_fit_cover', self.fit_cover.isChecked()) self.resize_cover() def cover_view_resized(self, event): QTimer.singleShot(1, self.resize_cover) + def slave(self, current, previous): row = current.row() self.refresh(row) diff --git a/src/calibre/gui2/dialogs/book_info.ui b/src/calibre/gui2/dialogs/book_info.ui index d9ff87bcdd..7eb6ccd3d3 100644 --- a/src/calibre/gui2/dialogs/book_info.ui +++ b/src/calibre/gui2/dialogs/book_info.ui @@ -47,9 +47,15 @@ Comments - - + + + + + 0 + 0 + + 350 diff --git a/src/calibre/gui2/dialogs/metadata_bulk.py b/src/calibre/gui2/dialogs/metadata_bulk.py index 6b5ef60263..8e9fca718e 100644 --- a/src/calibre/gui2/dialogs/metadata_bulk.py +++ b/src/calibre/gui2/dialogs/metadata_bulk.py @@ -240,13 +240,13 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog): self.writable_fields = [''] fm = self.db.field_metadata for f in fm: - if (f in ['author_sort'] or ( - fm[f]['datatype'] in ['text', 'series']) - and fm[f].get('search_terms', None) - and f not in ['formats', 'ondevice']): + if (f in ['author_sort'] or + (fm[f]['datatype'] in ['text', 'series'] + and fm[f].get('search_terms', None) + and f not in ['formats', 'ondevice', 'sort'])): self.all_fields.append(f) self.writable_fields.append(f) - if fm[f]['datatype'] == 'composite': + if f in ['sort'] or fm[f]['datatype'] == 'composite': self.all_fields.append(f) self.all_fields.sort() self.writable_fields.sort() @@ -274,7 +274,6 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog): self.main_heading = _( 'You can destroy your library using this feature. ' 'Changes are permanent. There is no undo function. ' - ' This feature is experimental, and there may be bugs. ' 'You are strongly encouraged to back up your library ' 'before proceeding.

' 'Search and replace in text fields using character matching ' @@ -338,7 +337,10 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog): def s_r_get_field(self, mi, field): if field: fm = self.db.metadata_for_field(field) - val = mi.get(field, None) + if field == 'sort': + val = mi.get('title_sort', None) + else: + val = mi.get(field, None) if val is None: val = [] elif not fm['is_multiple']: diff --git a/src/calibre/gui2/dialogs/metadata_bulk.ui b/src/calibre/gui2/dialogs/metadata_bulk.ui index 62a40a9676..c6830c5d5f 100644 --- a/src/calibre/gui2/dialogs/metadata_bulk.ui +++ b/src/calibre/gui2/dialogs/metadata_bulk.ui @@ -400,7 +400,7 @@ Future conversion of these books will use the default settings. - &Search and replace (experimental) + &Search and replace diff --git a/src/calibre/gui2/dialogs/metadata_single.py b/src/calibre/gui2/dialogs/metadata_single.py index 668239f941..2b951a7b2b 100644 --- a/src/calibre/gui2/dialogs/metadata_single.py +++ b/src/calibre/gui2/dialogs/metadata_single.py @@ -845,7 +845,7 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog): if cf is not None and hasattr(cf, 'terminate'): cf.terminate() cf.wait() - + self.save_state() QDialog.reject(self, *args) def read_state(self): diff --git a/src/calibre/gui2/dialogs/search.py b/src/calibre/gui2/dialogs/search.py index 041e7ff1fc..ba09a34a68 100644 --- a/src/calibre/gui2/dialogs/search.py +++ b/src/calibre/gui2/dialogs/search.py @@ -1,17 +1,75 @@ __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal ' -import re -from PyQt4.QtGui import QDialog + +import re, copy + +from PyQt4.QtGui import QDialog, QDialogButtonBox from calibre.gui2.dialogs.search_ui import Ui_Dialog from calibre.library.caches import CONTAINS_MATCH, EQUALS_MATCH +from calibre.gui2 import gprefs + +box_values = {} class SearchDialog(QDialog, Ui_Dialog): - def __init__(self, *args): - QDialog.__init__(self, *args) + def __init__(self, parent, db): + QDialog.__init__(self, parent) self.setupUi(self) self.mc = '' + searchables = sorted(db.field_metadata.searchable_fields(), + lambda x, y: cmp(x if x[0] != '#' else x[1:], + y if y[0] != '#' else y[1:])) + self.general_combo.addItems(searchables) + + self.box_last_values = copy.deepcopy(box_values) + if self.box_last_values: + for k,v in self.box_last_values.items(): + if k == 'general_index': + continue + getattr(self, k).setText(v) + self.general_combo.setCurrentIndex( + self.general_combo.findText(self.box_last_values['general_index'])) + + self.buttonBox.accepted.connect(self.advanced_search_button_pushed) + self.tab_2_button_box.accepted.connect(self.accept) + self.tab_2_button_box.rejected.connect(self.reject) + self.clear_button.clicked.connect(self.clear_button_pushed) + self.adv_search_used = False + + current_tab = gprefs.get('advanced search dialog current tab', 0) + self.tabWidget.setCurrentIndex(current_tab) + self.tabWidget.currentChanged[int].connect(self.tab_changed) + self.tab_changed(current_tab) + + def save_state(self): + gprefs['advanced search dialog current tab'] = \ + self.tabWidget.currentIndex() + + def accept(self): + self.save_state() + return QDialog.accept(self) + + def reject(self): + self.save_state() + return QDialog.reject(self) + + def tab_changed(self, idx): + if idx == 1: + self.tab_2_button_box.button(QDialogButtonBox.Ok).setDefault(True) + else: + self.buttonBox.button(QDialogButtonBox.Ok).setDefault(True) + + def advanced_search_button_pushed(self): + self.adv_search_used = True + self.accept() + + def clear_button_pushed(self): + self.title_box.setText('') + self.authors_box.setText('') + self.series_box.setText('') + self.tags_box.setText('') + self.general_box.setText('') def tokens(self, raw): phrases = re.findall(r'\s*".*?"\s*', raw) @@ -21,6 +79,12 @@ class SearchDialog(QDialog, Ui_Dialog): return ['"' + self.mc + t + '"' for t in phrases + [r.strip() for r in raw.split()]] def search_string(self): + if self.adv_search_used: + return self.adv_search_string() + else: + return self.box_search_string() + + def adv_search_string(self): mk = self.matchkind.currentIndex() if mk == CONTAINS_MATCH: self.mc = '' @@ -56,3 +120,36 @@ class SearchDialog(QDialog, Ui_Dialog): tok = '"%s"'%tok return tok + def box_search_string(self): + ans = [] + self.box_last_values = {} + title = unicode(self.title_box.text()).strip() + self.box_last_values['title_box'] = title + if title: + ans.append('title:"' + title + '"') + author = unicode(self.authors_box.text()).strip() + self.box_last_values['authors_box'] = author + if author: + ans.append('author:"' + author + '"') + series = unicode(self.series_box.text()).strip() + self.box_last_values['series_box'] = series + if series: + ans.append('series:"' + series + '"') + self.mc = '=' + tags = unicode(self.tags_box.text()) + self.box_last_values['tags_box'] = tags + tags = self.tokens(tags) + if tags: + tags = ['tags:' + t for t in tags] + ans.append('(' + ' or '.join(tags) + ')') + general = unicode(self.general_box.text()) + self.box_last_values['general_box'] = general + general_index = unicode(self.general_combo.currentText()) + self.box_last_values['general_index'] = general_index + global box_values + box_values = copy.deepcopy(self.box_last_values) + if general: + ans.append(unicode(self.general_combo.currentText()) + ':"' + general + '"') + if ans: + return ' and '.join(ans) + return '' diff --git a/src/calibre/gui2/dialogs/search.ui b/src/calibre/gui2/dialogs/search.ui index 9e8817b1f4..41de898b56 100644 --- a/src/calibre/gui2/dialogs/search.ui +++ b/src/calibre/gui2/dialogs/search.ui @@ -1,195 +1,359 @@ - + + Dialog - - + + 0 0 - 667 - 391 + 731 + 384 - + Advanced Search - - + + :/images/search.png:/images/search.png - - - - - Find entries that have... + + + + + 0 - - - - - - - &All these words: - - - all - - - - - - - - - - - - - - This exact &phrase: - - - all - - - - - - - - - - - - - - &One or more of these words: - - - all - - - - - - - - - - - - - - - But dont show entries that have... - - - - - - - - Any of these &unwanted words: - - - all - - - - - - - - - - - - - - - - 16777215 - 60 - - - - - - - What kind of match to use: - - - matchkind - - - - - - - - Contains: the word or phrase matches anywhere in the metadata + + + A&dvanced Search + + + + + + Find entries that have... - - - - Equals: the word or phrase must match an entire metadata field + + + + + + + &All these words: + + + all + + + + + + + + + + + + + + This exact &phrase: + + + all + + + + + + + + + + + + + + &One or more of these words: + + + all + + + + + + + + + + + + + + + But dont show entries that have... - - - - Regular expression: the expression must match anywhere in the metadata + + + + + + + Any of these &unwanted words: + + + all + + + + + + + + + + + + + 16777215 + 60 + + + + + + + What kind of match to use: + + + matchkind + + + + + + + + Contains: the word or phrase matches anywhere in the metadata + + + + + Equals: the word or phrase must match an entire metadata field + + + + + Regular expression: the expression must match anywhere in the metadata + + + + + + + + + 40 + 0 + + + + + + + matchkind + + + + + + + + + + + 16777215 + 30 + + + + See the <a href="http://calibre-ebook.com/user_manual/gui.html#the-search-interface">User Manual</a> for more help + + + true + + + + + + + + + + Qt::Vertical - - - - - - - - 40 - 0 - - - - - - - matchkind - - - - - - - - - - - 16777215 - 30 - - - - See the <a href="http://calibre-ebook.com/user_manual/gui.html#the-search-interface">User Manual</a> for more help - - - true - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - + + + 20 + 40 + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + Titl&e/Author/Series ... + + + + + + &Title: + + + title_box + + + + + + + Enter the title. + + + + + + + &Author: + + + authors_box + + + + + + + &Series: + + + series_box + + + + + + + Ta&gs: + + + tags_box + + + + + + + Enter an author's name. Only one author can be used. + + + + + + + Enter a series name, without an index. Only one series name can be used. + + + + + + + Enter tags separated by spaces + + + + + + + + + + + + + + + &Clear + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Search only in specific fields: + + + + + + + all + phrase + any + none + matchkind + buttonBox + title_box + authors_box + series_box + tags_box + general_combo + general_box + clear_button + tab_2_button_box + tabWidget + - + @@ -198,11 +362,11 @@ Dialog accept() - + 248 254 - + 157 274 @@ -214,11 +378,11 @@ Dialog reject() - + 316 260 - + 286 274 diff --git a/src/calibre/gui2/layout.py b/src/calibre/gui2/layout.py index 885a9cc33f..c6177ed882 100644 --- a/src/calibre/gui2/layout.py +++ b/src/calibre/gui2/layout.py @@ -167,6 +167,7 @@ class SearchBar(QWidget): # {{{ x.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) parent.advanced_search_button = x = QToolButton(self) + parent.advanced_search_button.setShortcut(_("Shift+Ctrl+F")) x.setIcon(QIcon(I('search.png'))) l.addWidget(x) x.setToolTip(_("Advanced search")) diff --git a/src/calibre/gui2/library/views.py b/src/calibre/gui2/library/views.py index e39c48a70c..224c095429 100644 --- a/src/calibre/gui2/library/views.py +++ b/src/calibre/gui2/library/views.py @@ -51,6 +51,10 @@ class BooksView(QTableView): # {{{ QTableView.__init__(self, parent) self.setEditTriggers(self.SelectedClicked|self.EditKeyPressed) + if tweaks['doubleclick_on_library_view'] == 'edit_cell': + self.setEditTriggers(self.DoubleClicked|self.editTriggers()) + elif tweaks['doubleclick_on_library_view'] == 'open_viewer': + self.doubleClicked.connect(parent.iactions['View'].view_triggered) self.drag_allowed = True self.setDragEnabled(True) @@ -100,8 +104,6 @@ class BooksView(QTableView): # {{{ self._model.about_to_be_sorted.connect(self.about_to_be_sorted) self._model.sorting_done.connect(self.sorting_done) - self.doubleClicked.connect(parent.iactions['View'].view_triggered) - # Column Header Context Menu {{{ def column_header_context_handler(self, action=None, column=None): if not action or not column: diff --git a/src/calibre/gui2/search_box.py b/src/calibre/gui2/search_box.py index 0b85749370..b37d74f51f 100644 --- a/src/calibre/gui2/search_box.py +++ b/src/calibre/gui2/search_box.py @@ -392,7 +392,7 @@ class SearchBoxMixin(object): self.tags_view.clear() def do_advanced_search(self, *args): - d = SearchDialog(self) + d = SearchDialog(self, self.library_view.model().db) if d.exec_() == QDialog.Accepted: self.search.set_search_string(d.search_string()) diff --git a/src/calibre/gui2/viewer/main.py b/src/calibre/gui2/viewer/main.py index 4a098785b1..81a372a3ca 100644 --- a/src/calibre/gui2/viewer/main.py +++ b/src/calibre/gui2/viewer/main.py @@ -472,8 +472,12 @@ class EbookViewer(MainWindow, Ui_EbookViewer): if path != self.current_page: self.pending_anchor = frag self.load_path(path) - elif frag: - self.view.scroll_to(frag) + else: + if frag: + self.view.scroll_to(frag) + else: + # Scroll to top + self.view.scroll_to('#') else: open_url(url) diff --git a/src/calibre/library/server/browse.py b/src/calibre/library/server/browse.py index 9530a34c73..5609416273 100644 --- a/src/calibre/library/server/browse.py +++ b/src/calibre/library/server/browse.py @@ -245,6 +245,9 @@ class BrowseServer(object): ans = ans.replace('{sort_select_label}', xml(_('Sort by')+':')) ans = ans.replace('{sort_cookie_name}', scn) ans = ans.replace('{prefix}', self.opts.url_prefix) + ans = ans.replace('{library}', _('library')) + ans = ans.replace('{home}', _('home')) + ans = ans.replace('{Search}', _('Search')) opts = ['' % ( 'selected="selected" ' if k==sort else '', xml(k), xml(n), ) for k, n in diff --git a/src/calibre/manual/conversion.rst b/src/calibre/manual/conversion.rst index cfc2871396..fea20a3163 100644 --- a/src/calibre/manual/conversion.rst +++ b/src/calibre/manual/conversion.rst @@ -377,7 +377,7 @@ They are XPath expressions that match tags in the intermediate XHTML produced by how to construct XPath expressions. Next to each option is a button that launches a wizard to help with the creation of basic XPath expressions. The following simple example illustrates how to use these options. -Suppose you have an input document taht results in XHTML that look like this: +Suppose you have an input document that results in XHTML that look like this: .. code-block:: html @@ -418,6 +418,25 @@ This will result in an automatically generated two level Table of Contents that Not all output formats support a multi level Table of Contents. You should first try with EPUB Output. If that works, then try your format of choice. +Using images as chapter titles when converting HTML input documents +--------------------------------------------------------------------- + +Suppose you want to use an image as your chapter title, but still want |app| to be able to automatically generate a Table of Contents for you from the chapter titles. +Use the following HTML markup to achieve this + +.. code-block:: html + + + +

Chapter 1

+

chapter 1 text...

+

+

chapter 2 text...

+ + + +Set the :guilabel:`Level 1 TOC` setting to ``//h:h2``. Then, for chapter two, |app| will take the title from the value of the ``title`` attribute on the ``

`` tag, since the tag has no text. + How options are set/saved for Conversion ------------------------------------------- diff --git a/src/calibre/manual/gui.rst b/src/calibre/manual/gui.rst index 4d75400b7d..c81688ba8c 100644 --- a/src/calibre/manual/gui.rst +++ b/src/calibre/manual/gui.rst @@ -380,6 +380,8 @@ Calibre has several keyboard shortcuts to save you time and mouse movement. Thes - Show books in the same series as current book * - :kbd:`/, Ctrl+F` - Focus the search bar + * - :kbd:`Shift+Ctrl+F` + - Open the advanced search dialog * - :kbd:`Ctrl+D` - Download metadata and shortcuts * - :kbd:`Ctrl+R` diff --git a/src/calibre/translations/calibre.pot b/src/calibre/translations/calibre.pot index 4e25612b5d..350cb14bca 100644 --- a/src/calibre/translations/calibre.pot +++ b/src/calibre/translations/calibre.pot @@ -5,8 +5,8 @@ msgid "" msgstr "" "Project-Id-Version: calibre 0.7.27\n" -"POT-Creation-Date: 2010-11-05 15:17+MDT\n" -"PO-Revision-Date: 2010-11-05 15:17+MDT\n" +"POT-Creation-Date: 2010-11-06 09:35+MDT\n" +"PO-Revision-Date: 2010-11-06 09:35+MDT\n" "Last-Translator: Automatically generated\n" "Language-Team: LANGUAGE\n" "MIME-Version: 1.0\n" @@ -1932,7 +1932,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:313 #: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1127 #: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:160 -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:620 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:623 msgid "Tags" msgstr "" @@ -2299,7 +2299,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/jacket.py:159 #: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:71 -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:618 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:621 msgid "Rating" msgstr "" @@ -3570,7 +3570,7 @@ msgid "Click the show details button to see which ones." msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/show_book_details.py:16 -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:625 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:628 msgid "Show book details" msgstr "" @@ -3878,7 +3878,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:25 #: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:49 #: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:58 -#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:402 +#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:403 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:119 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:120 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:121 @@ -3925,7 +3925,7 @@ msgstr "" msgid "None" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:401 +#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:402 msgid "Double-click to open Book Details window" msgstr "" @@ -7601,7 +7601,7 @@ msgid "Successfully downloaded metadata for %d out of %d books" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/metadata.py:287 -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:624 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:627 msgid "Details" msgstr "" @@ -8581,6 +8581,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/search_box.py:97 #: /home/kovid/work/calibre/src/calibre/gui2/search_box.py:270 #: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:574 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:250 msgid "Search" msgstr "" @@ -10477,7 +10478,7 @@ msgid "Password to access your calibre library. Username is " msgstr "" #: /home/kovid/work/calibre/src/calibre/library/server/browse.py:51 -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:402 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:405 msgid "Loading, please wait" msgstr "" @@ -10522,70 +10523,78 @@ msgstr "" msgid "Sort by" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:307 -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:513 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:248 +msgid "library" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:249 +msgid "home" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:310 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:516 #: /home/kovid/work/calibre/src/calibre/library/server/opds.py:569 msgid "Newest" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:308 -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:514 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:311 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:517 msgid "All books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:341 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:344 msgid "Browse books by" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:346 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:349 msgid "Choose a category to browse by:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:422 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:425 msgid "Browsing by" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:423 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:426 msgid "Up" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:544 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:547 msgid "in" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:547 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:550 msgid "Books in" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:599 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:602 msgid "Other formats" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:606 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:609 msgid "Read %s in the %s format" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:611 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:614 msgid "Get" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:626 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:629 msgid "Permalink" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:627 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:630 msgid "A permanent link to this book" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:638 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:641 msgid "This book has been deleted" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:722 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:725 msgid "in search" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:724 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:727 msgid "Matching books" msgstr ""