merge from trunk
@ -31,3 +31,4 @@ nbproject/
|
|||||||
.pydevproject
|
.pydevproject
|
||||||
.settings/
|
.settings/
|
||||||
*.DS_Store
|
*.DS_Store
|
||||||
|
calibre_plugins/
|
@ -19,6 +19,72 @@
|
|||||||
# new recipes:
|
# new recipes:
|
||||||
# - title:
|
# - title:
|
||||||
|
|
||||||
|
- version: 0.8.2
|
||||||
|
date: 2011-05-20
|
||||||
|
|
||||||
|
new features:
|
||||||
|
- title: "Various new ebook sources added to Get Books: Google Books, O'Reilly, archive.org, some Polish ebooks stores, etc."
|
||||||
|
|
||||||
|
- title: "Amazon metadata download: Allow user to configure Amazon plugin to use any of the US, UK, German, French and Italian Amazon websites"
|
||||||
|
|
||||||
|
- title: "When deleting large numbers of books, give the user the option to skip the Recycle Bin, since sending lots of files to the recycle bin can be very slow."
|
||||||
|
tickets: [784987]
|
||||||
|
|
||||||
|
- title: "OS X: The unified title+toolbar was disabled as it had various bugs. If you really want it you can turn it on again via Preferences->Tweaks, but be aware that you will see problems like the calibre windowd being too wide, weird animations when a device is detected, etc."
|
||||||
|
|
||||||
|
- title: "Add a tweak that controls what words are treated as suffixes when generating an author sort string from an author name."
|
||||||
|
|
||||||
|
- title: "Get Books: Store last few searches in history"
|
||||||
|
|
||||||
|
bug fixes:
|
||||||
|
- title: "Fix a crash when a device is connected/disconnected while a modal dialog opened from the toolbar is visible"
|
||||||
|
tickets: [780484]
|
||||||
|
|
||||||
|
- title: "Fix incorrect results from ebooks.com when searching via Get Books"
|
||||||
|
|
||||||
|
- title: "Metadata plugboards: Add prioritization scheme to allow for using different settings for different locations"
|
||||||
|
tickets: [783229]
|
||||||
|
|
||||||
|
- title: "Fix manage authors dialog too wide"
|
||||||
|
tickets: [783065]
|
||||||
|
|
||||||
|
- title: "Fix multiple bracket types in author names not handled correctly when generating author sort string"
|
||||||
|
tickets: [782551]
|
||||||
|
|
||||||
|
- title: "MOBI Input: Don't error out when detecting TOC structure if one of the elements has an invalid margin unit"
|
||||||
|
|
||||||
|
- title: "More fixes for japanese language calibre on windows"
|
||||||
|
tickets: [782408]
|
||||||
|
|
||||||
|
- title: "Linux binaries: Always use either Cleanlook or Plastique styles for the GUI if no style can be loaded from the host computer"
|
||||||
|
|
||||||
|
improved recipes:
|
||||||
|
- Newsweek
|
||||||
|
- Economist
|
||||||
|
- Dvhn
|
||||||
|
- United Daily
|
||||||
|
- Dagens Nyheter
|
||||||
|
- GoComics
|
||||||
|
- faz.net
|
||||||
|
- golem.de
|
||||||
|
|
||||||
|
new recipes:
|
||||||
|
- title: National Geographic
|
||||||
|
author: gagsays
|
||||||
|
|
||||||
|
- title: Various German news sources
|
||||||
|
author: schuster
|
||||||
|
|
||||||
|
- title: Dilema Veche
|
||||||
|
author: Silviu Cotoara
|
||||||
|
|
||||||
|
- title: "Glamour, Good to Know, Good Housekeeping and Men's Health"
|
||||||
|
author: Anonymous
|
||||||
|
|
||||||
|
- title: "Financial Sense and iProfessional"
|
||||||
|
author: Darko Miletic
|
||||||
|
|
||||||
|
|
||||||
- version: 0.8.1
|
- version: 0.8.1
|
||||||
date: 2011-05-13
|
date: 2011-05-13
|
||||||
|
|
||||||
|
42
recipes/aachener_nachrichten.recipe
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||||
|
class AdvancedUserRecipe(BasicNewsRecipe):
|
||||||
|
|
||||||
|
title = u'Aachener Nachrichten'
|
||||||
|
__author__ = 'schuster'
|
||||||
|
oldest_article = 1
|
||||||
|
max_articles_per_feed = 100
|
||||||
|
use_embedded_content = False
|
||||||
|
language = 'de'
|
||||||
|
remove_javascript = True
|
||||||
|
cover_url = 'http://www.an-online.de/einwaage/images/an_logo.png'
|
||||||
|
masthead_url = 'http://www.an-online.de/einwaage/images/an_logo.png'
|
||||||
|
extra_css = '''
|
||||||
|
.fliesstext_detail:{margin-bottom:10%;}
|
||||||
|
.headline_1:{margin-bottom:25%;}
|
||||||
|
b{font-family:Arial,Helvetica,sans-serif; font-weight:200;font-size:large;}
|
||||||
|
a{font-family:Arial,Helvetica,sans-serif; font-weight:400;font-size:large;}
|
||||||
|
ll{font-family:Arial,Helvetica,sans-serif; font-weight:100;font-size:large;}
|
||||||
|
h4{font-family:Arial,Helvetica,sans-serif; font-weight:normal;font-size:small;}
|
||||||
|
img {min-width:300px; max-width:600px; min-height:300px; max-height:800px}
|
||||||
|
dd{font-family:Arial,Helvetica,sans-serif;font-size:large;}
|
||||||
|
body{font-family:Helvetica,Arial,sans-serif;font-size:small;}
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
keep_only_tags = [
|
||||||
|
dict(name='span', attrs={'class':['fliesstext_detail', 'headline_1', 'autor_detail']}),
|
||||||
|
dict(id=['header-logo'])
|
||||||
|
]
|
||||||
|
|
||||||
|
feeds = [(u'Euregio', u'http://www.an-online.de/an/rss/Euregio.xml'),
|
||||||
|
(u'Aachen', u'http://www.an-online.de/an/rss/Aachen.xml'),
|
||||||
|
(u'Nordkreis', u'http://www.an-online.de/an/rss/Nordkreis.xml'),
|
||||||
|
(u'Düren', u'http://www.an-online.de/an/rss/Dueren.xml'),
|
||||||
|
(u'Eiffel', u'http://www.an-online.de/an/rss/Eifel.xml'),
|
||||||
|
(u'Eschweiler', u'http://www.an-online.de/an/rss/Eschweiler.xml'),
|
||||||
|
(u'Geilenkirchen', u'http://www.an-online.de/an/rss/Geilenkirchen.xml'),
|
||||||
|
(u'Heinsberg', u'http://www.an-online.de/an/rss/Heinsberg.xml'),
|
||||||
|
(u'Jülich', u'http://www.an-online.de/an/rss/Juelich.xml'),
|
||||||
|
(u'Stolberg', u'http://www.an-online.de/an/rss/Stolberg.xml'),
|
||||||
|
(u'Ratgebenr', u'http://www.an-online.de/an/rss/Ratgeber.xml')]
|
46
recipes/bild_de.recipe
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||||
|
class AdvancedUserRecipe1303841067(BasicNewsRecipe):
|
||||||
|
title = u'Bild.de'
|
||||||
|
__author__ = 'schuster'
|
||||||
|
oldest_article = 1
|
||||||
|
max_articles_per_feed = 50
|
||||||
|
no_stylesheets = True
|
||||||
|
use_embedded_content = False
|
||||||
|
language = 'de'
|
||||||
|
remove_javascript = True
|
||||||
|
|
||||||
|
# get cover from myspace
|
||||||
|
cover_url = 'http://a3.l3-images.myspacecdn.com/images02/56/0232f842170b4d349779f8379c27e073/l.jpg'
|
||||||
|
|
||||||
|
# set what to fetch on the site
|
||||||
|
remove_tags_before = dict(name = 'h2', attrs={'id':'cover'})
|
||||||
|
remove_tags_after = dict(name ='div', attrs={'class':'back'})
|
||||||
|
|
||||||
|
# thanx to kiklop74 for code (see sticky thread -> Recipes - Re-usable code)
|
||||||
|
# this one removes a lot of direct-link's
|
||||||
|
def preprocess_html(self, soup):
|
||||||
|
for alink in soup.findAll('a'):
|
||||||
|
if alink.string is not None:
|
||||||
|
tstr = alink.string
|
||||||
|
alink.replaceWith(tstr)
|
||||||
|
return soup
|
||||||
|
|
||||||
|
# remove the ad's
|
||||||
|
filter_regexps = [r'.\.smartadserver\.com']
|
||||||
|
def skip_ad_pages(self, soup):
|
||||||
|
return None
|
||||||
|
|
||||||
|
#get the real url behind .feedsportal.com and fetch the artikels
|
||||||
|
def get_article_url(self, article):
|
||||||
|
return article.get('id', article.get('guid', None))
|
||||||
|
|
||||||
|
#list of the rss source from www.bild.de
|
||||||
|
feeds = [(u'Überblick', u'http://rss.bild.de/bild.xml'),
|
||||||
|
(u'News', u'http://rss.bild.de/bild-news.xml'),
|
||||||
|
(u'Politik', u'http://rss.bild.de/bild-politik.xml'),
|
||||||
|
(u'Unterhaltung', u'http://rss.bild.de/bild-unterhaltung.xml'),
|
||||||
|
(u'Sport', u'http://rss.bild.de/bild-sport.xml'),
|
||||||
|
(u'Lifestyle', u'http://rss.bild.de/bild-lifestyle.xml'),
|
||||||
|
(u'Ratgeber', u'http://rss.bild.de/bild-ratgeber.xml')
|
||||||
|
]
|
33
recipes/borse_online.recipe
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||||
|
class AdvancedUserRecipe1303841067(BasicNewsRecipe):
|
||||||
|
|
||||||
|
title = u'Börse-online'
|
||||||
|
__author__ = 'schuster'
|
||||||
|
oldest_article = 1
|
||||||
|
max_articles_per_feed = 100
|
||||||
|
no_stylesheets = True
|
||||||
|
use_embedded_content = False
|
||||||
|
language = 'de'
|
||||||
|
remove_javascript = True
|
||||||
|
cover_url = 'http://www.dpv.de/images/1995/source.gif'
|
||||||
|
masthead_url = 'http://www.zeitschriften-cover.de/cover/boerse-online-cover-januar-2010-x1387.jpg'
|
||||||
|
extra_css = '''
|
||||||
|
h1{font-family:Arial,Helvetica,sans-serif; font-weight:bold;font-size:large;}
|
||||||
|
h4{font-family:Arial,Helvetica,sans-serif; font-weight:normal;font-size:small;}
|
||||||
|
img {min-width:300px; max-width:600px; min-height:300px; max-height:800px}
|
||||||
|
p{font-family:Arial,Helvetica,sans-serif;font-size:small;}
|
||||||
|
body{font-family:Helvetica,Arial,sans-serif;font-size:small;}
|
||||||
|
'''
|
||||||
|
remove_tags_bevor = [dict(name='h3')]
|
||||||
|
remove_tags_after = [dict(name='div', attrs={'class':'artikelfuss'})]
|
||||||
|
remove_tags = [dict(attrs={'class':['moduleTopNav', 'moduleHeaderNav', 'text', 'blau', 'poll1150']}),
|
||||||
|
dict(id=['newsletterlayer', 'newsletterlayerClose', 'newsletterlayer_body', 'newsletterarray_error', 'newsletterlayer_emailadress', 'newsletterlayer_submit', 'kommentar']),
|
||||||
|
dict(name=['h2', 'Gesamtranking', 'h3',''])]
|
||||||
|
|
||||||
|
def print_version(self, url):
|
||||||
|
return url.replace('.html#nv=rss', '.html?mode=print')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
feeds = [(u'Börsennachrichten', u'http://www.boerse-online.de/rss/')]
|
||||||
|
|
61
recipes/capital_de.recipe
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
class AdvancedUserRecipe1305470859(BasicNewsRecipe):
|
||||||
|
title = u'Capital.de'
|
||||||
|
language = 'de'
|
||||||
|
__author__ = 'schuster'
|
||||||
|
oldest_article =7
|
||||||
|
max_articles_per_feed = 35
|
||||||
|
no_stylesheets = True
|
||||||
|
remove_javascript = True
|
||||||
|
use_embedded_content = False
|
||||||
|
masthead_url = 'http://www.wirtschaftsmedien-shop.de/media/stores/wirtschaftsmedien/capital/teaser_large_abo.jpg'
|
||||||
|
cover_url = 'http://d1kb9jvg6ylufe.cloudfront.net/WebsiteCMS/de/unternehmen/linktipps/mainColumn/08/image/DE_Capital_bis20mm_SW.jpg'
|
||||||
|
|
||||||
|
extra_css = '''
|
||||||
|
h1{font-family:Arial,Helvetica,sans-serif; font-weight:bold;font-size:large;}
|
||||||
|
h4{font-family:Arial,Helvetica,sans-serif; font-weight:normal;font-size:small;}
|
||||||
|
img {min-width:300px; max-width:600px; min-height:300px; max-height:800px}
|
||||||
|
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):
|
||||||
|
return url.replace ('nv=rss#utm_source=rss2&utm_medium=rss_feed&utm_campaign=/', 'mode=print')
|
||||||
|
remove_tags_bevor = [dict(name='td', attrs={'class':'textcell'})]
|
||||||
|
remove_tags_after = [dict(name='div', attrs={'class':'artikelsplit'})]
|
||||||
|
|
||||||
|
feeds = [ (u'Wirtschaftsmagazin', u'http://www.capital.de/rss/'),
|
||||||
|
(u'Unternehmen', u'http://www.capital.de/rss/unternehmen'),
|
||||||
|
(u'Finanz & Geldanlage', u'http://www.capital.de/rss/finanzen/geldanlage')]
|
||||||
|
|
||||||
|
def append_page(self, soup, appendtag, position):
|
||||||
|
pager = soup.find('div',attrs={'class':'artikelsplit'})
|
||||||
|
if pager:
|
||||||
|
nexturl = self.INDEX + pager.a['href']
|
||||||
|
soup2 = self.index_to_soup(nexturl)
|
||||||
|
texttag = soup2.find('div', attrs={'class':'printable'})
|
||||||
|
for it in texttag.findAll(style=True):
|
||||||
|
del it['style']
|
||||||
|
newpos = len(texttag.contents)
|
||||||
|
self.append_page(soup2,texttag,newpos)
|
||||||
|
texttag.extract()
|
||||||
|
appendtag.insert(position,texttag)
|
||||||
|
|
||||||
|
|
||||||
|
def preprocess_html(self, soup):
|
||||||
|
for item in soup.findAll(style=True):
|
||||||
|
del item['style']
|
||||||
|
for item in soup.findAll('div', attrs={'class':'artikelsplit'}):
|
||||||
|
item.extract()
|
||||||
|
self.append_page(soup, soup.body, 3)
|
||||||
|
pager = soup.find('div',attrs={'class':'artikelsplit'})
|
||||||
|
if pager:
|
||||||
|
pager.extract()
|
||||||
|
return self.adeify_images(soup)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
remove_tags = [dict(attrs={'class':['navSeitenAlle', 'kommentieren', 'teaserheader', 'teasercontent', 'info', 'zwischenhead', 'artikelsplit']}),
|
||||||
|
dict(id=['topNav', 'mainNav', 'subNav', 'socialmedia', 'footerRahmen', 'gatrixx_marktinformationen', 'pager', 'weitere']),
|
||||||
|
dict(span=['ratingtext', 'Gesamtranking', 'h3','']),
|
||||||
|
dict(rel=['canonical'])]
|
||||||
|
|
34
recipes/cosmopolitan_de.recipe
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
|
||||||
|
class AdvancedUserRecipe1305567197(BasicNewsRecipe):
|
||||||
|
title = u'Cosmopolitan.de'
|
||||||
|
__author__ = 'schuster'
|
||||||
|
oldest_article = 7
|
||||||
|
language = 'de'
|
||||||
|
max_articles_per_feed = 100
|
||||||
|
no_stylesheets = True
|
||||||
|
use_embedded_content = False
|
||||||
|
remove_javascript = True
|
||||||
|
cover_url = 'http://www.cosmopolitan.com/cm/shared/site_images/print_this/cosmopolitan_logo.gif'
|
||||||
|
remove_tags_before = dict(name = 'h1', attrs={'class':'artikel'})
|
||||||
|
remove_tags_after = dict(name ='div', attrs={'class':'morePages'})
|
||||||
|
extra_css = '''
|
||||||
|
h2{font-family:Arial,Helvetica,sans-serif; font-size: x-small;}
|
||||||
|
h1{ font-family:Arial,Helvetica,sans-serif; font-size:x-large; font-weight:bold;}
|
||||||
|
'''
|
||||||
|
remove_tags = [ dict(id='strong'),
|
||||||
|
dict(title='strong'),
|
||||||
|
dict(name='span'),
|
||||||
|
dict(name='li', attrs={'class':'large'}),
|
||||||
|
dict(name='ul', attrs={'class':'articleImagesPortrait clearfix'}),
|
||||||
|
dict(name='p', attrs={'class':'external'}),
|
||||||
|
dict(name='a', attrs={'target':'_blank'}),]
|
||||||
|
feeds = [ (u'Komplett', u'http://www.cosmopolitan.de/rss/allgemein.xml'),
|
||||||
|
(u'Mode', u'http://www.cosmopolitan.de/rss/mode.xml'),
|
||||||
|
(u'Beauty', u'http://www.cosmopolitan.de/rss/beauty.xml'),
|
||||||
|
(u'Liebe&Sex', u'http://www.cosmopolitan.de/rss/liebe.xml'),
|
||||||
|
(u'Psychologie', u'http://www.cosmopolitan.de/rss/psychologie.xml'),
|
||||||
|
(u'Job&Karriere', u'http://www.cosmopolitan.de/rss/job.xml'),
|
||||||
|
(u'Lifestyle', u'http://www.cosmopolitan.de/rss/lifestyle.xml'),
|
||||||
|
(u'Shopping', u'http://www.cosmopolitan.de/rss/shopping.xml'),
|
||||||
|
(u'Bildergalerien', u'http://www.cosmopolitan.de/rss/bildgalerien.xml')]
|
55
recipes/dilemaveche.recipe
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = u'2011, Silviu Cotoar\u0103'
|
||||||
|
'''
|
||||||
|
dilemaveche.ro
|
||||||
|
'''
|
||||||
|
|
||||||
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
|
||||||
|
class DilemaVeche(BasicNewsRecipe):
|
||||||
|
title = u'Dilema Veche'
|
||||||
|
__author__ = u'Silviu Cotoar\u0103'
|
||||||
|
description = u'Sunt vechi, domnule!'
|
||||||
|
publisher = u'Dilema Veche'
|
||||||
|
oldest_article = 50
|
||||||
|
language = 'ro'
|
||||||
|
max_articles_per_feed = 100
|
||||||
|
no_stylesheets = True
|
||||||
|
use_embedded_content = False
|
||||||
|
category = 'Ziare'
|
||||||
|
encoding = 'utf-8'
|
||||||
|
cover_url = 'http://www.dilemaveche.ro/sites/all/themes/dilema/theme/dilema_two/layouter/dilema_two_homepage/logo.png'
|
||||||
|
|
||||||
|
conversion_options = {
|
||||||
|
'comments' : description
|
||||||
|
,'tags' : category
|
||||||
|
,'language' : language
|
||||||
|
,'publisher' : publisher
|
||||||
|
}
|
||||||
|
|
||||||
|
keep_only_tags = [
|
||||||
|
dict(name='h1', attrs={'class':'art_title'})
|
||||||
|
, dict(name='h1', attrs={'class':'art_title online'})
|
||||||
|
, dict(name='div', attrs={'class':'item'})
|
||||||
|
, dict(name='div', attrs={'class':'art_content'})
|
||||||
|
]
|
||||||
|
|
||||||
|
remove_tags = [
|
||||||
|
dict(name='div', attrs={'class':['article_details']})
|
||||||
|
, dict(name='div', attrs={'class':['controale']})
|
||||||
|
, dict(name='div', attrs={'class':['art_related_left']})
|
||||||
|
]
|
||||||
|
|
||||||
|
remove_tags_after = [
|
||||||
|
dict(name='div', attrs={'class':['article_details']})
|
||||||
|
]
|
||||||
|
|
||||||
|
feeds = [
|
||||||
|
(u'Feeds', u'http://www.dilemaveche.ro/rss.xml')
|
||||||
|
]
|
||||||
|
|
||||||
|
def preprocess_html(self, soup):
|
||||||
|
return self.adeify_images(soup)
|
@ -37,7 +37,7 @@ class DN_se(BasicNewsRecipe):
|
|||||||
,(u'Kultur' , u'http://www.dn.se/kultur-rss' )
|
,(u'Kultur' , u'http://www.dn.se/kultur-rss' )
|
||||||
]
|
]
|
||||||
|
|
||||||
keep_only_tags = [dict(name='div', attrs={'id':'article'})]
|
keep_only_tags = [dict(name='div', attrs={'id':'article-content'})]
|
||||||
remove_tags_before = dict(name='h1')
|
remove_tags_before = dict(name='h1')
|
||||||
remove_tags_after = dict(name='div',attrs={'id':'byline'})
|
remove_tags_after = dict(name='div',attrs={'id':'byline'})
|
||||||
remove_tags = [
|
remove_tags = [
|
||||||
|
@ -1,19 +1,21 @@
|
|||||||
|
import re
|
||||||
from calibre.web.feeds.news import BasicNewsRecipe
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
|
||||||
class AdvancedUserRecipe1302341394(BasicNewsRecipe):
|
class AdvancedUserRecipe1302341394(BasicNewsRecipe):
|
||||||
title = u'DvhN'
|
title = u'DvhN'
|
||||||
oldest_article = 1
|
__author__ = 'Reijndert'
|
||||||
|
oldest_article = 7
|
||||||
max_articles_per_feed = 200
|
max_articles_per_feed = 200
|
||||||
|
|
||||||
__author__ = 'Reijndert'
|
|
||||||
no_stylesheets = True
|
no_stylesheets = True
|
||||||
cover_url = 'http://www.dvhn.nl/template/Dagblad_v2.0/gfx/logo_DvhN.gif'
|
cover_url = 'http://members.home.nl/apm.de.haas/calibre/DvhN.jpg'
|
||||||
language = 'nl'
|
language = 'nl'
|
||||||
country = 'NL'
|
country = 'NL'
|
||||||
version = 1
|
version = 1
|
||||||
publisher = u'Dagblad van het Noorden'
|
publisher = u'Dagblad van het Noorden'
|
||||||
category = u'Nieuws'
|
category = u'Nieuws'
|
||||||
description = u'Nieuws uit Noord Nederland'
|
description = u'Nieuws uit Noord Nederland'
|
||||||
|
timefmt = ' %Y-%m-%d (%a)'
|
||||||
|
|
||||||
|
|
||||||
keep_only_tags = [dict(name='div', attrs={'id':'fullPicture'})
|
keep_only_tags = [dict(name='div', attrs={'id':'fullPicture'})
|
||||||
@ -21,11 +23,26 @@ class AdvancedUserRecipe1302341394(BasicNewsRecipe):
|
|||||||
]
|
]
|
||||||
|
|
||||||
remove_tags = [
|
remove_tags = [
|
||||||
dict(name=['object','link','iframe','base'])
|
dict(name='span',attrs={'class':'location'})
|
||||||
,dict(name='span',attrs={'class':'copyright'})
|
|
||||||
]
|
]
|
||||||
|
|
||||||
feeds = [(u'Drenthe', u'http://www.dvhn.nl/nieuws/drenthe/index.jsp?service=rss'), (u'Groningen', u'http://www.dvhn.nl/nieuws/groningen/index.jsp?service=rss'), (u'Nederland', u'http://www.dvhn.nl/nieuws/nederland/index.jsp?service=rss'), (u'Wereld', u'http://www.dvhn.nl/nieuws/wereld/index.jsp?service=rss'), (u'Economie', u'http://www.dvhn.nl/nieuws/economie/index.jsp?service=rss'), (u'Sport', u'http://www.dvhn.nl/nieuws/sport/index.jsp?service=rss'), (u'Cultuur', u'http://www.dvhn.nl/nieuws/kunst/index.jsp?service=rss'), (u'24 Uur', u'http://www.dvhn.nl/nieuws/24uurdvhn/index.jsp?service=rss&selectiontype=last24hours')]
|
preprocess_regexps = [
|
||||||
|
(re.compile(r'<a.*?>'), lambda h1: '')
|
||||||
|
,(re.compile(r'</a>'), lambda h2: '')
|
||||||
|
,(re.compile(r'Word vriend van Dagblad van het Noorden op Facebook'), lambda h3: '')
|
||||||
|
,(re.compile(r'Volg Dagblad van het Noorden op Twitter'), lambda h3: '')
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
feeds = [(u'Drenthe', u'http://www.dvhn.nl/nieuws/drenthe/index.jsp?service=rss')
|
||||||
|
, (u'Groningen', u'http://www.dvhn.nl/nieuws/groningen/index.jsp?service=rss')
|
||||||
|
, (u'Nederland', u'http://www.dvhn.nl/nieuws/nederland/index.jsp?service=rss')
|
||||||
|
, (u'Wereld', u'http://www.dvhn.nl/nieuws/wereld/index.jsp?service=rss')
|
||||||
|
, (u'Economie', u'http://www.dvhn.nl/nieuws/economie/index.jsp?service=rss')
|
||||||
|
, (u'Sport', u'http://www.dvhn.nl/nieuws/sport/index.jsp?service=rss')
|
||||||
|
, (u'Cultuur', u'http://www.dvhn.nl/nieuws/kunst/index.jsp?service=rss')
|
||||||
|
, (u'24 Uur', u'http://www.dvhn.nl/nieuws/24uurdvhn/index.jsp?service=rss&selectiontype=last24hours')
|
||||||
|
]
|
||||||
|
|
||||||
extra_css = '''
|
extra_css = '''
|
||||||
body {font-family: verdana, arial, helvetica, geneva, sans-serif;}
|
body {font-family: verdana, arial, helvetica, geneva, sans-serif;}
|
||||||
|
@ -20,7 +20,7 @@ class Economist(BasicNewsRecipe):
|
|||||||
INDEX = 'http://www.economist.com/printedition'
|
INDEX = 'http://www.economist.com/printedition'
|
||||||
description = ('Global news and current affairs from a European'
|
description = ('Global news and current affairs from a European'
|
||||||
' perspective. Best downloaded on Friday mornings (GMT)')
|
' perspective. Best downloaded on Friday mornings (GMT)')
|
||||||
|
extra_css = '.headline {font-size: x-large;} \n h2 { font-size: small; } \n h1 { font-size: medium; }'
|
||||||
oldest_article = 7.0
|
oldest_article = 7.0
|
||||||
cover_url = 'http://www.economist.com/images/covers/currentcoverus_large.jpg'
|
cover_url = 'http://www.economist.com/images/covers/currentcoverus_large.jpg'
|
||||||
remove_tags = [
|
remove_tags = [
|
||||||
|
@ -14,7 +14,7 @@ class Economist(BasicNewsRecipe):
|
|||||||
description = ('Global news and current affairs from a European'
|
description = ('Global news and current affairs from a European'
|
||||||
' perspective. Best downloaded on Friday mornings (GMT).'
|
' perspective. Best downloaded on Friday mornings (GMT).'
|
||||||
' Much slower than the print edition based version.')
|
' Much slower than the print edition based version.')
|
||||||
|
extra_css = '.headline {font-size: x-large;} \n h2 { font-size: small; } \n h1 { font-size: medium; }'
|
||||||
oldest_article = 7.0
|
oldest_article = 7.0
|
||||||
cover_url = 'http://www.economist.com/images/covers/currentcoverus_large.jpg'
|
cover_url = 'http://www.economist.com/images/covers/currentcoverus_large.jpg'
|
||||||
remove_tags = [
|
remove_tags = [
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__copyright__ = '2009, Darko Miletic <darko.miletic at gmail.com>'
|
__copyright__ = '2009-2011, Darko Miletic <darko.miletic at gmail.com>'
|
||||||
'''
|
'''
|
||||||
elmundo.es
|
elmundo.es
|
||||||
'''
|
'''
|
||||||
@ -10,15 +10,24 @@ from calibre.web.feeds.news import BasicNewsRecipe
|
|||||||
class ElMundo(BasicNewsRecipe):
|
class ElMundo(BasicNewsRecipe):
|
||||||
title = 'El Mundo'
|
title = 'El Mundo'
|
||||||
__author__ = 'Darko Miletic'
|
__author__ = 'Darko Miletic'
|
||||||
description = 'News from Spain'
|
description = 'Lider de informacion en espaniol'
|
||||||
publisher = 'El Mundo'
|
publisher = 'Unidad Editorial Informacion General S.L.U.'
|
||||||
category = 'news, politics, Spain'
|
category = 'news, politics, Spain'
|
||||||
oldest_article = 2
|
oldest_article = 2
|
||||||
max_articles_per_feed = 100
|
max_articles_per_feed = 100
|
||||||
no_stylesheets = True
|
no_stylesheets = True
|
||||||
use_embedded_content = False
|
use_embedded_content = False
|
||||||
encoding = 'iso8859_15'
|
encoding = 'iso8859_15'
|
||||||
language = 'es'
|
language = 'es_ES'
|
||||||
|
masthead_url = 'http://estaticos03.elmundo.es/elmundo/iconos/v4.x/v4.01/bg_h1.png'
|
||||||
|
publication_type = 'newspaper'
|
||||||
|
extra_css = """
|
||||||
|
body{font-family: Arial,Helvetica,sans-serif}
|
||||||
|
.metadata_noticia{font-size: small}
|
||||||
|
h1,h2,h3,h4,h5,h6,.subtitulo {color: #3F5974}
|
||||||
|
.hora{color: red}
|
||||||
|
.update{color: gray}
|
||||||
|
"""
|
||||||
|
|
||||||
conversion_options = {
|
conversion_options = {
|
||||||
'comments' : description
|
'comments' : description
|
||||||
@ -30,22 +39,31 @@ class ElMundo(BasicNewsRecipe):
|
|||||||
keep_only_tags = [dict(name='div', attrs={'class':'noticia'})]
|
keep_only_tags = [dict(name='div', attrs={'class':'noticia'})]
|
||||||
remove_tags_before = dict(attrs={'class':['titular','antetitulo'] })
|
remove_tags_before = dict(attrs={'class':['titular','antetitulo'] })
|
||||||
remove_tags_after = dict(name='div' , attrs={'id':['desarrollo_noticia','tamano']})
|
remove_tags_after = dict(name='div' , attrs={'id':['desarrollo_noticia','tamano']})
|
||||||
|
remove_attributes = ['lang','border']
|
||||||
remove_tags = [
|
remove_tags = [
|
||||||
dict(name='div', attrs={'class':['herramientas','publicidad_google']})
|
dict(name='div', attrs={'class':['herramientas','publicidad_google']})
|
||||||
,dict(name='div', attrs={'id':'modulo_multimedia' })
|
,dict(name='div', attrs={'id':'modulo_multimedia' })
|
||||||
,dict(name='ul', attrs={'class':'herramientas' })
|
,dict(name='ul', attrs={'class':'herramientas' })
|
||||||
,dict(name=['object','link'])
|
,dict(name=['object','link','embed','iframe','base','meta'])
|
||||||
]
|
]
|
||||||
|
|
||||||
feeds = [
|
feeds = [
|
||||||
(u'Portada' , u'http://rss.elmundo.es/rss/descarga.htm?data2=4' )
|
(u'Portada' , u'http://estaticos.elmundo.es/elmundo/rss/portada.xml' )
|
||||||
,(u'Deportes' , u'http://rss.elmundo.es/rss/descarga.htm?data2=14')
|
,(u'Deportes' , u'http://estaticos.elmundo.es/elmundodeporte/rss/portada.xml')
|
||||||
,(u'Economia' , u'http://rss.elmundo.es/rss/descarga.htm?data2=7' )
|
,(u'Economia' , u'http://estaticos.elmundo.es/elmundo/rss/economia.xml' )
|
||||||
,(u'Espana' , u'http://rss.elmundo.es/rss/descarga.htm?data2=8' )
|
,(u'Espana' , u'http://estaticos.elmundo.es/elmundo/rss/espana.xml' )
|
||||||
,(u'Internacional' , u'http://rss.elmundo.es/rss/descarga.htm?data2=9' )
|
,(u'Internacional' , u'http://estaticos.elmundo.es/elmundo/rss/internacional.xml' )
|
||||||
,(u'Cultura' , u'http://rss.elmundo.es/rss/descarga.htm?data2=6' )
|
,(u'Cultura' , u'http://estaticos.elmundo.es/elmundo/rss/cultura.xml' )
|
||||||
,(u'Ciencia/Ecologia', u'http://rss.elmundo.es/rss/descarga.htm?data2=5' )
|
,(u'Ciencia/Ecologia', u'http://estaticos.elmundo.es/elmundo/rss/ciencia.xml' )
|
||||||
,(u'Comunicacion' , u'http://rss.elmundo.es/rss/descarga.htm?data2=26')
|
,(u'Comunicacion' , u'http://estaticos.elmundo.es/elmundo/rss/comunicacion.xml' )
|
||||||
,(u'Television' , u'http://rss.elmundo.es/rss/descarga.htm?data2=76')
|
,(u'Television' , u'http://estaticos.elmundo.es/elmundo/rss/television.xml' )
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def preprocess_html(self, soup):
|
||||||
|
for item in soup.findAll(style=True):
|
||||||
|
del item['style']
|
||||||
|
return soup
|
||||||
|
|
||||||
|
def get_article_url(self, article):
|
||||||
|
return article.get('guid', None)
|
||||||
|
|
||||||
|
74
recipes/express_de.recipe
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
|
||||||
|
class AdvancedUserRecipe1303841067(BasicNewsRecipe):
|
||||||
|
|
||||||
|
title = u'Express.de'
|
||||||
|
__author__ = 'schuster'
|
||||||
|
oldest_article = 2
|
||||||
|
max_articles_per_feed = 50
|
||||||
|
no_stylesheets = True
|
||||||
|
use_embedded_content = False
|
||||||
|
language = 'de'
|
||||||
|
extra_css = '''
|
||||||
|
h2{font-family:Arial,Helvetica,sans-serif; font-size: x-small;}
|
||||||
|
h1{ font-family:Arial,Helvetica,sans-serif; font-size:x-large; font-weight:bold;}
|
||||||
|
|
||||||
|
'''
|
||||||
|
remove_javascript = True
|
||||||
|
remove_tags_befor = [dict(name='div', attrs={'class':'Datum'})]
|
||||||
|
remove_tags_after = [dict(name='div', attrs={'class':'MoreNews'})]
|
||||||
|
|
||||||
|
remove_tags = [dict(id='kalaydo'),
|
||||||
|
dict(id='Header'),
|
||||||
|
dict(id='Searchline'),
|
||||||
|
dict(id='MainNav'),
|
||||||
|
dict(id='Logo'),
|
||||||
|
dict(id='MainLinkSpacer'),
|
||||||
|
dict(id='MainLinks'),
|
||||||
|
dict(title='Diese Seite Bookmarken'),
|
||||||
|
|
||||||
|
dict(name='span'),
|
||||||
|
dict(name='div', attrs={'class':'spacer_leftneu'}),
|
||||||
|
dict(name='div', attrs={'class':'button kalaydologo'}),
|
||||||
|
dict(name='div', attrs={'class':'button stellenneu'}),
|
||||||
|
dict(name='div', attrs={'class':'button autoneu'}),
|
||||||
|
dict(name='div', attrs={'class':'button immobilienneu'}),
|
||||||
|
dict(name='div', attrs={'class':'button kleinanzeigen'}),
|
||||||
|
dict(name='div', attrs={'class':'button tiereneu'}),
|
||||||
|
dict(name='div', attrs={'class':'button ferienwohnungen'}),
|
||||||
|
dict(name='div', attrs={'class':'button inserierenneu'}),
|
||||||
|
dict(name='div', attrs={'class':'spacer_rightneu'}),
|
||||||
|
dict(name='div', attrs={'class':'spacer_rightcorner'}),
|
||||||
|
dict(name='div', attrs={'class':'HeaderMetaNav'}),
|
||||||
|
dict(name='div', attrs={'class':'HeaderSearchOption'}),
|
||||||
|
dict(name='div', attrs={'class':'HeaderSearch'}),
|
||||||
|
dict(name='div', attrs={'class':'sbutton'}),
|
||||||
|
dict(name='div', attrs={'class':'active'}),
|
||||||
|
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def preprocess_html(self, soup):
|
||||||
|
for alink in soup.findAll('a'):
|
||||||
|
if alink.string is not None:
|
||||||
|
tstr = alink.string
|
||||||
|
alink.replaceWith(tstr)
|
||||||
|
return soup
|
||||||
|
|
||||||
|
feeds = [(u'Top-Themen', u'http://www.express.de/home/-/2126/2126/-/view/asFeed/-/index.xml'),
|
||||||
|
(u'Regional - Köln', u'http://www.express.de/regional/koeln/-/2856/2856/-/view/asFeed/-/index.xml'),
|
||||||
|
(u'Regional - Bonn', u'http://www.express.de/regional/bonn/-/2860/2860/-/view/asFeed/-/index.xml'),
|
||||||
|
(u'Regional - Düsseldorf', u'http://www.express.de/regional/duesseldorf/-/2858/2858/-/view/asFeed/-/index.xml'),
|
||||||
|
(u'Regional - Region', u'http://www.express.de/regional/-/2178/2178/-/view/asFeed/-/index.xml'),
|
||||||
|
(u'Sport-News', u'http://www.express.de/sport/-/2176/2176/-/view/asFeed/-/index.xml'),
|
||||||
|
(u'Fussball-News', u'http://www.express.de/sport/fussball/-/3186/3186/-/view/asFeed/-/index.xml'),
|
||||||
|
(u'1.FC Köln News', u'http://www.express.de/sport/fussball/fc-koeln/-/3192/3192/-/view/asFeed/-/index.xml'),
|
||||||
|
(u'Alemannia Aachen News', u'http://www.express.de/sport/fussball/alemannia/-/3290/3290/-/view/asFeed/-/index.xml'),
|
||||||
|
(u'Borussia M~Gladbach', u'http://www.express.de/sport/fussball/gladbach/-/3286/3286/-/view/asFeed/-/index.xml'),
|
||||||
|
(u'Fortuna D~Dorf', u'http://www.express.de/sport/fussball/fortuna/-/3292/3292/-/view/asFeed/-/index.xml'),
|
||||||
|
(u'Basketball News', u'http://www.express.de/sport/basketball/-/3190/3190/-/view/asFeed/-/index.xml'),
|
||||||
|
(u'Big Brother', u'http://www.express.de/news/promi-show/big-brother/-/2402/2402/-/view/asFeed/-/index.xml'),
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
]
|
@ -1,51 +1,35 @@
|
|||||||
__license__ = 'GPL v3'
|
from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||||
__copyright__ = '2008-2009, Kovid Goyal <kovid at kovidgoyal.net>, Darko Miletic <darko at gmail.com>'
|
class AdvancedUserRecipe1303841067(BasicNewsRecipe):
|
||||||
'''
|
|
||||||
Profile to download FAZ.net
|
|
||||||
'''
|
|
||||||
|
|
||||||
from calibre.web.feeds.news import BasicNewsRecipe
|
title = u'Faz.net'
|
||||||
|
__author__ = 'schuster'
|
||||||
class FazNet(BasicNewsRecipe):
|
oldest_article = 1
|
||||||
title = 'FAZ NET'
|
|
||||||
__author__ = 'Kovid Goyal, Darko Miletic'
|
|
||||||
description = 'Frankfurter Allgemeine Zeitung'
|
description = 'Frankfurter Allgemeine Zeitung'
|
||||||
publisher = 'FAZ Electronic Media GmbH'
|
max_articles_per_feed = 100
|
||||||
category = 'news, politics, Germany'
|
no_stylesheets = True
|
||||||
use_embedded_content = False
|
use_embedded_content = False
|
||||||
language = 'de'
|
language = 'de'
|
||||||
|
remove_javascript = True
|
||||||
|
cover_url = 'http://www.faz.net/f30/Images/Logos/logo.gif'
|
||||||
|
|
||||||
max_articles_per_feed = 30
|
remove_tags = [dict(attrs={'class':['LinkBoxModulSmall', 'ModulLesermeinungenFooter', 'ModulArtikelServices', 'SocialMediaUnten', 'ArrowLinkRight', 'ModulVerlagsInfo', 'AdData', 'FazFooter', 'Date']}),
|
||||||
no_stylesheets = True
|
dict(id=['FAZNavHeader', 'FAZNavMain', 'RightColumn', 'FazFooter', 'BreadCrumbs', 'FAZNavSubMain', 'FAZImgEvent']),
|
||||||
encoding = 'utf-8'
|
dict(name=['jksrdt'])]
|
||||||
remove_javascript = True
|
|
||||||
|
|
||||||
html2lrf_options = [
|
|
||||||
'--comment', description
|
|
||||||
, '--category', category
|
|
||||||
, '--publisher', publisher
|
|
||||||
]
|
|
||||||
|
|
||||||
html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"'
|
|
||||||
|
|
||||||
keep_only_tags = [dict(name='div', attrs={'class':'Article'})]
|
|
||||||
|
|
||||||
remove_tags = [
|
|
||||||
dict(name=['object','link','embed','base'])
|
|
||||||
,dict(name='div', attrs={'class':['LinkBoxModulSmall','ModulVerlagsInfo']})
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
feeds = [ ('FAZ.NET', 'http://www.faz.net/s/Rub/Tpl~Epartner~SRss_.xml') ]
|
feeds = [(u'Politik', u'http://www.faz.net/s/RubA24ECD630CAE40E483841DB7D16F4211/Tpl~Epartner~SRss_.xml'),
|
||||||
|
(u'Wirtschaft', u'http://www.faz.net/s/RubC9401175958F4DE28E143E68888825F6/Tpl~Epartner~SRss_.xml'),
|
||||||
|
(u'Feuilleton', u'http://www.faz.net/s/RubCC21B04EE95145B3AC877C874FB1B611/Tpl~Epartner~SRss_.xml'),
|
||||||
|
(u'Sport', u'http://www.faz.net/s/Rub9F27A221597D4C39A82856B0FE79F051/Tpl~Epartner~SRss_.xml'),
|
||||||
|
(u'Gesellschaft', u'http://www.faz.net/s/Rub02DBAA63F9EB43CEB421272A670A685C/Tpl~Epartner~SRss_.xml'),
|
||||||
|
(u'Finanzen', u'http://www.faz.net/s/Rub4B891837ECD14082816D9E088A2D7CB4/Tpl~Epartner~SRss_.xml'),
|
||||||
|
(u'Wissen', u'http://www.faz.net/s/Rub7F4BEE0E0C39429A8565089709B70C44/Tpl~Epartner~SRss_.xml'),
|
||||||
|
(u'Reise', u'http://www.faz.net/s/RubE2FB5CA667054BDEA70FB3BC45F8D91C/Tpl~Epartner~SRss_.xml'),
|
||||||
|
(u'Technik & Motor', u'http://www.faz.net/s/Rub01E4D53776494844A85FDF23F5707AD8/Tpl~Epartner~SRss_.xml'),
|
||||||
|
(u'Beruf & Chance', u'http://www.faz.net/s/RubB1E10A8367E8446897468EDAA6EA0504/Tpl~Epartner~SRss_.xml'),
|
||||||
|
(u'Kunstmarkt', u'http://www.faz.net/s/RubBC09F7BF72A2405A96718ECBFB68FBFE/Tpl~Epartner~SRss_.xml'),
|
||||||
|
(u'Immobilien ', u'http://www.faz.net/s/RubFED172A9E10F46B3A5F01B02098C0C8D/Tpl~Epartner~SRss_.xml'),
|
||||||
|
(u'Rhein-Main Zeitung', u'http://www.faz.net/s/RubABE881A6669742C2A5EBCB5D50D7EBEE/Tpl~Epartner~SRss_.xml'),
|
||||||
|
(u'Atomdebatte ', u'http://www.faz.net/s/Rub469C43057F8C437CACC2DE9ED41B7950/Tpl~Epartner~SRss_.xml')
|
||||||
|
]
|
||||||
|
|
||||||
def print_version(self, url):
|
|
||||||
article, sep, rest = url.partition('?')
|
|
||||||
return article.replace('.html', '~Afor~Eprint.html')
|
|
||||||
|
|
||||||
def preprocess_html(self, soup):
|
|
||||||
mtag = '<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>'
|
|
||||||
soup.head.insert(0,mtag)
|
|
||||||
del soup.body['onload']
|
|
||||||
for item in soup.findAll(style=True):
|
|
||||||
del item['style']
|
|
||||||
return soup
|
|
||||||
|
48
recipes/focus_de.recipe
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
|
||||||
|
class AdvancedUserRecipe1305567197(BasicNewsRecipe):
|
||||||
|
title = u'Focus (DE)'
|
||||||
|
__author__ = 'Anonymous'
|
||||||
|
language = 'de'
|
||||||
|
oldest_article = 7
|
||||||
|
max_articles_per_feed = 100
|
||||||
|
no_stylesheets = True
|
||||||
|
use_embedded_content = False
|
||||||
|
remove_javascript = True
|
||||||
|
|
||||||
|
def print_version(self, url):
|
||||||
|
return url + '?drucken=1'
|
||||||
|
|
||||||
|
keep_only_tags = [
|
||||||
|
dict(name='div', attrs={'id':['article']}) ]
|
||||||
|
|
||||||
|
remove_tags = [dict(name='div', attrs={'class':'sidebar'}),
|
||||||
|
dict(name='div', attrs={'class':'commentForm'}),
|
||||||
|
dict(name='div', attrs={'class':'comment clearfix oid-3534591 open'}),
|
||||||
|
dict(name='div', attrs={'class':'similarityBlock'}),
|
||||||
|
dict(name='div', attrs={'class':'footer'}),
|
||||||
|
dict(name='div', attrs={'class':'getMoreComments'}),
|
||||||
|
dict(name='div', attrs={'class':'moreComments'}),
|
||||||
|
dict(name='div', attrs={'class':'ads'}),
|
||||||
|
dict(name='div', attrs={'class':'articleContent'}),
|
||||||
|
|
||||||
|
|
||||||
|
]
|
||||||
|
remove_tags_after = [
|
||||||
|
dict(name='div',attrs={'class':['commentForm','title', 'actions clearfix']})
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
feeds = [ (u'Eilmeldungen', u'http://rss2.focus.de/c/32191/f/533875/index.rss'),
|
||||||
|
(u'Auto-News', u'http://rss2.focus.de/c/32191/f/443320/index.rss'),
|
||||||
|
(u'Digital-News', u'http://rss2.focus.de/c/32191/f/443315/index.rss'),
|
||||||
|
(u'Finanzen-News', u'http://rss2.focus.de/c/32191/f/443317/index.rss'),
|
||||||
|
(u'Gesundheit-News', u'http://rss2.focus.de/c/32191/f/443314/index.rss'),
|
||||||
|
(u'Immobilien-News', u'http://rss2.focus.de/c/32191/f/443318/index.rss'),
|
||||||
|
(u'Kultur-News', u'http://rss2.focus.de/c/32191/f/443321/index.rss'),
|
||||||
|
(u'Panorama-News', u'http://rss2.focus.de/c/32191/f/533877/index.rss'),
|
||||||
|
(u'Politik-News', u'http://rss2.focus.de/c/32191/f/443313/index.rss'),
|
||||||
|
(u'Reisen-News', u'http://rss2.focus.de/c/32191/f/443316/index.rss'),
|
||||||
|
(u'Sport-News', u'http://rss2.focus.de/c/32191/f/443319/index.rss'),
|
||||||
|
(u'Wissen-News', u'http://rss2.focus.de/c/32191/f/533876/index.rss'),
|
||||||
|
]
|
35
recipes/frankfurter_rundschau.recipe
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||||
|
class AdvancedUserRecipe(BasicNewsRecipe):
|
||||||
|
|
||||||
|
title = u'Frankfurter Rundschau'
|
||||||
|
__author__ = 'schuster'
|
||||||
|
oldest_article = 1
|
||||||
|
max_articles_per_feed = 100
|
||||||
|
no_stylesheets = True
|
||||||
|
use_embedded_content = False
|
||||||
|
language = 'de'
|
||||||
|
remove_javascript = True
|
||||||
|
cover_url = 'http://www.fr-online.de/image/view/-/1474018/data/823538/-/logo.png'
|
||||||
|
extra_css = '''
|
||||||
|
h1{font-family:Arial,Helvetica,sans-serif; font-weight:bold;font-size:large;}
|
||||||
|
h4{font-family:Arial,Helvetica,sans-serif; font-weight:normal;font-size:small;}
|
||||||
|
img {min-width:300px; max-width:600px; min-height:300px; max-height:800px}
|
||||||
|
p{font-family:Arial,Helvetica,sans-serif;font-size:small;}
|
||||||
|
body{font-family:Helvetica,Arial,sans-serif;font-size:small;}
|
||||||
|
'''
|
||||||
|
|
||||||
|
feeds = [(u'Startseite', u'http://www.fr-online.de/home/-/1472778/1472778/-/view/asFeed/-/index.xml'),
|
||||||
|
(u'Politik', u'http://www.fr-online.de/politik/-/1472596/1472596/-/view/asFeed/-/index.xml'),
|
||||||
|
(u'Meinungen', u'http://www.fr-online.de/politik/meinung/-/1472602/1472602/-/view/asFeed/-/index.xml'),
|
||||||
|
(u'Wirtschaft', u'http://www.fr-online.de/wirtschaft/-/1472780/1472780/-/view/asFeed/-/index.xml'),
|
||||||
|
(u'Sport', u'http://www.fr-online.de/sport/-/1472784/1472784/-/view/asFeed/-/index.xml'),
|
||||||
|
(u'Kultur', u'http://www.fr-online.de/kultur/-/1472786/1472786/-/view/asFeed/-/index.xml'),
|
||||||
|
(u'Panorama', u'http://www.fr-online.de/panorama/-/1472782/1472782/-/view/asFeed/-/index.xml'),
|
||||||
|
(u'Digital', u'http://www.fr-online.de/digital/-/1472406/1472406/-/view/asFeed/-/index.xml'),
|
||||||
|
(u'Wissenschaft', u'http://www.fr-online.de/wissenschaft/-/1472788/1472788/-/view/asFeed/-/index.xml')
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def print_version(self, url):
|
||||||
|
return url.replace('index.html', 'view/printVersion/-/index.html')
|
||||||
|
|
38
recipes/glamour.recipe
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
|
||||||
|
class AdvancedUserRecipe1305547242(BasicNewsRecipe):
|
||||||
|
title = u'Glamour (US)'
|
||||||
|
oldest_article = 21
|
||||||
|
max_articles_per_feed = 100
|
||||||
|
no_stylesheets = True
|
||||||
|
use_embedded_content = False
|
||||||
|
language = 'en'
|
||||||
|
remove_javascript = True
|
||||||
|
__author__ = 'Anonymous'
|
||||||
|
remove_tags = [dict(name='div', attrs={'class':'articles_footer', 'class':'printoptions'})]
|
||||||
|
|
||||||
|
def print_version(self, url):
|
||||||
|
return url + '?printable=true'
|
||||||
|
|
||||||
|
def preprocess_html(self, soup):
|
||||||
|
for alink in soup.findAll('a'):
|
||||||
|
if alink.string is not None:
|
||||||
|
tstr = alink.string
|
||||||
|
alink.replaceWith(tstr)
|
||||||
|
return soup
|
||||||
|
|
||||||
|
feeds = [ (u'All Fashion', u'http://feeds.glamour.com/glamour/all_fashion'),
|
||||||
|
(u'All Beauty', u'http://feeds.glamour.com/glamour/all_beauty'),
|
||||||
|
(u'All Sex, Love & Life', u'http://feeds.glamour.com/glamour/sex_love_life'),
|
||||||
|
(u'All Health & Fitness', u'http://feeds.glamour.com/glamour/health_fitness'),
|
||||||
|
(u'Shopping', u'http://feeds.glamour.com/glamour/shopping'),
|
||||||
|
(u'Slaves to Fashion blog', u'http://feeds.glamour.com/glamour/slavestofashion'),
|
||||||
|
(u'The Girls in the Beauty Department', u'http://feeds.glamour.com/glamour/thegirlsinthebeautydepartment'),
|
||||||
|
(u'Smitten blog', u'http://feeds.glamour.com/glamour/smitten'),
|
||||||
|
(u'Save the Date', u'http://feeds.feedburner.com/glamour/save-the-date'),
|
||||||
|
(u'Single-ish blog', u'http://feeds.glamour.com/glamour/glamoursingle-ish'),
|
||||||
|
(u'Save the Date', u'http://feeds.feedburner.com/glamour/save-the-date'),
|
||||||
|
(u'Vitamin G blog', u'http://feeds.glamour.com/glamour/vitamin-g'),
|
||||||
|
(u'Margarita Shapes Up blog', u'http://feeds.glamour.com/glamour/margaritashapesup'),
|
||||||
|
(u'Little Miss Fortune blog', u'http://feeds.glamour.com/glamour/little-miss-fortune'),
|
||||||
|
]
|
@ -6,13 +6,13 @@ __copyright__ = 'Copyright 2010 Starson17'
|
|||||||
www.gocomics.com
|
www.gocomics.com
|
||||||
'''
|
'''
|
||||||
from calibre.web.feeds.news import BasicNewsRecipe
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
import mechanize
|
import mechanize, re
|
||||||
|
|
||||||
class GoComics(BasicNewsRecipe):
|
class GoComics(BasicNewsRecipe):
|
||||||
title = 'GoComics'
|
title = 'GoComics'
|
||||||
__author__ = 'Starson17'
|
__author__ = 'Starson17'
|
||||||
__version__ = '1.03'
|
__version__ = '1.05'
|
||||||
__date__ = '09 October 2010'
|
__date__ = '19 may 2011'
|
||||||
description = u'200+ Comics - Customize for more days/comics: Defaults to 7 days, 25 comics - 20 general, 5 editorial.'
|
description = u'200+ Comics - Customize for more days/comics: Defaults to 7 days, 25 comics - 20 general, 5 editorial.'
|
||||||
category = 'news, comics'
|
category = 'news, comics'
|
||||||
language = 'en'
|
language = 'en'
|
||||||
@ -20,6 +20,7 @@ class GoComics(BasicNewsRecipe):
|
|||||||
no_stylesheets = True
|
no_stylesheets = True
|
||||||
remove_javascript = True
|
remove_javascript = True
|
||||||
cover_url = 'http://paulbuckley14059.files.wordpress.com/2008/06/calvin-and-hobbes.jpg'
|
cover_url = 'http://paulbuckley14059.files.wordpress.com/2008/06/calvin-and-hobbes.jpg'
|
||||||
|
remove_attributes = ['style']
|
||||||
|
|
||||||
####### USER PREFERENCES - COMICS, IMAGE SIZE AND NUMBER OF COMICS TO RETRIEVE ########
|
####### USER PREFERENCES - COMICS, IMAGE SIZE AND NUMBER OF COMICS TO RETRIEVE ########
|
||||||
# num_comics_to_get - I've tried up to 99 on Calvin&Hobbes
|
# num_comics_to_get - I've tried up to 99 on Calvin&Hobbes
|
||||||
@ -40,6 +41,8 @@ class GoComics(BasicNewsRecipe):
|
|||||||
|
|
||||||
remove_tags = [dict(name='a', attrs={'class':['beginning','prev','cal','next','newest']}),
|
remove_tags = [dict(name='a', attrs={'class':['beginning','prev','cal','next','newest']}),
|
||||||
dict(name='div', attrs={'class':['tag-wrapper']}),
|
dict(name='div', attrs={'class':['tag-wrapper']}),
|
||||||
|
dict(name='a', attrs={'href':re.compile(r'.*mutable_[0-9]+', re.IGNORECASE)}),
|
||||||
|
dict(name='img', attrs={'src':re.compile(r'.*mutable_[0-9]+', re.IGNORECASE)}),
|
||||||
dict(name='ul', attrs={'class':['share-nav','feature-nav']}),
|
dict(name='ul', attrs={'class':['share-nav','feature-nav']}),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1,83 +1,70 @@
|
|||||||
#!/usr/bin/env python
|
from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||||
|
|
||||||
|
class AdvancedUserRecipe1303841067(BasicNewsRecipe):
|
||||||
|
|
||||||
from calibre.web.feeds.news import BasicNewsRecipe
|
|
||||||
class golem_ger(BasicNewsRecipe):
|
|
||||||
title = u'Golem.de'
|
title = u'Golem.de'
|
||||||
language = 'de'
|
__author__ = 'schuster'
|
||||||
__author__ = 'Kovid Goyal'
|
|
||||||
oldest_article = 7
|
oldest_article = 7
|
||||||
max_articles_per_feed = 100
|
max_articles_per_feed = 10
|
||||||
language = 'de'
|
no_stylesheets = True
|
||||||
lang = 'de-DE'
|
use_embedded_content = False
|
||||||
no_stylesheets = True
|
language = 'de'
|
||||||
encoding = 'iso-8859-1'
|
cover_url = 'http://www.e-energy.de/images/logo_golem.jpg'
|
||||||
recursions = 1
|
masthead_url = 'http://www.golem.de/staticrl/images/logo.png'
|
||||||
match_regexps = [r'http://www.golem.de/.*.html']
|
|
||||||
|
|
||||||
keep_only_tags = [
|
|
||||||
dict(name='h1', attrs={'class':'artikelhead'}),
|
|
||||||
dict(name='p', attrs={'class':'teaser'}),
|
|
||||||
dict(name='div', attrs={'class':'artikeltext'}),
|
|
||||||
dict(name='h2', attrs={'id':'artikelhead'}),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
remove_tags = [
|
|
||||||
dict(name='div', attrs={'id':['similarContent','topContentWrapper','storycarousel','aboveFootPromo','comments','toolbar','breadcrumbs','commentlink','sidebar','rightColumn']}),
|
|
||||||
dict(name='div', attrs={'class':['gg_embeddedSubText','gg_embeddedIndex gg_solid','gg_toOldGallery','golemGallery']}),
|
|
||||||
dict(name='img', attrs={'class':['gg_embedded','gg_embeddedIconRight gg_embeddedIconFS gg_cursorpointer']}),
|
|
||||||
dict(name='td', attrs={'class':['xsmall']}),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
# remove_tags_after = [
|
|
||||||
# dict(name='div', attrs={'id':['contentad2']})
|
|
||||||
# ]
|
|
||||||
|
|
||||||
|
|
||||||
feeds = [
|
|
||||||
(u'Golem.de', u'http://rss.golem.de/rss.php?feed=ATOM1.0'),
|
|
||||||
(u'Audio/Video', u'http://rss.golem.de/rss.php?tp=av&feed=RSS2.0'),
|
|
||||||
(u'Foto', u'http://rss.golem.de/rss.php?tp=foto&feed=RSS2.0'),
|
|
||||||
(u'Games', u'http://rss.golem.de/rss.php?tp=games&feed=RSS2.0'),
|
|
||||||
(u'Internet', u'http://rss.golem.de/rss.php?tp=inet&feed=RSS1.0'),
|
|
||||||
(u'Mobil', u'http://rss.golem.de/rss.php?tp=mc&feed=ATOM1.0'),
|
|
||||||
(u'Internet', u'http://rss.golem.de/rss.php?tp=inet&feed=RSS1.0'),
|
|
||||||
(u'Politik/Recht', u'http://rss.golem.de/rss.php?tp=pol&feed=ATOM1.0'),
|
|
||||||
(u'Desktop-Applikationen', u'http://rss.golem.de/rss.php?tp=apps&feed=RSS2.0'),
|
|
||||||
(u'Software-Entwicklung', u'http://rss.golem.de/rss.php?tp=dev&feed=RSS2.0'),
|
|
||||||
(u'Wirtschaft', u'http://rss.golem.de/rss.php?tp=wirtschaft&feed=RSS2.0'),
|
|
||||||
(u'Hardware', u'http://rss.golem.de/rss.php?r=hw&feed=RSS2.0'),
|
|
||||||
(u'Software', u'http://rss.golem.de/rss.php?r=sw&feed=RSS2.0'),
|
|
||||||
(u'Networld', u'http://rss.golem.de/rss.php?r=nw&feed=RSS2.0'),
|
|
||||||
(u'Entertainment', u'http://rss.golem.de/rss.php?r=et&feed=RSS2.0'),
|
|
||||||
(u'TK', u'http://rss.golem.de/rss.php?r=tk&feed=RSS2.0'),
|
|
||||||
(u'E-Commerce', u'http://rss.golem.de/rss.php?r=ec&feed=RSS2.0'),
|
|
||||||
(u'Unternehmen/Maerkte', u'http://rss.golem.de/rss.php?r=wi&feed=RSS2.0')
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
feeds = [
|
|
||||||
(u'Golem.de', u'http://rss.golem.de/rss.php?feed=ATOM1.0'),
|
|
||||||
(u'Mobil', u'http://rss.golem.de/rss.php?tp=mc&feed=feed=RSS2.0'),
|
|
||||||
(u'OSS', u'http://rss.golem.de/rss.php?tp=oss&feed=RSS2.0'),
|
|
||||||
(u'Politik/Recht', u'http://rss.golem.de/rss.php?tp=pol&feed=RSS2.0'),
|
|
||||||
(u'Desktop-Applikationen', u'http://rss.golem.de/rss.php?tp=apps&feed=RSS2.0'),
|
|
||||||
(u'Software-Entwicklung', u'http://rss.golem.de/rss.php?tp=dev&feed=RSS2.0'),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
extra_css = '''
|
extra_css = '''
|
||||||
h1 {color:#0066CC;font-family:Arial,Helvetica,sans-serif; font-size:30px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:20px;margin-bottom:2 em;}
|
h2{font-family:Arial,Helvetica,sans-serif; font-size: x-small;}
|
||||||
h2 {color:#4D4D4D;font-family:Arial,Helvetica,sans-serif; font-size:22px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:16px; }
|
h1{ font-family:Arial,Helvetica,sans-serif; font-size:x-large; font-weight:bold;}
|
||||||
h3 {color:#4D4D4D;font-family:Arial,Helvetica,sans-serif; font-size:x-small; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:normal; line-height:5px;}
|
|
||||||
h4 {color:#333333; font-family:Arial,Helvetica,sans-serif;font-size:13px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:13px; }
|
|
||||||
h5 {color:#333333; font-family:Arial,Helvetica,sans-serif; font-size:11px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:11px; text-transform:uppercase;}
|
|
||||||
.teaser {font-style:italic;font-size:12pt;margin-bottom:15pt;}
|
|
||||||
.xsmall{font-style:italic;font-size:x-small;}
|
|
||||||
.td{font-style:italic;font-size:x-small;}
|
|
||||||
img {align:left;}
|
|
||||||
'''
|
'''
|
||||||
|
remove_javascript = True
|
||||||
|
remove_tags_befor = [dict(name='header', attrs={'class':'cluster-header'})]
|
||||||
|
remove_tags_after = [dict(name='p', attrs={'class':'meta'})]
|
||||||
|
remove_tags = [dict(rel='nofollow'),
|
||||||
|
dict(name='header', attrs={'id':'header'}),
|
||||||
|
dict(name='div', attrs={'class':'dh1'}),
|
||||||
|
dict(name='label', attrs={'class':'implied'}),
|
||||||
|
dict(name='section', attrs={'id':'comments'}),
|
||||||
|
dict(name='li', attrs={'class':'gg_prebackcounterItem'}),
|
||||||
|
dict(name='li', attrs={'class':'gg_prebackcounterItem gg_embeddedIndexCounter'}),
|
||||||
|
dict(name='img', attrs={'class':'gg_embeddedIconRight gg_embeddedIconFS gg_cursorpointer'}),
|
||||||
|
dict(name='div', attrs={'target':'_blank'})
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_browser(self, *args, **kwargs):
|
||||||
|
from calibre import browser
|
||||||
|
kwargs['user_agent'] = 'mozilla'
|
||||||
|
return browser(*args, **kwargs)
|
||||||
|
|
||||||
|
def get_article_url(self, article):
|
||||||
|
return article.get('id', article.get('guid', None))
|
||||||
|
|
||||||
|
def preprocess_html(self, soup):
|
||||||
|
for alink in soup.findAll('a'):
|
||||||
|
if alink.string is not None:
|
||||||
|
tstr = alink.string
|
||||||
|
alink.replaceWith(tstr)
|
||||||
|
return soup
|
||||||
|
|
||||||
|
feeds = [(u'Audio/Video', u'http://rss.golem.de/rss.php?tp=av&feed=RSS2.0'),
|
||||||
|
(u'Foto', u'http://rss.golem.de/rss.php?tp=foto&feed=RSS2.0'),
|
||||||
|
(u'Games', u'http://rss.golem.de/rss.php?tp=games&feed=RSS2.0'),
|
||||||
|
(u'Handy', u'http://rss.golem.de/rss.php?tp=handy&feed=RSS2.0'),
|
||||||
|
(u'Internet', u'http://rss.golem.de/rss.php?tp=inet&feed=RSS2.0'),
|
||||||
|
(u'Mobile', u'http://rss.golem.de/rss.php?tp=mc&feed=RSS2.0'),
|
||||||
|
(u'OSS', u'http://rss.golem.de/rss.php?tp=oss&feed=RSS2.0'),
|
||||||
|
(u'Politik/Recht', u'http://rss.golem.de/rss.php?tp=pol&feed=RSS2.0'),
|
||||||
|
(u'Security', u'http://rss.golem.de/rss.php?tp=sec&feed=RSS2.0'),
|
||||||
|
(u'Desktop-Applikationen', u'http://rss.golem.de/rss.php?tp=apps&feed=RSS2.0'),
|
||||||
|
(u'Software-Entwicklung', u'http://rss.golem.de/rss.php?tp=dev&feed=RSS2.0'),
|
||||||
|
(u'Wirtschaft', u'http://rss.golem.de/rss.php?tp=wirtschaft&feed=RSS2.0'),
|
||||||
|
(u'Hardware', u'http://rss.golem.de/rss.php?r=hw&feed=RSS2.0'),
|
||||||
|
(u'Software', u'http://rss.golem.de/rss.php?r=sw&feed=RSS2.0'),
|
||||||
|
(u'Networld', u'http://rss.golem.de/rss.php?r=nw&feed=RSS2.0'),
|
||||||
|
(u'Entertainment', u'http://rss.golem.de/rss.php?r=et&feed=RSS2.0'),
|
||||||
|
(u'TK', u'http://rss.golem.de/rss.php?r=tk&feed=RSS2.0'),
|
||||||
|
(u'Wirtschaft', u'http://rss.golem.de/rss.php?r=wi&feed=RSS2.0'),
|
||||||
|
(u'E-Commerce', u'http://rss.golem.de/rss.php?r=ec&feed=RSS2.0')
|
||||||
|
|
||||||
|
]
|
||||||
|
|
||||||
|
31
recipes/good_house_keeping.recipe
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
|
||||||
|
class AdvancedUserRecipe1305547242(BasicNewsRecipe):
|
||||||
|
title = u'Good House Keeping'
|
||||||
|
language = 'en'
|
||||||
|
__author__ = 'Anonymous'
|
||||||
|
oldest_article = 7
|
||||||
|
max_articles_per_feed = 100
|
||||||
|
no_stylesheets = True
|
||||||
|
use_embedded_content = False
|
||||||
|
remove_javascript = True
|
||||||
|
|
||||||
|
def print_version(self,url):
|
||||||
|
segments = url.split('/')
|
||||||
|
printURL = '/'.join(segments[0:3]) + '/print-this/' + '/'.join(segments[4:])
|
||||||
|
return printURL
|
||||||
|
|
||||||
|
def preprocess_html(self, soup):
|
||||||
|
for alink in soup.findAll('a'):
|
||||||
|
if alink.string is not None:
|
||||||
|
tstr = alink.string
|
||||||
|
alink.replaceWith(tstr)
|
||||||
|
return soup
|
||||||
|
|
||||||
|
feeds = [ (u'Recipes & Entertaining', u'http://www.goodhousekeeping.com/food/food-rss/?src=rss'),
|
||||||
|
(u'Home & House', u'http://www.goodhousekeeping.com/home/home-rss/?src=rss'),
|
||||||
|
(u'Diet & Health', u'http://www.goodhousekeeping.com/health/health-rss/?src=rss'),
|
||||||
|
(u'Beauty & Style', u'http://www.goodhousekeeping.com/beauty/beauty-rss/?src=rss'),
|
||||||
|
(u'Family & Pets', u'http://www.goodhousekeeping.com/family/family-rss/?src=rss'),
|
||||||
|
(u'Saving Money', u'http://www.goodhousekeeping.com/money/money-rss/?src=rss'),
|
||||||
|
]
|
32
recipes/good_to_know.recipe
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
|
||||||
|
class AdvancedUserRecipe1305547242(BasicNewsRecipe):
|
||||||
|
title = u'Good to Know (uk)'
|
||||||
|
oldest_article = 14
|
||||||
|
max_articles_per_feed = 100
|
||||||
|
no_stylesheets = True
|
||||||
|
use_embedded_content = False
|
||||||
|
remove_javascript = True
|
||||||
|
__author__ = 'Anonymous'
|
||||||
|
language = 'en_GB'
|
||||||
|
remove_tags = [dict(name='div', attrs={'class':'articles_footer', 'class':'printoptions'})]
|
||||||
|
|
||||||
|
def print_version(self, url):
|
||||||
|
return url + '/print/1'
|
||||||
|
|
||||||
|
def preprocess_html(self, soup):
|
||||||
|
for alink in soup.findAll('a'):
|
||||||
|
if alink.string is not None:
|
||||||
|
tstr = alink.string
|
||||||
|
alink.replaceWith(tstr)
|
||||||
|
return soup
|
||||||
|
|
||||||
|
feeds = [ (u'Family Conception Advice', u'http://www.goodtoknow.co.uk/feeds/family.rss'),
|
||||||
|
(u'Family Health Advice', u'http://www.goodtoknow.co.uk/feeds/health.rss'),
|
||||||
|
(u'Diet Advice', u'http://www.goodtoknow.co.uk/feeds/diet.rss'),
|
||||||
|
(u'Food Advice', u'http://www.goodtoknow.co.uk/feeds/food.rss'),
|
||||||
|
(u'Sex Advice', u'http://www.goodtoknow.co.uk/feeds/sex.rss'),
|
||||||
|
(u'Easy Exercise', u'http://www.goodtoknow.co.uk/feeds/easyexercise.rss'),
|
||||||
|
(u'Recipes', u'http://www.goodtoknow.co.uk/feeds/recipes.rss'),
|
||||||
|
(u'Food Quick-tips', u'http://www.goodtoknow.co.uk/feeds/foodquicktips.rss'),
|
||||||
|
]
|
36
recipes/grrm.recipe
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2011, Darko Miletic <darko.miletic at gmail.com>'
|
||||||
|
'''
|
||||||
|
grrm.livejournal.com
|
||||||
|
'''
|
||||||
|
|
||||||
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
|
||||||
|
class NotABlog(BasicNewsRecipe):
|
||||||
|
title = 'Not A Blog - George R.R. Martin'
|
||||||
|
__author__ = 'Darko Miletic'
|
||||||
|
description = 'George R.R. Martin'
|
||||||
|
oldest_article = 15
|
||||||
|
max_articles_per_feed = 100
|
||||||
|
language = 'en'
|
||||||
|
encoding = 'utf-8'
|
||||||
|
no_stylesheets = True
|
||||||
|
use_embedded_content = True
|
||||||
|
publication_type = 'blog'
|
||||||
|
|
||||||
|
conversion_options = {
|
||||||
|
'comment' : description
|
||||||
|
, 'tags' : 'sf, fantasy, game of thrones'
|
||||||
|
, 'publisher': 'George R.R. Martin'
|
||||||
|
, 'language' : language
|
||||||
|
}
|
||||||
|
|
||||||
|
feeds = [(u'Posts', u'http://grrm.livejournal.com/data/rss')]
|
||||||
|
|
||||||
|
def preprocess_html(self, soup):
|
||||||
|
for item in soup.findAll(style=True):
|
||||||
|
del item['style']
|
||||||
|
return self.adeify_images(soup)
|
||||||
|
|
||||||
|
|
BIN
recipes/icons/dilemaveche.png
Normal file
After Width: | Height: | Size: 558 B |
Before Width: | Height: | Size: 550 B After Width: | Height: | Size: 1.1 KiB |
BIN
recipes/icons/marca.png
Normal file
After Width: | Height: | Size: 293 B |
BIN
recipes/icons/natgeo.png
Normal file
After Width: | Height: | Size: 247 B |
BIN
recipes/icons/osnews_pl.png
Normal file
After Width: | Height: | Size: 1006 B |
BIN
recipes/icons/rmf24_opinie.png
Normal file
After Width: | Height: | Size: 722 B |
BIN
recipes/icons/swiatkindle.png
Normal file
After Width: | Height: | Size: 425 B |
BIN
recipes/icons/the_nation.png
Normal file
After Width: | Height: | Size: 925 B |
BIN
recipes/icons/wash_post.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
32
recipes/impulse_de.recipe
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
class AdvancedUserRecipe1305470859(BasicNewsRecipe):
|
||||||
|
title = u'Impulse.de'
|
||||||
|
language = 'de'
|
||||||
|
__author__ = 'schuster'
|
||||||
|
oldest_article =14
|
||||||
|
max_articles_per_feed = 100
|
||||||
|
no_stylesheets = True
|
||||||
|
remove_javascript = True
|
||||||
|
use_embedded_content = False
|
||||||
|
cover_url = 'http://www.bvk.de/files/image/bilder/Logo%20Impulse.jpg'
|
||||||
|
|
||||||
|
extra_css = '''
|
||||||
|
h1{font-family:Arial,Helvetica,sans-serif; font-weight:bold;font-size:large;}
|
||||||
|
h4{font-family:Arial,Helvetica,sans-serif; font-weight:normal;font-size:small;}
|
||||||
|
img {min-width:300px; max-width:600px; min-height:300px; max-height:800px}
|
||||||
|
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):
|
||||||
|
return url.replace ('#utm_source=rss2&utm_medium=rss_feed&utm_campaign=/', '?mode=print')
|
||||||
|
remove_tags_bevor = [dict(name='h1', attrs={'class':'h2'})]
|
||||||
|
remove_tags_after = [dict(name='div', attrs={'class':'artikelfuss'})]
|
||||||
|
|
||||||
|
feeds = [ (u'impulstest', u'http://www.impulse.de/rss/')]
|
||||||
|
|
||||||
|
|
||||||
|
remove_tags = [dict(attrs={'class':['navSeitenAlle', 'kommentieren', 'teaserheader', 'teasercontent', 'info', 'zwischenhead', 'kasten_artikel']}),
|
||||||
|
dict(id=['metaNav', 'impKopf', 'impTopNav', 'impSubNav', 'footerRahmen', 'gatrixx_marktinformationen', 'pager', 'weitere', 'socialmedia', 'rating_open']),
|
||||||
|
dict(span=['ratingtext', 'Gesamtranking', 'h3','']),
|
||||||
|
dict(rel=['canonical'])]
|
||||||
|
|
@ -3,8 +3,9 @@ from calibre.web.feeds.news import BasicNewsRecipe
|
|||||||
class AdvancedUserRecipe1295262156(BasicNewsRecipe):
|
class AdvancedUserRecipe1295262156(BasicNewsRecipe):
|
||||||
title = u'kath.net'
|
title = u'kath.net'
|
||||||
__author__ = 'Bobus'
|
__author__ = 'Bobus'
|
||||||
|
description = u'Katholische Nachrichten'
|
||||||
oldest_article = 7
|
oldest_article = 7
|
||||||
language = 'en'
|
language = 'de'
|
||||||
max_articles_per_feed = 100
|
max_articles_per_feed = 100
|
||||||
|
|
||||||
feeds = [(u'kath.net', u'http://www.kath.net/2005/xml/index.xml')]
|
feeds = [(u'kath.net', u'http://www.kath.net/2005/xml/index.xml')]
|
||||||
|
@ -5,14 +5,16 @@ class Kathimerini(BasicNewsRecipe):
|
|||||||
__author__ = 'Pan'
|
__author__ = 'Pan'
|
||||||
description = 'News from Greece'
|
description = 'News from Greece'
|
||||||
max_articles_per_feed = 100
|
max_articles_per_feed = 100
|
||||||
oldest_article = 100
|
oldest_article = 100
|
||||||
publisher = 'Kathimerini'
|
publisher = 'Kathimerini'
|
||||||
category = 'news, GR'
|
category = 'news, GR'
|
||||||
language = 'el'
|
language = 'el'
|
||||||
|
encoding = 'windows-1253'
|
||||||
|
conversion_options = { 'linearize_tables': True}
|
||||||
no_stylesheets = True
|
no_stylesheets = True
|
||||||
remove_tags_before = dict(name='td',attrs={'class':'news'})
|
remove_tags_before = dict(name='td',attrs={'class':'news'})
|
||||||
remove_tags_after = dict(name='td',attrs={'class':'news'})
|
remove_tags_after = dict(name='td',attrs={'class':'news'})
|
||||||
remove_attributes = ['width', 'src','header','footer']
|
remove_attributes = ['width', 'src','header','footer']
|
||||||
|
|
||||||
feeds = [(u'\u03a0\u03bf\u03bb\u03b9\u03c4\u03b9\u03ba\u03ae',
|
feeds = [(u'\u03a0\u03bf\u03bb\u03b9\u03c4\u03b9\u03ba\u03ae',
|
||||||
'http://wk.kathimerini.gr/xml_files/politics.xml'),
|
'http://wk.kathimerini.gr/xml_files/politics.xml'),
|
||||||
@ -34,4 +36,3 @@ class Kathimerini(BasicNewsRecipe):
|
|||||||
def print_version(self, url):
|
def print_version(self, url):
|
||||||
return url.replace('http://news.kathimerini.gr/4dcgi/', 'http://news.kathimerini.gr/4dcgi/4dcgi/')
|
return url.replace('http://news.kathimerini.gr/4dcgi/', 'http://news.kathimerini.gr/4dcgi/4dcgi/')
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,14 +1,11 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__copyright__ = '2009, Darko Miletic <darko.miletic at gmail.com>'
|
__copyright__ = '2009-2011, Darko Miletic <darko.miletic at gmail.com>'
|
||||||
'''
|
'''
|
||||||
www.marca.com
|
www.marca.com
|
||||||
'''
|
'''
|
||||||
|
|
||||||
from calibre.web.feeds.news import BasicNewsRecipe
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
from calibre.ebooks.BeautifulSoup import Tag
|
|
||||||
|
|
||||||
class Marca(BasicNewsRecipe):
|
class Marca(BasicNewsRecipe):
|
||||||
title = 'Marca'
|
title = 'Marca'
|
||||||
@ -22,35 +19,30 @@ class Marca(BasicNewsRecipe):
|
|||||||
use_embedded_content = False
|
use_embedded_content = False
|
||||||
delay = 1
|
delay = 1
|
||||||
encoding = 'iso-8859-15'
|
encoding = 'iso-8859-15'
|
||||||
language = 'es'
|
language = 'es_ES'
|
||||||
|
publication_type = 'newsportal'
|
||||||
|
masthead_url = 'http://estaticos.marca.com/deporte/img/v3.0/img_marca-com.png'
|
||||||
|
extra_css = """
|
||||||
|
body{font-family: Tahoma,Geneva,sans-serif}
|
||||||
|
h1,h2,h3,h4,h5,h6{font-family: 'LatoBlack',Tahoma,Geneva,sans-serif}
|
||||||
|
.cab_articulo h4 {font-family: Georgia,"Times New Roman",Times,serif}
|
||||||
|
.antetitulo{text-transform: uppercase}
|
||||||
|
"""
|
||||||
|
|
||||||
direction = 'ltr'
|
feeds = [(u'Portada', u'http://estaticos.marca.com/rss/portada.xml')]
|
||||||
|
|
||||||
html2lrf_options = [
|
keep_only_tags = [dict(name='div', attrs={'class':['cab_articulo','cuerpo_articulo']})]
|
||||||
'--comment' , description
|
remove_attributes = ['lang']
|
||||||
, '--category' , category
|
remove_tags = [
|
||||||
, '--publisher', publisher
|
dict(name=['object','link','script','embed','iframe','meta','base'])
|
||||||
]
|
,dict(name='div', attrs={'class':'tabs'})
|
||||||
|
|
||||||
html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"'
|
|
||||||
|
|
||||||
feeds = [(u'Portada', u'http://rss.marca.com/rss/descarga.htm?data2=425')]
|
|
||||||
|
|
||||||
keep_only_tags = [dict(name='div', attrs={'class':['cab_articulo','col_izq']})]
|
|
||||||
|
|
||||||
remove_tags = [
|
|
||||||
dict(name=['object','link','script'])
|
|
||||||
,dict(name='div', attrs={'class':['colC','peu']})
|
|
||||||
,dict(name='div', attrs={'class':['utilidades estirar','bloque_int_corr estirar']})
|
|
||||||
]
|
]
|
||||||
|
|
||||||
remove_tags_after = [dict(name='div', attrs={'class':'bloque_int_corr estirar'})]
|
|
||||||
|
|
||||||
def preprocess_html(self, soup):
|
def preprocess_html(self, soup):
|
||||||
soup.html['dir' ] = self.direction
|
|
||||||
mcharset = Tag(soup,'meta',[("http-equiv","Content-Type"),("content","text/html; charset=utf-8")])
|
|
||||||
soup.head.insert(0,mcharset)
|
|
||||||
for item in soup.findAll(style=True):
|
for item in soup.findAll(style=True):
|
||||||
del item['style']
|
del item['style']
|
||||||
return soup
|
return soup
|
||||||
|
|
||||||
|
def get_article_url(self, article):
|
||||||
|
return article.get('guid', None)
|
||||||
|
|
||||||
|
22
recipes/max_planck.recipe
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||||
|
class AdvancedUserRecipe1303841067(BasicNewsRecipe):
|
||||||
|
|
||||||
|
title = u'Max-Planck-Inst.'
|
||||||
|
__author__ = 'schuster'
|
||||||
|
remove_tags = [dict(attrs={'class':['clearfix', 'lens', 'col2_box_list', 'col2_box_teaser group_ext no_print', 'dotted_line', 'col2_box_teaser', 'box_image small', 'bold', 'col2_box_teaser no_print', 'print_kontakt']}),
|
||||||
|
dict(id=['ie_clearing', 'col2', 'col2_content']),
|
||||||
|
dict(name=['script', 'noscript', 'style'])]
|
||||||
|
oldest_article = 30
|
||||||
|
max_articles_per_feed = 100
|
||||||
|
no_stylesheets = True
|
||||||
|
use_embedded_content = False
|
||||||
|
language = 'de'
|
||||||
|
remove_javascript = True
|
||||||
|
|
||||||
|
def print_version(self, url):
|
||||||
|
split_url = url.split("/")
|
||||||
|
print_url = 'http://www.mpg.de/print/' + split_url[3]
|
||||||
|
return print_url
|
||||||
|
|
||||||
|
feeds = [(u'Forschung', u'http://www.mpg.de/de/forschung.rss')]
|
||||||
|
|
10
recipes/mens_health.recipe
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
|
||||||
|
class AdvancedUserRecipe1305636254(BasicNewsRecipe):
|
||||||
|
title = u'Mens Health (US)'
|
||||||
|
language = 'en'
|
||||||
|
__author__ = 'Anonymous'
|
||||||
|
oldest_article = 14
|
||||||
|
max_articles_per_feed = 100
|
||||||
|
|
||||||
|
feeds = [(u'News', u'http://blogs.menshealth.com/health-headlines/feed')]
|
71
recipes/natgeo.recipe
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2011, gagsays <gagsays at gmail dot com>'
|
||||||
|
'''
|
||||||
|
nationalgeographic.com
|
||||||
|
'''
|
||||||
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
class NatGeo(BasicNewsRecipe):
|
||||||
|
title = u'National Geographic'
|
||||||
|
description = 'Daily news articles from The National Geographic'
|
||||||
|
language = 'en'
|
||||||
|
oldest_article = 20
|
||||||
|
max_articles_per_feed = 25
|
||||||
|
encoding = 'utf8'
|
||||||
|
publisher = 'nationalgeographic.com'
|
||||||
|
category = 'science, nat geo'
|
||||||
|
__author__ = 'gagsays'
|
||||||
|
masthead_url = 'http://s.ngeo.com/wpf/sites/themes/global/i/presentation/ng_logo_small.png'
|
||||||
|
description = 'Inspiring people to care about the planet since 1888'
|
||||||
|
timefmt = ' [%a, %d %b, %Y]'
|
||||||
|
no_stylesheets = True
|
||||||
|
use_embedded_content = False
|
||||||
|
|
||||||
|
extra_css = '''
|
||||||
|
body {color: #000000;font-size: medium;}
|
||||||
|
h1 {color: #222222; font-size: large; font-weight:lighter; text-decoration:none; text-align: center;font-family:Georgia,Times New Roman,Times,serif;}
|
||||||
|
h2 {color: #454545; font-size: small; font-weight:lighter; text-decoration:none; text-align: justify; font-style:italic;font-family :Georgia,Times New Roman,Times,serif;}
|
||||||
|
h3 {color: #555555; font-size: small; font-style:italic; margin-top: 10px;}
|
||||||
|
img{margin-bottom: 0.25em;display:block;margin-left: auto;margin-right: auto;}
|
||||||
|
a:link,a,.a,href {text-decoration: none;color: #000000;}
|
||||||
|
.caption{color: #000000;font-size: xx-small;text-align: justify;font-weight:normal;}
|
||||||
|
.credit{color: #555555;font-size: xx-small;text-align: left;font-weight:lighter;}
|
||||||
|
p.author,p.publication{color: #000000;font-size: xx-small;text-align: left;display:inline;}
|
||||||
|
p.publication_time{color: #000000;font-size: xx-small;text-align: right;text-decoration: underline;}
|
||||||
|
p {margin-bottom: 0;}
|
||||||
|
p + p {text-indent: 1.5em;margin-top: 0;}
|
||||||
|
.hidden{display:none;}
|
||||||
|
#page_head{text-transform:uppercase;}
|
||||||
|
'''
|
||||||
|
|
||||||
|
def parse_feeds (self):
|
||||||
|
feeds = BasicNewsRecipe.parse_feeds(self)
|
||||||
|
for feed in feeds:
|
||||||
|
for article in feed.articles[:]:
|
||||||
|
if 'Presented' in article.title or 'Pictures' in article.title:
|
||||||
|
feed.articles.remove(article)
|
||||||
|
return feeds
|
||||||
|
|
||||||
|
def preprocess_html(self, soup):
|
||||||
|
for alink in soup.findAll('a'):
|
||||||
|
if alink.string is not None:
|
||||||
|
tstr = alink.string
|
||||||
|
alink.replaceWith(tstr)
|
||||||
|
return soup
|
||||||
|
|
||||||
|
remove_tags_before = dict(id='page_head')
|
||||||
|
keep_only_tags = [
|
||||||
|
dict(name='div',attrs={'id':['page_head','content_mainA']})
|
||||||
|
]
|
||||||
|
remove_tags_after = [
|
||||||
|
dict(name='div',attrs={'class':['article_text','promo_collection']})
|
||||||
|
]
|
||||||
|
remove_tags = [
|
||||||
|
dict(name='div', attrs={'class':['aside','primary full_width']})
|
||||||
|
,dict(name='div', attrs={'id':['header_search','navigation_mainB_wrap']})
|
||||||
|
]
|
||||||
|
feeds = [
|
||||||
|
(u'Daily News', u'http://feeds.nationalgeographic.com/ng/News/News_Main')
|
||||||
|
]
|
||||||
|
|
25
recipes/national_geographic_de.recipe
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
|
||||||
|
class AdvancedUserRecipe1305567197(BasicNewsRecipe):
|
||||||
|
title = u'National Geographic (DE)'
|
||||||
|
__author__ = 'Anonymous'
|
||||||
|
language = 'de'
|
||||||
|
oldest_article = 7
|
||||||
|
max_articles_per_feed = 1000
|
||||||
|
no_stylesheets = True
|
||||||
|
use_embedded_content = False
|
||||||
|
remove_javascript = True
|
||||||
|
cover_url = 'http://www.nationalgeographic.de/images/national-geographic-logo.jpg'
|
||||||
|
keep_only_tags = [
|
||||||
|
dict(name='div', attrs={'class':['contentbox_no_top_border']}) ]
|
||||||
|
|
||||||
|
remove_tags = [
|
||||||
|
dict(name='div', attrs={'class':'related'}),
|
||||||
|
dict(name='li', attrs={'class':'first'}),
|
||||||
|
dict(name='div', attrs={'class':'extrasbox_inner'}),
|
||||||
|
|
||||||
|
]
|
||||||
|
|
||||||
|
feeds = [ (u'National Geographic', u'http://feeds.nationalgeographic.de/ng-neueste-artikel'),
|
||||||
|
|
||||||
|
]
|
@ -11,6 +11,20 @@ class Newsweek(BasicNewsRecipe):
|
|||||||
|
|
||||||
BASE_URL = 'http://www.newsweek.com'
|
BASE_URL = 'http://www.newsweek.com'
|
||||||
|
|
||||||
|
topics = {
|
||||||
|
'Culture' : '/tag/culture.html',
|
||||||
|
'Business' : '/tag/business.html',
|
||||||
|
'Society' : '/tag/society.html',
|
||||||
|
'Science' : '/tag/science.html',
|
||||||
|
'Education' : '/tag/education.html',
|
||||||
|
'Politics' : '/tag/politics.html',
|
||||||
|
'Health' : '/tag/health.html',
|
||||||
|
'World' : '/tag/world.html',
|
||||||
|
'Nation' : '/tag/nation.html',
|
||||||
|
'Technology' : '/tag/technology.html',
|
||||||
|
'Game Changers' : '/tag/game-changers.html',
|
||||||
|
}
|
||||||
|
|
||||||
keep_only_tags = dict(name='article', attrs={'class':'article-text'})
|
keep_only_tags = dict(name='article', attrs={'class':'article-text'})
|
||||||
remove_tags = [dict(attrs={'data-dartad':True})]
|
remove_tags = [dict(attrs={'data-dartad':True})]
|
||||||
remove_attributes = ['property']
|
remove_attributes = ['property']
|
||||||
@ -21,14 +35,10 @@ class Newsweek(BasicNewsRecipe):
|
|||||||
return soup
|
return soup
|
||||||
|
|
||||||
def newsweek_sections(self):
|
def newsweek_sections(self):
|
||||||
return [
|
for topic_name, topic_url in self.topics.iteritems():
|
||||||
('Nation', 'http://www.newsweek.com/tag/nation.html'),
|
yield (topic_name,
|
||||||
('Society', 'http://www.newsweek.com/tag/society.html'),
|
self.BASE_URL+topic_url)
|
||||||
('Culture', 'http://www.newsweek.com/tag/culture.html'),
|
|
||||||
('World', 'http://www.newsweek.com/tag/world.html'),
|
|
||||||
('Politics', 'http://www.newsweek.com/tag/politics.html'),
|
|
||||||
('Business', 'http://www.newsweek.com/tag/business.html'),
|
|
||||||
]
|
|
||||||
|
|
||||||
def newsweek_parse_section_page(self, soup):
|
def newsweek_parse_section_page(self, soup):
|
||||||
for article in soup.findAll('article', about=True,
|
for article in soup.findAll('article', about=True,
|
||||||
|
29
recipes/ngz.recipe
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||||
|
class AdvancedUserRecipe1303841067(BasicNewsRecipe):
|
||||||
|
|
||||||
|
title = u'NGZ-online'
|
||||||
|
__author__ = 'schuster'
|
||||||
|
remove_tags_before = dict(id='bu')
|
||||||
|
remove_tags_after = dict(id='noblock')
|
||||||
|
remove_tags = [dict(attrs={'class':['articleTools', 'post-tools', 'side_tool', 'nextArticleLink clearfix', 'liketext']}),
|
||||||
|
dict(id=['footer', 'toolsRight', 'articleInline', 'navigation', 'archive', 'side_search', 'blog_sidebar', 'side_tool', 'side_index', 'Verlinken', 'vorheriger', 'LESERKOMMENTARE', 'bei facebook', 'bei twitter', 'Schreiben Sie jetzt Ihre Meinung:', 'Thema', 'Ihr Beitrag', 'Ihr Name', 'Ich möchte über weitere Lesermeinungen zu diesem Artikel per E-Mail informiert werden.', 'banneroben', 'bannerrechts', 'inserieren', 'stellen', 'auto', 'immobilien', 'kleinanzeige', 'tiere', 'ferienwohnung', 'NGZ Card', 'Mediengruppe RP', 'Werben', 'Newsletter', 'Wetter', 'RSS', 'Abo', 'Anzeigen', 'Redaktion', 'Schulprojekte', 'Gast', 'Mein NGZ', 'Nachrichten', 'Sport', 'Wirtschaft', 'Stadt-Infos', 'Bilderserien', 'Bookmarken', 'del.icio.us', 'Mister Wong', 'YiGG', 'Webnews', 'Shortnews', 'Twitter', 'Newsider', 'Facebook', 'StudiVZ/MeinVZ', 'Versenden', 'Drucken']),
|
||||||
|
dict(name=['script', 'noscript', 'style'])]
|
||||||
|
oldest_article = 7
|
||||||
|
max_articles_per_feed = 100
|
||||||
|
no_stylesheets = True
|
||||||
|
use_embedded_content = False
|
||||||
|
language = 'de'
|
||||||
|
remove_javascript = True
|
||||||
|
cover_url = 'http://www.rhein-kreis-neuss-macht-sport.de/sport/includes/bilder/ngz_logo.jpg'
|
||||||
|
|
||||||
|
def print_version(self, url):
|
||||||
|
return url + '?ot=de.circit.rpo.PopupPageLayout.ot'
|
||||||
|
feeds = [
|
||||||
|
(u'Grevenbroich', u'http://www.ngz-online.de/app/feed/rss/grevenbroich'),
|
||||||
|
(u'Kreis Neuss', u'http://www.ngz-online.de/app/feed/rss/rheinkreisneuss'),
|
||||||
|
(u'Dormagen', u'http://www.ngz-online.de/app/feed/rss/dormagen'),
|
||||||
|
(u'J\xfcchen', u'http://www.ngz-online.de/app/feed/rss/juechen'),
|
||||||
|
(u'Rommerskirchen', u'http://www.ngz-online.de/app/feed/rss/rommerskirchen')
|
||||||
|
|
||||||
|
]
|
||||||
|
|
22
recipes/pro_physik.recipe
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||||
|
class AdvancedUserRecipe1303841067(BasicNewsRecipe):
|
||||||
|
|
||||||
|
title = u'Pro Physik'
|
||||||
|
__author__ = 'schuster'
|
||||||
|
oldest_article = 4
|
||||||
|
max_articles_per_feed = 100
|
||||||
|
no_stylesheets = True
|
||||||
|
use_embedded_content = False
|
||||||
|
language = 'de'
|
||||||
|
remove_javascript = True
|
||||||
|
cover_url = 'http://www.pro-physik.de/Phy/images/site/prophysik_logo1.jpg'
|
||||||
|
|
||||||
|
|
||||||
|
def print_version(self, url):
|
||||||
|
return url.replace('leadArticle.do', 'print.do')
|
||||||
|
|
||||||
|
|
||||||
|
feeds = [(u'Hightech', u'http://www.pro-physik.de/Phy/hightechfeed.xml'),
|
||||||
|
(u'Forschung', u'http://www.pro-physik.de/Phy/forschungfeed.xml'),
|
||||||
|
(u'Magazin', u'http://www.pro-physik.de/Phy/magazinfeed.xml')]
|
||||||
|
|
55
recipes/rheinische_post.recipe
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||||
|
class AdvancedUserRecipe(BasicNewsRecipe):
|
||||||
|
|
||||||
|
title = u'RP-online'
|
||||||
|
__author__ = 'schuster'
|
||||||
|
oldest_article = 2
|
||||||
|
max_articles_per_feed = 100
|
||||||
|
no_stylesheets = True
|
||||||
|
use_embedded_content = False
|
||||||
|
language = 'de'
|
||||||
|
remove_javascript = True
|
||||||
|
masthead_url = 'http://www.die-zeitungen.de/uploads/pics/LOGO_RP_ONLINE_01.jpg'
|
||||||
|
cover_url = 'http://www.manroland.com/com/pressinfo_images/com/RheinischePost_Logo_300dpi.jpg'
|
||||||
|
extra_css = '''
|
||||||
|
h1{font-family:Arial,Helvetica,sans-serif; font-weight:bold;font-size:large;}
|
||||||
|
h4{font-family:Arial,Helvetica,sans-serif; font-weight:normal;font-size:small;}
|
||||||
|
img {min-width:300px; max-width:600px; min-height:300px; max-height:800px}
|
||||||
|
p{font-family:Arial,Helvetica,sans-serif;font-size:small;}
|
||||||
|
body{font-family:Helvetica,Arial,sans-serif;font-size:small;}
|
||||||
|
'''
|
||||||
|
remove_tags_before = dict(id='article_content')
|
||||||
|
remove_tags_after = dict(id='article_content')
|
||||||
|
remove_tags = [dict(attrs={'class':['goodies', 'left', 'right', 'clear-all', 'teaser anzeigenwerbung', 'lesermeinung', 'goodiebox', 'goodiebox 1', 'goodiebox 2', 'goodiebox 3', 'boxframe', 'link']}),
|
||||||
|
dict(id=['click_Fotos_link']),
|
||||||
|
dict(name=['script', 'noscript', 'style', '_top', 'click_Fotos_link'])]
|
||||||
|
|
||||||
|
feeds = [ (u'Top-News', u'http://www.ngz-online.de/app/feed/rss/topnews'),
|
||||||
|
(u'Politik', u'http://www.ngz-online.de/app/feed/rss/politik'),
|
||||||
|
(u'Wirtschaft', u'http://www.ngz-online.de/app/feed/rss/wirtschaft'),
|
||||||
|
(u'Panorama', u'http://www.ngz-online.de/app/feed/rss/panorama'),
|
||||||
|
(u'Sport', u'http://www.ngz-online.de/app/feed/rss/sport'),
|
||||||
|
(u'Tour de France', u'http://www.ngz-online.de/app/feed/rss/tourdefrance'),
|
||||||
|
(u'Fußball', u'http://www.ngz-online.de/app/feed/rss/fussball'),
|
||||||
|
(u'Fußball BuLi', u'http://www.ngz-online.de/app/feed/rss/bundesliga'),
|
||||||
|
(u'Formel 1', u'http://www.ngz-online.de/app/feed/rss/formel1'),
|
||||||
|
(u'US-Sport', u'http://www.ngz-online.de/app/feed/rss/us-sports'),
|
||||||
|
(u'Boxen', u'http://www.ngz-online.de/app/feed/rss/boxen'),
|
||||||
|
(u'Eishockey', u'http://www.ngz-online.de/app/feed/rss/eishockey'),
|
||||||
|
(u'Basketball', u'http://www.ngz-online.de/app/feed/rss/basketball'),
|
||||||
|
(u'Handball', u'http://www.ngz-online.de/app/feed/rss/handball'),
|
||||||
|
(u'Motorsport', u'http://www.ngz-online.de/app/feed/rss/motorsport'),
|
||||||
|
(u'Tennis', u'http://www.ngz-online.de/app/feed/rss/tennis'),
|
||||||
|
(u'Radsport', u'http://www.ngz-online.de/app/feed/rss/radsport'),
|
||||||
|
(u'Kultur', u'http://www.ngz-online.de/app/feed/rss/kultur'),
|
||||||
|
(u'Gesellschaft', u'http://www.ngz-online.de/app/feed/rss/gesellschaft'),
|
||||||
|
(u'Wissenschaft', u'http://www.ngz-online.de/app/feed/rss/wissen'),
|
||||||
|
(u'Gesundheit', u'http://www.ngz-online.de/app/feed/rss/gesundheit'),
|
||||||
|
(u'Digitale Welt', u'http://www.ngz-online.de/app/feed/rss/digitale'),
|
||||||
|
(u'Auto & Mobil', u'http://www.ngz-online.de/app/feed/rss/auto'),
|
||||||
|
(u'Reise & Welt', u'http://www.ngz-online.de/app/feed/rss/reise'),
|
||||||
|
(u'Beruf & Karriere', u'http://www.ngz-online.de/app/feed/rss/beruf'),
|
||||||
|
(u'Herzrasen', u'http://www.ngz-online.de/app/feed/rss/herzrasen'),
|
||||||
|
(u'About a Boy', u'http://www.ngz-online.de/app/feed/rss/about_a_boy'),
|
||||||
|
|
||||||
|
]
|
28
recipes/spektrum.recipe
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||||
|
class AdvancedUserRecipe1303841067(BasicNewsRecipe):
|
||||||
|
title = u'Spektrum (der Wissenschaft)'
|
||||||
|
__author__ = 'schuster'
|
||||||
|
oldest_article = 7
|
||||||
|
max_articles_per_feed = 100
|
||||||
|
language = 'de'
|
||||||
|
cover_url = 'http://upload.wikimedia.org/wikipedia/de/3/3b/Spektrum_der_Wissenschaft_Logo.svg'
|
||||||
|
|
||||||
|
remove_tags = [dict(attrs={'class':['hauptnaviPkt gainlayout', 'hauptnaviButton', 'suchButton', 'suchbegriffKasten', 'loginButton', 'subnavigation', 'artikelInfoLeiste gainlayout', 'artikelTools', 'nurLetzteSeite', 'link', 'boxUnterArtikel', 'leserbriefeBlock', 'boxTitel', 'boxInhalt', 'sehrklein', 'boxabstand', 'werbeboxinhalt', 'rbabstand', 'bildlinks', 'rechtebox', 'denkmalbox', 'denkmalfrage']}),
|
||||||
|
dict(id=['pflip', 'verlagsleiste', 'bereich', 'bannerVertikal', 'headerLogoLink', 'kopf', 'topNavi', 'headerSchnellsuche', 'headerSchnellsucheWarten', 'navigation', 'navigationL', 'navigationR', 'inhalt', 'rechtespalte', 'sdwboxenshop', 'shopboxen', 'fuss']),
|
||||||
|
dict(name=['naservice'])]
|
||||||
|
|
||||||
|
def print_version(self,url):
|
||||||
|
newurl = url.replace('artikel/', 'sixcms/detail.php?id=')
|
||||||
|
return newurl + '&_druckversion=1'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
feeds = [(u'Spektrum der Wissenschaft', u'http://www.spektrum.de/artikel/982623'),
|
||||||
|
(u'SpektrumDirekt', u'http://www.spektrumdirekt.de/artikel/996406'),
|
||||||
|
(u'Sterne und Weltraum', u'http://www.astronomie-heute.de/artikel/865248'),
|
||||||
|
(u'Gehirn & Geist', u'http://www.gehirn-und-geist.de/artikel/982626'),
|
||||||
|
(u'epoc', u'http://www.epoc.de/artikel/982625')
|
||||||
|
|
||||||
|
]
|
||||||
|
|
||||||
|
filter_regexps = [r'ads\.doubleclick\.net']
|
24
recipes/technology_review_de.recipe
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||||
|
class AdvancedUserRecipe1303841067(BasicNewsRecipe):
|
||||||
|
|
||||||
|
title = u'Technology Review'
|
||||||
|
__author__ = 'schuster'
|
||||||
|
remove_tags_before = dict(id='keywords')
|
||||||
|
remove_tags_after = dict(id='kommentar')
|
||||||
|
remove_tags = [dict(attrs={'class':['navi_oben_pvg', 'navi_oben_tarifr', 'navi_oben_itm', 'navi_oben_eve', 'navi_oben_whi', 'navi_oben_abo', 'navi_oben_shop', 'navi_top_logo', 'navi_top_abschnitt', 'first']}),
|
||||||
|
dict(id=['footer', 'toolsRight', 'articleInline', 'navigation', 'archive', 'side_search', 'blog_sidebar', 'side_tool', 'side_index']),
|
||||||
|
dict(name=['script', 'noscript', 'style'])]
|
||||||
|
oldest_article = 4
|
||||||
|
max_articles_per_feed = 100
|
||||||
|
no_stylesheets = True
|
||||||
|
use_embedded_content = False
|
||||||
|
language = 'de'
|
||||||
|
remove_javascript = True
|
||||||
|
|
||||||
|
def print_version(self, url):
|
||||||
|
return url + '?view=print'
|
||||||
|
|
||||||
|
|
||||||
|
feeds = [
|
||||||
|
(u'Technik News', u'http://www.heise.de/tr/news-atom.xml') ]
|
||||||
|
|
@ -1,5 +1,5 @@
|
|||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__copyright__ = '2008 - 2010, Darko Miletic <darko.miletic at gmail.com>'
|
__copyright__ = '2008 - 2011, Darko Miletic <darko.miletic at gmail.com>'
|
||||||
'''
|
'''
|
||||||
thenation.com
|
thenation.com
|
||||||
'''
|
'''
|
||||||
@ -16,10 +16,17 @@ class Thenation(BasicNewsRecipe):
|
|||||||
max_articles_per_feed = 100
|
max_articles_per_feed = 100
|
||||||
no_stylesheets = True
|
no_stylesheets = True
|
||||||
language = 'en'
|
language = 'en'
|
||||||
use_embedded_content = False
|
use_embedded_content = False
|
||||||
delay = 1
|
delay = 1
|
||||||
masthead_url = 'http://www.thenation.com/sites/default/themes/thenation/images/logo-main.gif'
|
masthead_url = 'http://www.thenation.com/sites/default/themes/thenation/images/logo-main.gif'
|
||||||
exra_css = ' body{font-family: Arial,Helvetica,sans-serif;} .print-created{font-size: small;} .caption{display: block; font-size: x-small;} '
|
login_url = 'http://www.thenation.com/user?destination=%3Cfront%3E'
|
||||||
|
publication_type = 'magazine'
|
||||||
|
needs_subscription = 'optional'
|
||||||
|
exra_css = """
|
||||||
|
body{font-family: Arial,Helvetica,sans-serif;}
|
||||||
|
.print-created{font-size: small;}
|
||||||
|
.caption{display: block; font-size: x-small;}
|
||||||
|
"""
|
||||||
|
|
||||||
conversion_options = {
|
conversion_options = {
|
||||||
'comment' : description
|
'comment' : description
|
||||||
@ -28,13 +35,30 @@ class Thenation(BasicNewsRecipe):
|
|||||||
, 'language' : language
|
, 'language' : language
|
||||||
}
|
}
|
||||||
|
|
||||||
keep_only_tags = [ dict(attrs={'class':['print-title','print-created','print-content','print-links']}) ]
|
keep_only_tags = [dict(attrs={'class':['print-title','print-created','print-content','print-links']})]
|
||||||
remove_tags = [dict(name='link')]
|
remove_tags = [dict(name=['link','iframe','base','meta','object','embed'])]
|
||||||
|
remove_attributes = ['lang']
|
||||||
|
|
||||||
feeds = [(u"Editor's Picks", u'http://www.thenation.com/rss/editors_picks')]
|
feeds = [(u"Articles", u'http://www.thenation.com/rss/articles')]
|
||||||
|
|
||||||
def print_version(self, url):
|
def print_version(self, url):
|
||||||
return url.replace('.thenation.com/','.thenation.com/print/')
|
return url.replace('.thenation.com/','.thenation.com/print/')
|
||||||
|
|
||||||
def preprocess_html(self, soup):
|
def get_browser(self):
|
||||||
return self.adeify_images(soup)
|
br = BasicNewsRecipe.get_browser()
|
||||||
|
br.open('http://www.thenation.com/')
|
||||||
|
if self.username is not None and self.password is not None:
|
||||||
|
br.open(self.login_url)
|
||||||
|
br.select_form(nr=1)
|
||||||
|
br['name'] = self.username
|
||||||
|
br['pass'] = self.password
|
||||||
|
br.submit()
|
||||||
|
return br
|
||||||
|
|
||||||
|
def get_cover_url(self):
|
||||||
|
soup = self.index_to_soup('http://www.thenation.com/issue/')
|
||||||
|
item = soup.find('div',attrs={'id':'cover-wrapper'})
|
||||||
|
if item:
|
||||||
|
return item.img['src']
|
||||||
|
return None
|
||||||
|
|
@ -14,6 +14,7 @@ class UnitedDaily(BasicNewsRecipe):
|
|||||||
(u'生活', u'http://udn.com/udnrss/life.xml'),
|
(u'生活', u'http://udn.com/udnrss/life.xml'),
|
||||||
(u'綜合', u'http://udn.com/udnrss/education.xml'),
|
(u'綜合', u'http://udn.com/udnrss/education.xml'),
|
||||||
(u'意見評論', u'http://udn.com/udnrss/opinion.xml'),
|
(u'意見評論', u'http://udn.com/udnrss/opinion.xml'),
|
||||||
|
(u'校園博覽會', u'http://mag.udn.com/udnrss/campus_rss.xml'),
|
||||||
(u'大台北', u'http://udn.com/udnrss/local_taipei.xml'),
|
(u'大台北', u'http://udn.com/udnrss/local_taipei.xml'),
|
||||||
(u'桃竹苗', u'http://udn.com/udnrss/local_tyhcml.xml'),
|
(u'桃竹苗', u'http://udn.com/udnrss/local_tyhcml.xml'),
|
||||||
(u'中彰投', u'http://udn.com/udnrss/local_tcchnt.xml'),
|
(u'中彰投', u'http://udn.com/udnrss/local_tcchnt.xml'),
|
||||||
@ -21,15 +22,21 @@ class UnitedDaily(BasicNewsRecipe):
|
|||||||
(u'高屏離島', u'http://udn.com/udnrss/local_ksptisland.xml'),
|
(u'高屏離島', u'http://udn.com/udnrss/local_ksptisland.xml'),
|
||||||
(u'基宜花東', u'http://udn.com/udnrss/local_klilhltt.xml'),
|
(u'基宜花東', u'http://udn.com/udnrss/local_klilhltt.xml'),
|
||||||
(u'台灣百寶鄉', u'http://udn.com/udnrss/local_oddlyenough.xml'),
|
(u'台灣百寶鄉', u'http://udn.com/udnrss/local_oddlyenough.xml'),
|
||||||
|
(u'台灣人物', u'http://mag.udn.com/udnrss/people_rss.xml'),
|
||||||
(u'兩岸要聞', u'http://udn.com/udnrss/mainland.xml'),
|
(u'兩岸要聞', u'http://udn.com/udnrss/mainland.xml'),
|
||||||
(u'國際焦點', u'http://udn.com/udnrss/international.xml'),
|
(u'國際焦點', u'http://udn.com/udnrss/international.xml'),
|
||||||
(u'台商經貿', u'http://udn.com/udnrss/financechina.xml'),
|
(u'台商經貿', u'http://udn.com/udnrss/financechina.xml'),
|
||||||
(u'國際財經', u'http://udn.com/udnrss/financeworld.xml'),
|
(u'國際財經', u'http://udn.com/udnrss/financeworld.xml'),
|
||||||
|
(u'全球觀察', u'http://mag.udn.com/udnrss/world_rss.xml'),
|
||||||
(u'財經焦點', u'http://udn.com/udnrss/financesfocus.xml'),
|
(u'財經焦點', u'http://udn.com/udnrss/financesfocus.xml'),
|
||||||
(u'股市要聞', u'http://udn.com/udnrss/stock.xml'),
|
(u'股市要聞', u'http://udn.com/udnrss/stock.xml'),
|
||||||
(u'股市快訊', u'http://udn.com/udnrss/stklatest.xml'),
|
(u'股市快訊', u'http://udn.com/udnrss/stklatest.xml'),
|
||||||
(u'稅務法務', u'http://udn.com/udnrss/tax.xml'),
|
(u'稅務法務', u'http://udn.com/udnrss/tax.xml'),
|
||||||
(u'房市情報', u'http://udn.com/udnrss/houses.xml'),
|
(u'房市情報', u'http://udn.com/udnrss/houses.xml'),
|
||||||
|
(u'個人理財', u'http://mag.udn.com/udnrss/wealth_rss.xml'),
|
||||||
|
(u'研究報告', u'http://mag.udn.com/udnrss/report_rss.xml'),
|
||||||
|
(u'基金', u'http://mag.udn.com/udnrss/fund_rss.xml'),
|
||||||
|
(u'理財會客室', u'http://mag.udn.com/udnrss/m_forum_rss.xml'),
|
||||||
(u'棒球', u'http://udn.com/udnrss/baseball.xml'),
|
(u'棒球', u'http://udn.com/udnrss/baseball.xml'),
|
||||||
(u'籃球', u'http://udn.com/udnrss/basketball.xml'),
|
(u'籃球', u'http://udn.com/udnrss/basketball.xml'),
|
||||||
(u'體壇動態', u'http://udn.com/udnrss/sportsfocus.xml'),
|
(u'體壇動態', u'http://udn.com/udnrss/sportsfocus.xml'),
|
||||||
@ -40,19 +47,24 @@ class UnitedDaily(BasicNewsRecipe):
|
|||||||
(u'電影世界', u'http://udn.com/udnrss/movie.xml'),
|
(u'電影世界', u'http://udn.com/udnrss/movie.xml'),
|
||||||
(u'流行音樂', u'http://udn.com/udnrss/music.xml'),
|
(u'流行音樂', u'http://udn.com/udnrss/music.xml'),
|
||||||
(u'觀點專題', u'http://udn.com/udnrss/starssubject.xml'),
|
(u'觀點專題', u'http://udn.com/udnrss/starssubject.xml'),
|
||||||
|
(u'消費流行', u'http://mag.udn.com/udnrss/happylife_rss.xml'),
|
||||||
(u'食樂指南', u'http://udn.com/udnrss/food.xml'),
|
(u'食樂指南', u'http://udn.com/udnrss/food.xml'),
|
||||||
|
(u'數位資訊', u'http://mag.udn.com/udnrss/digital_rss.xml'),
|
||||||
(u'折扣好康', u'http://udn.com/udnrss/shopping.xml'),
|
(u'折扣好康', u'http://udn.com/udnrss/shopping.xml'),
|
||||||
|
(u'發燒車訊', u'http://mag.udn.com/udnrss/car_rss.xml'),
|
||||||
(u'醫藥新聞', u'http://udn.com/udnrss/health.xml'),
|
(u'醫藥新聞', u'http://udn.com/udnrss/health.xml'),
|
||||||
(u'家婦繽紛', u'http://udn.com/udnrss/benfen.xml'),
|
(u'家婦繽紛', u'http://udn.com/udnrss/benfen.xml'),
|
||||||
(u'談星論命', u'http://udn.com/udnrss/astrology.xml'),
|
(u'談星論命', u'http://udn.com/udnrss/astrology.xml'),
|
||||||
(u'文化副刊', u'http://udn.com/udnrss/reading.xml'),
|
(u'文化副刊', u'http://udn.com/udnrss/reading.xml'),
|
||||||
|
(u'旅遊休閒', u'http://travel.udn.com/udnrss/travel_rss.xml'),
|
||||||
|
(u'健康醫藥', u'http://mag.udn.com/udnrss/life_rss.xml'),
|
||||||
]
|
]
|
||||||
|
|
||||||
extra_css = '''div[id='story_title'] {font-size:200%; font-weight:bold;}'''
|
extra_css = '''div[id='story_title'] {font-size:200%; font-weight:bold;} td[class='story_title'] {font-size:200%; font-weight:bold;} td[class='story_title'] td[class='story_title']>div {font-size:200%; font-weight:bold;}'''
|
||||||
|
|
||||||
__author__ = 'Eddie Lau'
|
__author__ = 'Eddie Lau'
|
||||||
__version__ = '1.0'
|
__version__ = '1.1'
|
||||||
language = 'zh'
|
language = 'zh-TW'
|
||||||
publisher = 'United Daily News Group'
|
publisher = 'United Daily News Group'
|
||||||
description = 'United Daily (Taiwan)'
|
description = 'United Daily (Taiwan)'
|
||||||
category = 'News, Chinese, Taiwan'
|
category = 'News, Chinese, Taiwan'
|
||||||
@ -63,5 +75,12 @@ class UnitedDaily(BasicNewsRecipe):
|
|||||||
conversion_options = {'linearize_tables':True}
|
conversion_options = {'linearize_tables':True}
|
||||||
masthead_url = 'http://udn.com/NEWS/2004/images/logo_udn.gif'
|
masthead_url = 'http://udn.com/NEWS/2004/images/logo_udn.gif'
|
||||||
cover_url = 'http://udn.com/NEWS/2004/images/logo_udn.gif'
|
cover_url = 'http://udn.com/NEWS/2004/images/logo_udn.gif'
|
||||||
keep_only_tags = [dict(name='div', attrs={'id':['story_title','story_author', 'story']})]
|
keep_only_tags = [dict(name='td', attrs={'class':['story_title']}),
|
||||||
|
dict(name='div', attrs={'id':['story_title']}),
|
||||||
|
dict(name='td', attrs={'class':['story_author']}),
|
||||||
|
dict(name='div', attrs={'id':['story_author']}),
|
||||||
|
dict(name='td', attrs={'class':['story']}),
|
||||||
|
dict(name='div', attrs={'id':['story']}),
|
||||||
|
]
|
||||||
remove_tags = [dict(name='div', attrs={'id':['mvouter']})]
|
remove_tags = [dict(name='div', attrs={'id':['mvouter']})]
|
||||||
|
|
||||||
|
@ -1,64 +1,75 @@
|
|||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2011, Darko Miletic <darko.miletic at gmail.com>'
|
||||||
|
'''
|
||||||
|
www.washingtonpost.com
|
||||||
|
'''
|
||||||
|
|
||||||
|
from calibre import strftime
|
||||||
from calibre.web.feeds.news import BasicNewsRecipe
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
|
||||||
|
class TheWashingtonPost(BasicNewsRecipe):
|
||||||
|
title = 'The Washington Post'
|
||||||
|
__author__ = 'Darko Miletic'
|
||||||
|
description = 'Leading source for news, video and opinion on politics, business, world and national news, science, travel, entertainment and more. Our local coverage includes reporting on education, crime, weather, traffic, real estate, jobs and cars for DC, Maryland and Virginia. Offering award-winning opinion writing, entertainment information and restaurant reviews.'
|
||||||
|
publisher = 'The Washington Post Company'
|
||||||
|
category = 'news, politics, USA'
|
||||||
|
oldest_article = 2
|
||||||
|
max_articles_per_feed = 200
|
||||||
|
no_stylesheets = True
|
||||||
|
encoding = 'utf8'
|
||||||
|
delay = 1
|
||||||
|
use_embedded_content = False
|
||||||
|
language = 'en'
|
||||||
|
remove_empty_feeds = True
|
||||||
|
publication_type = 'newspaper'
|
||||||
|
masthead_url = 'http://www.washingtonpost.com/rw/sites/twpweb/img/logos/twp_logo_300.gif'
|
||||||
|
cover_url = strftime('http://www.washingtonpost.com/rw/WashingtonPost/Content/Epaper/%Y-%m-%d/Ax1.pdf')
|
||||||
|
extra_css = """
|
||||||
|
body{font-family: Georgia,serif }
|
||||||
|
"""
|
||||||
|
|
||||||
class WashingtonPost(BasicNewsRecipe):
|
conversion_options = {
|
||||||
|
'comment' : description
|
||||||
|
, 'tags' : category
|
||||||
|
, 'publisher' : publisher
|
||||||
|
, 'language' : language
|
||||||
|
}
|
||||||
|
|
||||||
title = 'Washington Post'
|
keep_only_tags = [dict(attrs={'id':['content','entryhead','entrytext']})]
|
||||||
description = 'US political news'
|
remove_tags = [
|
||||||
__author__ = 'Kovid Goyal'
|
dict(name=['meta','link','iframe','base'])
|
||||||
use_embedded_content = False
|
,dict(attrs={'id':'multimedia-leaf-page'})
|
||||||
max_articles_per_feed = 20
|
]
|
||||||
language = 'en'
|
remove_attributes= ['lang','property','epochtime','datetitle','pagetype','contenttype','comparetime']
|
||||||
encoding = 'utf-8'
|
|
||||||
|
|
||||||
|
|
||||||
remove_javascript = True
|
|
||||||
no_stylesheets = True
|
|
||||||
|
|
||||||
feeds = [
|
feeds = [
|
||||||
('Politics', 'http://www.washingtonpost.com/rss/politics'),
|
(u'World' , u'http://feeds.washingtonpost.com/rss/world' )
|
||||||
('Nation', 'http://www.washingtonpost.com/rss/national'),
|
,(u'National' , u'http://feeds.washingtonpost.com/rss/national' )
|
||||||
('World', 'http://www.washingtonpost.com/rss/world'),
|
,(u'White House' , u'http://feeds.washingtonpost.com/rss/politics/whitehouse' )
|
||||||
('Business', 'http://www.washingtonpost.com/rss/business'),
|
,(u'Business' , u'http://feeds.washingtonpost.com/rss/business' )
|
||||||
('Lifestyle', 'http://www.washingtonpost.com/rss/lifestyle'),
|
,(u'Opinions' , u'http://feeds.washingtonpost.com/rss/opinions' )
|
||||||
('Sports', 'http://www.washingtonpost.com/rss/sports'),
|
,(u'Investigations' , u'http://feeds.washingtonpost.com/rss/investigations' )
|
||||||
('Redskins', 'http://www.washingtonpost.com/rss/sports/redskins'),
|
,(u'Local' , u'http://feeds.washingtonpost.com/rss/local' )
|
||||||
('Opinions', 'http://www.washingtonpost.com/rss/opinions'),
|
,(u'Entertainment' , u'http://feeds.washingtonpost.com/rss/entertainment' )
|
||||||
('Entertainment', 'http://www.washingtonpost.com/rss/entertainment'),
|
,(u'Sports' , u'http://feeds.washingtonpost.com/rss/sports' )
|
||||||
('Local', 'http://www.washingtonpost.com/rss/local'),
|
,(u'Redskins' , u'http://feeds.washingtonpost.com/rss/sports/redskins' )
|
||||||
('Investigations',
|
,(u'Special Reports', u'http://feeds.washingtonpost.com/rss/national/special-reports')
|
||||||
'http://www.washingtonpost.com/rss/investigations'),
|
]
|
||||||
]
|
|
||||||
|
|
||||||
remove_tags = [
|
|
||||||
{'class':lambda x: x and 'article-toolbar' in x},
|
|
||||||
{'class':lambda x: x and 'quick-comments' in x},
|
|
||||||
{'class':lambda x: x and 'tweet' in x},
|
|
||||||
{'class':lambda x: x and 'article-related' in x},
|
|
||||||
{'class':lambda x: x and 'hidden' in x.split()},
|
|
||||||
{'class':lambda x: x and 'also-read' in x.split()},
|
|
||||||
{'class':lambda x: x and 'partners-content' in x.split()},
|
|
||||||
{'class':['module share', 'module ads', 'comment-vars', 'hidden',
|
|
||||||
'share-icons-wrap', 'comments', 'flipper']},
|
|
||||||
{'id':['right-rail', 'save-and-share']},
|
|
||||||
{'width':'1', 'height':'1'},
|
|
||||||
|
|
||||||
]
|
|
||||||
|
|
||||||
keep_only_tags = dict(id=['content', 'article'])
|
|
||||||
|
|
||||||
def get_article_url(self, *args):
|
|
||||||
ans = BasicNewsRecipe.get_article_url(self, *args)
|
|
||||||
ans = ans.rpartition('?')[0]
|
|
||||||
if ans.endswith('_video.html'):
|
|
||||||
return None
|
|
||||||
if 'ads.pheedo.com' in ans:
|
|
||||||
return None
|
|
||||||
#if not ans.endswith('_blog.html'):
|
|
||||||
# return None
|
|
||||||
return ans
|
|
||||||
|
|
||||||
|
|
||||||
def print_version(self, url):
|
def print_version(self, url):
|
||||||
return url.replace('_story.html', '_singlePage.html')
|
if '_story.html' in url:
|
||||||
|
return url.replace('_story.html','_print.html')
|
||||||
|
return url
|
||||||
|
|
||||||
|
def get_article_url(self, article):
|
||||||
|
link = BasicNewsRecipe.get_article_url(self,article)
|
||||||
|
if not 'washingtonpost.com' in link:
|
||||||
|
self.log('Skipping adds:', link)
|
||||||
|
return None
|
||||||
|
for it in ['_video.html','_gallery.html','_links.html']:
|
||||||
|
if it in link:
|
||||||
|
self.log('Skipping non-article:', link)
|
||||||
|
return None
|
||||||
|
return link
|
||||||
|
|
||||||
|
@ -41,14 +41,20 @@ authors_completer_append_separator = False
|
|||||||
#: Author sort name algorithm
|
#: Author sort name algorithm
|
||||||
# The algorithm used to copy author to author_sort
|
# The algorithm used to copy author to author_sort
|
||||||
# Possible values are:
|
# Possible values are:
|
||||||
# invert: use "fn ln" -> "ln, fn" (the default algorithm)
|
# invert: use "fn ln" -> "ln, fn"
|
||||||
# copy : copy author to author_sort without modification
|
# copy : copy author to author_sort without modification
|
||||||
# comma : use 'copy' if there is a ',' in the name, otherwise use 'invert'
|
# comma : use 'copy' if there is a ',' in the name, otherwise use 'invert'
|
||||||
# nocomma : "fn ln" -> "ln fn" (without the comma)
|
# nocomma : "fn ln" -> "ln fn" (without the comma)
|
||||||
# When this tweak is changed, the author_sort values stored with each author
|
# When this tweak is changed, the author_sort values stored with each author
|
||||||
# must be recomputed by right-clicking on an author in the left-hand tags pane,
|
# must be recomputed by right-clicking on an author in the left-hand tags pane,
|
||||||
# selecting 'manage authors', and pressing 'Recalculate all author sort values'.
|
# selecting 'manage authors', and pressing 'Recalculate all author sort values'.
|
||||||
|
# The author name suffixes are words that are ignored when they occur at the
|
||||||
|
# end of an author name. The case of the suffix is ignored and trailing
|
||||||
|
# periods are automatically handled.
|
||||||
author_sort_copy_method = 'comma'
|
author_sort_copy_method = 'comma'
|
||||||
|
author_name_suffixes = ('Jr', 'Sr', 'Inc', 'Ph.D', 'Phd',
|
||||||
|
'MD', 'M.D', 'I', 'II', 'III', 'IV',
|
||||||
|
'Junior', 'Senior')
|
||||||
|
|
||||||
#: Use author sort in Tag Browser
|
#: Use author sort in Tag Browser
|
||||||
# Set which author field to display in the tags pane (the list of authors,
|
# Set which author field to display in the tags pane (the list of authors,
|
||||||
@ -345,3 +351,11 @@ send_news_to_device_location = "main"
|
|||||||
# work on all operating systems)
|
# work on all operating systems)
|
||||||
server_listen_on = '0.0.0.0'
|
server_listen_on = '0.0.0.0'
|
||||||
|
|
||||||
|
#: Unified toolbar on OS X
|
||||||
|
# If you enable this option and restart calibre, the toolbar will be 'unified'
|
||||||
|
# with the titlebar as is normal for OS X applications. However, doing this has
|
||||||
|
# various bugs, for instance the minimum width of the toolbar becomes twice
|
||||||
|
# what it should be and it causes other random bugs on some systems, so turn it
|
||||||
|
# on at your own risk!
|
||||||
|
unified_title_toolbar_on_osx = False
|
||||||
|
|
||||||
|
@ -1,17 +1,21 @@
|
|||||||
{
|
{
|
||||||
|
"and": "def evaluate(self, formatter, kwargs, mi, locals, *args):\n i = 0\n while i < len(args):\n if not args[i]:\n return ''\n i += 1\n return '1'\n",
|
||||||
"contains": "def evaluate(self, formatter, kwargs, mi, locals,\n val, test, value_if_present, value_if_not):\n if re.search(test, val):\n return value_if_present\n else:\n return value_if_not\n",
|
"contains": "def evaluate(self, formatter, kwargs, mi, locals,\n val, test, value_if_present, value_if_not):\n if re.search(test, val):\n return value_if_present\n else:\n return value_if_not\n",
|
||||||
"divide": "def evaluate(self, formatter, kwargs, mi, locals, x, y):\n x = float(x if x else 0)\n y = float(y if y else 0)\n return unicode(x / y)\n",
|
"divide": "def evaluate(self, formatter, kwargs, mi, locals, x, y):\n x = float(x if x else 0)\n y = float(y if y else 0)\n return unicode(x / y)\n",
|
||||||
"uppercase": "def evaluate(self, formatter, kwargs, mi, locals, val):\n return val.upper()\n",
|
"uppercase": "def evaluate(self, formatter, kwargs, mi, locals, val):\n return val.upper()\n",
|
||||||
"strcat": "def evaluate(self, formatter, kwargs, mi, locals, *args):\n i = 0\n res = ''\n for i in range(0, len(args)):\n res += args[i]\n return res\n",
|
"strcat": "def evaluate(self, formatter, kwargs, mi, locals, *args):\n i = 0\n res = ''\n for i in range(0, len(args)):\n res += args[i]\n return res\n",
|
||||||
"substr": "def evaluate(self, formatter, kwargs, mi, locals, str_, start_, end_):\n return str_[int(start_): len(str_) if int(end_) == 0 else int(end_)]\n",
|
"in_list": "def evaluate(self, formatter, kwargs, mi, locals, val, sep, pat, fv, nfv):\n l = [v.strip() for v in val.split(sep) if v.strip()]\n for v in l:\n if re.search(pat, v):\n return fv\n return nfv\n",
|
||||||
|
"multiply": "def evaluate(self, formatter, kwargs, mi, locals, x, y):\n x = float(x if x else 0)\n y = float(y if y else 0)\n return unicode(x * y)\n",
|
||||||
"ifempty": "def evaluate(self, formatter, kwargs, mi, locals, val, value_if_empty):\n if val:\n return val\n else:\n return value_if_empty\n",
|
"ifempty": "def evaluate(self, formatter, kwargs, mi, locals, val, value_if_empty):\n if val:\n return val\n else:\n return value_if_empty\n",
|
||||||
"booksize": "def evaluate(self, formatter, kwargs, mi, locals):\n if mi.book_size is not None:\n try:\n return str(mi.book_size)\n except:\n pass\n return ''\n",
|
"booksize": "def evaluate(self, formatter, kwargs, mi, locals):\n if mi.book_size is not None:\n try:\n return str(mi.book_size)\n except:\n pass\n return ''\n",
|
||||||
"select": "def evaluate(self, formatter, kwargs, mi, locals, val, key):\n if not val:\n return ''\n vals = [v.strip() for v in val.split(',')]\n for v in vals:\n if v.startswith(key+':'):\n return v[len(key)+1:]\n return ''\n",
|
"select": "def evaluate(self, formatter, kwargs, mi, locals, val, key):\n if not val:\n return ''\n vals = [v.strip() for v in val.split(',')]\n for v in vals:\n if v.startswith(key+':'):\n return v[len(key)+1:]\n return ''\n",
|
||||||
"field": "def evaluate(self, formatter, kwargs, mi, locals, name):\n return formatter.get_value(name, [], kwargs)\n",
|
"strcmp": "def evaluate(self, formatter, kwargs, mi, locals, x, y, lt, eq, gt):\n v = strcmp(x, y)\n if v < 0:\n return lt\n if v == 0:\n return eq\n return gt\n",
|
||||||
|
"first_non_empty": "def evaluate(self, formatter, kwargs, mi, locals, *args):\n i = 0\n while i < len(args):\n if args[i]:\n return args[i]\n i += 1\n return ''\n",
|
||||||
|
"re": "def evaluate(self, formatter, kwargs, mi, locals, val, pattern, replacement):\n return re.sub(pattern, replacement, val)\n",
|
||||||
"subtract": "def evaluate(self, formatter, kwargs, mi, locals, x, y):\n x = float(x if x else 0)\n y = float(y if y else 0)\n return unicode(x - y)\n",
|
"subtract": "def evaluate(self, formatter, kwargs, mi, locals, x, y):\n x = float(x if x else 0)\n y = float(y if y else 0)\n return unicode(x - y)\n",
|
||||||
"list_item": "def evaluate(self, formatter, kwargs, mi, locals, val, index, sep):\n if not val:\n return ''\n index = int(index)\n val = val.split(sep)\n try:\n return val[index]\n except:\n return ''\n",
|
"list_item": "def evaluate(self, formatter, kwargs, mi, locals, val, index, sep):\n if not val:\n return ''\n index = int(index)\n val = val.split(sep)\n try:\n return val[index]\n except:\n return ''\n",
|
||||||
"shorten": "def evaluate(self, formatter, kwargs, mi, locals,\n val, leading, center_string, trailing):\n l = max(0, int(leading))\n t = max(0, int(trailing))\n if len(val) > l + len(center_string) + t:\n return val[0:l] + center_string + ('' if t == 0 else val[-t:])\n else:\n return val\n",
|
"shorten": "def evaluate(self, formatter, kwargs, mi, locals,\n val, leading, center_string, trailing):\n l = max(0, int(leading))\n t = max(0, int(trailing))\n if len(val) > l + len(center_string) + t:\n return val[0:l] + center_string + ('' if t == 0 else val[-t:])\n else:\n return val\n",
|
||||||
"re": "def evaluate(self, formatter, kwargs, mi, locals, val, pattern, replacement):\n return re.sub(pattern, replacement, val)\n",
|
"field": "def evaluate(self, formatter, kwargs, mi, locals, name):\n return formatter.get_value(name, [], kwargs)\n",
|
||||||
"add": "def evaluate(self, formatter, kwargs, mi, locals, x, y):\n x = float(x if x else 0)\n y = float(y if y else 0)\n return unicode(x + y)\n",
|
"add": "def evaluate(self, formatter, kwargs, mi, locals, x, y):\n x = float(x if x else 0)\n y = float(y if y else 0)\n return unicode(x + y)\n",
|
||||||
"lookup": "def evaluate(self, formatter, kwargs, mi, locals, val, *args):\n if len(args) == 2: # here for backwards compatibility\n if val:\n return formatter.vformat('{'+args[0].strip()+'}', [], kwargs)\n else:\n return formatter.vformat('{'+args[1].strip()+'}', [], kwargs)\n if (len(args) % 2) != 1:\n raise ValueError(_('lookup requires either 2 or an odd number of arguments'))\n i = 0\n while i < len(args):\n if i + 1 >= len(args):\n return formatter.vformat('{' + args[i].strip() + '}', [], kwargs)\n if re.search(args[i], val):\n return formatter.vformat('{'+args[i+1].strip() + '}', [], kwargs)\n i += 2\n",
|
"lookup": "def evaluate(self, formatter, kwargs, mi, locals, val, *args):\n if len(args) == 2: # here for backwards compatibility\n if val:\n return formatter.vformat('{'+args[0].strip()+'}', [], kwargs)\n else:\n return formatter.vformat('{'+args[1].strip()+'}', [], kwargs)\n if (len(args) % 2) != 1:\n raise ValueError(_('lookup requires either 2 or an odd number of arguments'))\n i = 0\n while i < len(args):\n if i + 1 >= len(args):\n return formatter.vformat('{' + args[i].strip() + '}', [], kwargs)\n if re.search(args[i], val):\n return formatter.vformat('{'+args[i+1].strip() + '}', [], kwargs)\n i += 2\n",
|
||||||
"template": "def evaluate(self, formatter, kwargs, mi, locals, template):\n template = template.replace('[[', '{').replace(']]', '}')\n return formatter.__class__().safe_format(template, kwargs, 'TEMPLATE', mi)\n",
|
"template": "def evaluate(self, formatter, kwargs, mi, locals, template):\n template = template.replace('[[', '{').replace(']]', '}')\n return formatter.__class__().safe_format(template, kwargs, 'TEMPLATE', mi)\n",
|
||||||
@ -21,14 +25,15 @@
|
|||||||
"sublist": "def evaluate(self, formatter, kwargs, mi, locals, val, start_index, end_index, sep):\n if not val:\n return ''\n si = int(start_index)\n ei = int(end_index)\n val = val.split(sep)\n try:\n if ei == 0:\n return sep.join(val[si:])\n else:\n return sep.join(val[si:ei])\n except:\n return ''\n",
|
"sublist": "def evaluate(self, formatter, kwargs, mi, locals, val, start_index, end_index, sep):\n if not val:\n return ''\n si = int(start_index)\n ei = int(end_index)\n val = val.split(sep)\n try:\n if ei == 0:\n return sep.join(val[si:])\n else:\n return sep.join(val[si:ei])\n except:\n return ''\n",
|
||||||
"test": "def evaluate(self, formatter, kwargs, mi, locals, val, value_if_set, value_not_set):\n if val:\n return value_if_set\n else:\n return value_not_set\n",
|
"test": "def evaluate(self, formatter, kwargs, mi, locals, val, value_if_set, value_not_set):\n if val:\n return value_if_set\n else:\n return value_not_set\n",
|
||||||
"eval": "def evaluate(self, formatter, kwargs, mi, locals, template):\n from formatter import eval_formatter\n template = template.replace('[[', '{').replace(']]', '}')\n return eval_formatter.safe_format(template, locals, 'EVAL', None)\n",
|
"eval": "def evaluate(self, formatter, kwargs, mi, locals, template):\n from formatter import eval_formatter\n template = template.replace('[[', '{').replace(']]', '}')\n return eval_formatter.safe_format(template, locals, 'EVAL', None)\n",
|
||||||
"multiply": "def evaluate(self, formatter, kwargs, mi, locals, x, y):\n x = float(x if x else 0)\n y = float(y if y else 0)\n return unicode(x * y)\n",
|
"not": "def evaluate(self, formatter, kwargs, mi, locals, *args):\n i = 0\n while i < len(args):\n if args[i]:\n return '1'\n i += 1\n return ''\n",
|
||||||
"format_date": "def evaluate(self, formatter, kwargs, mi, locals, val, format_string):\n if not val:\n return ''\n try:\n dt = parse_date(val)\n s = format_date(dt, format_string)\n except:\n s = 'BAD DATE'\n return s\n",
|
"format_date": "def evaluate(self, formatter, kwargs, mi, locals, val, format_string):\n if not val:\n return ''\n try:\n dt = parse_date(val)\n s = format_date(dt, format_string)\n except:\n s = 'BAD DATE'\n return s\n",
|
||||||
"capitalize": "def evaluate(self, formatter, kwargs, mi, locals, val):\n return capitalize(val)\n",
|
"capitalize": "def evaluate(self, formatter, kwargs, mi, locals, val):\n return capitalize(val)\n",
|
||||||
"count": "def evaluate(self, formatter, kwargs, mi, locals, val, sep):\n return unicode(len(val.split(sep)))\n",
|
"count": "def evaluate(self, formatter, kwargs, mi, locals, val, sep):\n return unicode(len(val.split(sep)))\n",
|
||||||
"lowercase": "def evaluate(self, formatter, kwargs, mi, locals, val):\n return val.lower()\n",
|
"lowercase": "def evaluate(self, formatter, kwargs, mi, locals, val):\n return val.lower()\n",
|
||||||
"strcmp": "def evaluate(self, formatter, kwargs, mi, locals, x, y, lt, eq, gt):\n v = strcmp(x, y)\n if v < 0:\n return lt\n if v == 0:\n return eq\n return gt\n",
|
"substr": "def evaluate(self, formatter, kwargs, mi, locals, str_, start_, end_):\n return str_[int(start_): len(str_) if int(end_) == 0 else int(end_)]\n",
|
||||||
"switch": "def evaluate(self, formatter, kwargs, mi, locals, val, *args):\n if (len(args) % 2) != 1:\n raise ValueError(_('switch requires an odd number of arguments'))\n i = 0\n while i < len(args):\n if i + 1 >= len(args):\n return args[i]\n if re.search(args[i], val):\n return args[i+1]\n i += 2\n",
|
|
||||||
"assign": "def evaluate(self, formatter, kwargs, mi, locals, target, value):\n locals[target] = value\n return value\n",
|
"assign": "def evaluate(self, formatter, kwargs, mi, locals, target, value):\n locals[target] = value\n return value\n",
|
||||||
|
"switch": "def evaluate(self, formatter, kwargs, mi, locals, val, *args):\n if (len(args) % 2) != 1:\n raise ValueError(_('switch requires an odd number of arguments'))\n i = 0\n while i < len(args):\n if i + 1 >= len(args):\n return args[i]\n if re.search(args[i], val):\n return args[i+1]\n i += 2\n",
|
||||||
|
"or": "def evaluate(self, formatter, kwargs, mi, locals, *args):\n i = 0\n while i < len(args):\n if args[i]:\n return '1'\n i += 1\n return ''\n",
|
||||||
"raw_field": "def evaluate(self, formatter, kwargs, mi, locals, name):\n return unicode(getattr(mi, name, None))\n",
|
"raw_field": "def evaluate(self, formatter, kwargs, mi, locals, name):\n return unicode(getattr(mi, name, None))\n",
|
||||||
"cmp": "def evaluate(self, formatter, kwargs, mi, locals, x, y, lt, eq, gt):\n x = float(x if x else 0)\n y = float(y if y else 0)\n if x < y:\n return lt\n if x == y:\n return eq\n return gt\n"
|
"cmp": "def evaluate(self, formatter, kwargs, mi, locals, x, y, lt, eq, gt):\n x = float(x if x else 0)\n y = float(y if y else 0)\n if x < y:\n return lt\n if x == y:\n return eq\n return gt\n"
|
||||||
}
|
}
|
@ -12,7 +12,9 @@ is64bit = platform.architecture()[0] == '64bit'
|
|||||||
iswindows = re.search('win(32|64)', sys.platform)
|
iswindows = re.search('win(32|64)', sys.platform)
|
||||||
isosx = 'darwin' in sys.platform
|
isosx = 'darwin' in sys.platform
|
||||||
isfreebsd = 'freebsd' in sys.platform
|
isfreebsd = 'freebsd' in sys.platform
|
||||||
islinux = not isosx and not iswindows and not isfreebsd
|
isnetbsd = 'netbsd' in sys.platform
|
||||||
|
isbsd = isnetbsd or isfreebsd
|
||||||
|
islinux = not isosx and not iswindows and not isbsd
|
||||||
SRC = os.path.abspath('src')
|
SRC = os.path.abspath('src')
|
||||||
sys.path.insert(0, SRC)
|
sys.path.insert(0, SRC)
|
||||||
sys.resources_location = os.path.join(os.path.dirname(SRC), 'resources')
|
sys.resources_location = os.path.join(os.path.dirname(SRC), 'resources')
|
||||||
|
@ -11,7 +11,7 @@ from distutils import sysconfig
|
|||||||
|
|
||||||
from PyQt4.pyqtconfig import QtGuiModuleMakefile
|
from PyQt4.pyqtconfig import QtGuiModuleMakefile
|
||||||
|
|
||||||
from setup import Command, islinux, isfreebsd, isosx, SRC, iswindows
|
from setup import Command, islinux, isfreebsd, isbsd, isosx, SRC, iswindows
|
||||||
from setup.build_environment import fc_inc, fc_lib, chmlib_inc_dirs, \
|
from setup.build_environment import fc_inc, fc_lib, chmlib_inc_dirs, \
|
||||||
fc_error, poppler_libs, poppler_lib_dirs, poppler_inc_dirs, podofo_inc, \
|
fc_error, poppler_libs, poppler_lib_dirs, poppler_inc_dirs, podofo_inc, \
|
||||||
podofo_lib, podofo_error, poppler_error, pyqt, OSX_SDK, NMAKE, \
|
podofo_lib, podofo_error, poppler_error, pyqt, OSX_SDK, NMAKE, \
|
||||||
@ -21,7 +21,7 @@ from setup.build_environment import fc_inc, fc_lib, chmlib_inc_dirs, \
|
|||||||
jpg_lib_dirs, chmlib_lib_dirs, sqlite_inc_dirs, icu_inc_dirs, \
|
jpg_lib_dirs, chmlib_lib_dirs, sqlite_inc_dirs, icu_inc_dirs, \
|
||||||
icu_lib_dirs
|
icu_lib_dirs
|
||||||
MT
|
MT
|
||||||
isunix = islinux or isosx or isfreebsd
|
isunix = islinux or isosx or isbsd
|
||||||
|
|
||||||
make = 'make' if isunix else NMAKE
|
make = 'make' if isunix else NMAKE
|
||||||
|
|
||||||
@ -205,7 +205,7 @@ if islinux:
|
|||||||
ldflags.append('-lpython'+sysconfig.get_python_version())
|
ldflags.append('-lpython'+sysconfig.get_python_version())
|
||||||
|
|
||||||
|
|
||||||
if isfreebsd:
|
if isbsd:
|
||||||
cflags.append('-pthread')
|
cflags.append('-pthread')
|
||||||
ldflags.append('-shared')
|
ldflags.append('-shared')
|
||||||
cflags.append('-I'+sysconfig.get_python_inc())
|
cflags.append('-I'+sysconfig.get_python_inc())
|
||||||
|
@ -8,7 +8,7 @@ __docformat__ = 'restructuredtext en'
|
|||||||
|
|
||||||
import sys, os, textwrap, subprocess, shutil, tempfile, atexit, stat, shlex
|
import sys, os, textwrap, subprocess, shutil, tempfile, atexit, stat, shlex
|
||||||
|
|
||||||
from setup import Command, islinux, isfreebsd, basenames, modules, functions, \
|
from setup import Command, islinux, isfreebsd, isbsd, basenames, modules, functions, \
|
||||||
__appname__, __version__
|
__appname__, __version__
|
||||||
|
|
||||||
HEADER = '''\
|
HEADER = '''\
|
||||||
@ -116,7 +116,7 @@ class Develop(Command):
|
|||||||
|
|
||||||
|
|
||||||
def pre_sub_commands(self, opts):
|
def pre_sub_commands(self, opts):
|
||||||
if not (islinux or isfreebsd):
|
if not (islinux or isbsd):
|
||||||
self.info('\nSetting up a source based development environment is only '
|
self.info('\nSetting up a source based development environment is only '
|
||||||
'supported on linux. On other platforms, see the User Manual'
|
'supported on linux. On other platforms, see the User Manual'
|
||||||
' for help with setting up a development environment.')
|
' for help with setting up a development environment.')
|
||||||
@ -156,7 +156,7 @@ class Develop(Command):
|
|||||||
self.warn('Failed to compile mount helper. Auto mounting of',
|
self.warn('Failed to compile mount helper. Auto mounting of',
|
||||||
' devices will not work')
|
' devices will not work')
|
||||||
|
|
||||||
if not isfreebsd and os.geteuid() != 0:
|
if not isbsd and os.geteuid() != 0:
|
||||||
return self.warn('Must be run as root to compile mount helper. Auto '
|
return self.warn('Must be run as root to compile mount helper. Auto '
|
||||||
'mounting of devices will not work.')
|
'mounting of devices will not work.')
|
||||||
src = os.path.join(self.SRC, 'calibre', 'devices', 'linux_mount_helper.c')
|
src = os.path.join(self.SRC, 'calibre', 'devices', 'linux_mount_helper.c')
|
||||||
@ -168,7 +168,7 @@ class Develop(Command):
|
|||||||
ret = p.wait()
|
ret = p.wait()
|
||||||
if ret != 0:
|
if ret != 0:
|
||||||
return warn()
|
return warn()
|
||||||
if not isfreebsd:
|
if not isbsd:
|
||||||
os.chown(dest, 0, 0)
|
os.chown(dest, 0, 0)
|
||||||
os.chmod(dest, stat.S_ISUID|stat.S_ISGID|stat.S_IRUSR|stat.S_IWUSR|\
|
os.chmod(dest, stat.S_ISUID|stat.S_ISGID|stat.S_IRUSR|stat.S_IWUSR|\
|
||||||
stat.S_IXUSR|stat.S_IXGRP|stat.S_IXOTH)
|
stat.S_IXUSR|stat.S_IXGRP|stat.S_IXOTH)
|
||||||
|
@ -32,7 +32,6 @@ class Win32(VMInstaller):
|
|||||||
FREEZE_TEMPLATE = 'python -OO setup.py {freeze_command} --no-ice'
|
FREEZE_TEMPLATE = 'python -OO setup.py {freeze_command} --no-ice'
|
||||||
INSTALLER_EXT = 'msi'
|
INSTALLER_EXT = 'msi'
|
||||||
SHUTDOWN_CMD = ['shutdown.exe', '-s', '-f', '-t', '0']
|
SHUTDOWN_CMD = ['shutdown.exe', '-s', '-f', '-t', '0']
|
||||||
BUILD_BUILD = ['python setup.py kakasi',] + VMInstaller.BUILD_BUILD
|
|
||||||
|
|
||||||
def download_installer(self):
|
def download_installer(self):
|
||||||
installer = self.installer()
|
installer = self.installer()
|
||||||
|
@ -26,6 +26,11 @@
|
|||||||
</Upgrade>
|
</Upgrade>
|
||||||
<CustomAction Id="PreventDowngrading" Error="Newer version already installed."/>
|
<CustomAction Id="PreventDowngrading" Error="Newer version already installed."/>
|
||||||
|
|
||||||
|
<Property Id="APPLICATIONFOLDER">
|
||||||
|
<RegistrySearch Id='calibreInstDir' Type='raw'
|
||||||
|
Root='HKLM' Key="Software\{app}\Installer" Name="InstallPath" />
|
||||||
|
</Property>
|
||||||
|
|
||||||
<Directory Id='TARGETDIR' Name='SourceDir'>
|
<Directory Id='TARGETDIR' Name='SourceDir'>
|
||||||
<Merge Id="VCRedist" SourceFile="{crt_msm}" DiskId="1" Language="0"/>
|
<Merge Id="VCRedist" SourceFile="{crt_msm}" DiskId="1" Language="0"/>
|
||||||
<Directory Id='ProgramFilesFolder' Name='PFiles'>
|
<Directory Id='ProgramFilesFolder' Name='PFiles'>
|
||||||
@ -43,6 +48,9 @@
|
|||||||
<Environment Id='UpdatePath' Name='PATH' Action='set' System='yes' Part='last' Value='[APPLICATIONFOLDER]' />
|
<Environment Id='UpdatePath' Name='PATH' Action='set' System='yes' Part='last' Value='[APPLICATIONFOLDER]' />
|
||||||
<RegistryValue Root="HKCU" Key="Software\Microsoft\{app}" Name="system_path_updated" Type="integer" Value="1" KeyPath="yes"/>
|
<RegistryValue Root="HKCU" Key="Software\Microsoft\{app}" Name="system_path_updated" Type="integer" Value="1" KeyPath="yes"/>
|
||||||
</Component>
|
</Component>
|
||||||
|
<Component Id="RememberInstallDir" Guid="*">
|
||||||
|
<RegistryValue Root="HKLM" Key="Software\{app}\Installer" Name="InstallPath" Type="string" Value="[APPLICATIONFOLDER]" KeyPath="yes"/>
|
||||||
|
</Component>
|
||||||
</DirectoryRef>
|
</DirectoryRef>
|
||||||
|
|
||||||
<DirectoryRef Id="ApplicationProgramsFolder">
|
<DirectoryRef Id="ApplicationProgramsFolder">
|
||||||
@ -87,7 +95,8 @@
|
|||||||
ConfigurableDirectory="APPLICATIONFOLDER">
|
ConfigurableDirectory="APPLICATIONFOLDER">
|
||||||
|
|
||||||
<Feature Id="MainApplication" Title="Program Files" Level="1"
|
<Feature Id="MainApplication" Title="Program Files" Level="1"
|
||||||
Description="All the files need to run {app}" Absent="disallow">
|
Description="All the files needed to run {app}" Absent="disallow">
|
||||||
|
<ComponentRef Id="RememberInstallDir"/>
|
||||||
</Feature>
|
</Feature>
|
||||||
|
|
||||||
<Feature Id="VCRedist" Title="Visual C++ 8.0 Runtime" AllowAdvertise="no" Display="hidden" Level="1">
|
<Feature Id="VCRedist" Title="Visual C++ 8.0 Runtime" AllowAdvertise="no" Display="hidden" Level="1">
|
||||||
@ -115,7 +124,7 @@
|
|||||||
<Property Id="ARPPRODUCTICON" Value="main_icon" />
|
<Property Id="ARPPRODUCTICON" Value="main_icon" />
|
||||||
|
|
||||||
<Condition
|
<Condition
|
||||||
Message="This application is only supported on Windows XP SP2, or higher.">
|
Message="This application is only supported on Windows XP SP3, or higher.">
|
||||||
<![CDATA[Installed OR (VersionNT >= 501)]]>
|
<![CDATA[Installed OR (VersionNT >= 501)]]>
|
||||||
</Condition>
|
</Condition>
|
||||||
<InstallExecuteSequence>
|
<InstallExecuteSequence>
|
||||||
|
@ -6,10 +6,10 @@ __license__ = 'GPL v3'
|
|||||||
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
|
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
import os, cPickle, re, anydbm, shutil, marshal, zipfile, glob
|
import os, cPickle, re, shutil, marshal, zipfile, glob
|
||||||
from zlib import compress
|
from zlib import compress
|
||||||
|
|
||||||
from setup import Command, basenames, __appname__, iswindows
|
from setup import Command, basenames, __appname__
|
||||||
|
|
||||||
def get_opts_from_parser(parser):
|
def get_opts_from_parser(parser):
|
||||||
def do_opt(opt):
|
def do_opt(opt):
|
||||||
@ -34,12 +34,12 @@ class Kakasi(Command):
|
|||||||
self.records = {}
|
self.records = {}
|
||||||
src = self.j(self.KAKASI_PATH, 'kakasidict.utf8')
|
src = self.j(self.KAKASI_PATH, 'kakasidict.utf8')
|
||||||
dest = self.j(self.RESOURCES, 'localization',
|
dest = self.j(self.RESOURCES, 'localization',
|
||||||
'pykakasi','kanwadict2.db')
|
'pykakasi','kanwadict2.pickle')
|
||||||
base = os.path.dirname(dest)
|
base = os.path.dirname(dest)
|
||||||
if not os.path.exists(base):
|
if not os.path.exists(base):
|
||||||
os.makedirs(base)
|
os.makedirs(base)
|
||||||
|
|
||||||
if self.newer(dest, src) or iswindows:
|
if self.newer(dest, src):
|
||||||
self.info('\tGenerating Kanwadict')
|
self.info('\tGenerating Kanwadict')
|
||||||
|
|
||||||
for line in open(src, "r"):
|
for line in open(src, "r"):
|
||||||
@ -50,7 +50,7 @@ class Kakasi(Command):
|
|||||||
dest = self.j(self.RESOURCES, 'localization',
|
dest = self.j(self.RESOURCES, 'localization',
|
||||||
'pykakasi','itaijidict2.pickle')
|
'pykakasi','itaijidict2.pickle')
|
||||||
|
|
||||||
if self.newer(dest, src) or iswindows:
|
if self.newer(dest, src):
|
||||||
self.info('\tGenerating Itaijidict')
|
self.info('\tGenerating Itaijidict')
|
||||||
self.mkitaiji(src, dest)
|
self.mkitaiji(src, dest)
|
||||||
|
|
||||||
@ -58,7 +58,7 @@ class Kakasi(Command):
|
|||||||
dest = self.j(self.RESOURCES, 'localization',
|
dest = self.j(self.RESOURCES, 'localization',
|
||||||
'pykakasi','kanadict2.pickle')
|
'pykakasi','kanadict2.pickle')
|
||||||
|
|
||||||
if self.newer(dest, src) or iswindows:
|
if self.newer(dest, src):
|
||||||
self.info('\tGenerating kanadict')
|
self.info('\tGenerating kanadict')
|
||||||
self.mkkanadict(src, dest)
|
self.mkkanadict(src, dest)
|
||||||
|
|
||||||
@ -75,7 +75,7 @@ class Kakasi(Command):
|
|||||||
continue
|
continue
|
||||||
pair = re.sub(r'\\u([0-9a-fA-F]{4})', lambda x:unichr(int(x.group(1),16)), line)
|
pair = re.sub(r'\\u([0-9a-fA-F]{4})', lambda x:unichr(int(x.group(1),16)), line)
|
||||||
dic[pair[0]] = pair[1]
|
dic[pair[0]] = pair[1]
|
||||||
cPickle.dump(dic, open(dst, 'w'), protocol=-1) #pickle
|
cPickle.dump(dic, open(dst, 'wb'), protocol=-1) #pickle
|
||||||
|
|
||||||
def mkkanadict(self, src, dst):
|
def mkkanadict(self, src, dst):
|
||||||
dic = {}
|
dic = {}
|
||||||
@ -87,7 +87,7 @@ class Kakasi(Command):
|
|||||||
continue
|
continue
|
||||||
(alpha, kana) = line.split(' ')
|
(alpha, kana) = line.split(' ')
|
||||||
dic[kana] = alpha
|
dic[kana] = alpha
|
||||||
cPickle.dump(dic, open(dst, 'w'), protocol=-1) #pickle
|
cPickle.dump(dic, open(dst, 'wb'), protocol=-1) #pickle
|
||||||
|
|
||||||
def parsekdict(self, line):
|
def parsekdict(self, line):
|
||||||
line = line.decode("utf-8").strip()
|
line = line.decode("utf-8").strip()
|
||||||
@ -115,16 +115,11 @@ class Kakasi(Command):
|
|||||||
self.records[key][kanji]=[(yomi, tail)]
|
self.records[key][kanji]=[(yomi, tail)]
|
||||||
|
|
||||||
def kanwaout(self, out):
|
def kanwaout(self, out):
|
||||||
try:
|
with open(out, 'wb') as f:
|
||||||
# Needed as otherwise anydbm tries to create a gdbm db when the db
|
dic = {}
|
||||||
# created on Unix is found
|
for k, v in self.records.iteritems():
|
||||||
os.remove(out)
|
dic[k] = compress(marshal.dumps(v))
|
||||||
except:
|
cPickle.dump(dic, f, -1)
|
||||||
pass
|
|
||||||
dic = anydbm.open(out, 'n')
|
|
||||||
for (k, v) in self.records.iteritems():
|
|
||||||
dic[k] = compress(marshal.dumps(v))
|
|
||||||
dic.close()
|
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
kakasi = self.j(self.RESOURCES, 'localization', 'pykakasi')
|
kakasi = self.j(self.RESOURCES, 'localization', 'pykakasi')
|
||||||
|
@ -12,10 +12,10 @@ from functools import partial
|
|||||||
warnings.simplefilter('ignore', DeprecationWarning)
|
warnings.simplefilter('ignore', DeprecationWarning)
|
||||||
|
|
||||||
|
|
||||||
from calibre.constants import (iswindows, isosx, islinux, isfreebsd, isfrozen,
|
from calibre.constants import (iswindows, isosx, islinux, isfrozen,
|
||||||
preferred_encoding, __appname__, __version__, __author__,
|
isbsd, preferred_encoding, __appname__, __version__, __author__,
|
||||||
win32event, win32api, winerror, fcntl,
|
win32event, win32api, winerror, fcntl,
|
||||||
filesystem_encoding, plugins, config_dir)
|
filesystem_encoding, plugins, config_dir)
|
||||||
from calibre.startup import winutil, winutilerror
|
from calibre.startup import winutil, winutilerror
|
||||||
|
|
||||||
if False and islinux and not getattr(sys, 'frozen', False):
|
if False and islinux and not getattr(sys, 'frozen', False):
|
||||||
@ -31,7 +31,7 @@ if False:
|
|||||||
# Prevent pyflakes from complaining
|
# Prevent pyflakes from complaining
|
||||||
winutil, winutilerror, __appname__, islinux, __version__
|
winutil, winutilerror, __appname__, islinux, __version__
|
||||||
fcntl, win32event, isfrozen, __author__
|
fcntl, win32event, isfrozen, __author__
|
||||||
winerror, win32api, isfreebsd
|
winerror, win32api, isbsd
|
||||||
|
|
||||||
_mt_inited = False
|
_mt_inited = False
|
||||||
def _init_mimetypes():
|
def _init_mimetypes():
|
||||||
@ -630,6 +630,24 @@ def human_readable(size):
|
|||||||
size = size[:-2]
|
size = size[:-2]
|
||||||
return size + " " + suffix
|
return size + " " + suffix
|
||||||
|
|
||||||
|
def remove_bracketed_text(src,
|
||||||
|
brackets={u'(':u')', u'[':u']', u'{':u'}'}):
|
||||||
|
from collections import Counter
|
||||||
|
counts = Counter()
|
||||||
|
buf = []
|
||||||
|
src = force_unicode(src)
|
||||||
|
rmap = dict([(v, k) for k, v in brackets.iteritems()])
|
||||||
|
for char in src:
|
||||||
|
if char in brackets:
|
||||||
|
counts[char] += 1
|
||||||
|
elif char in rmap:
|
||||||
|
idx = rmap[char]
|
||||||
|
if counts[idx] > 0:
|
||||||
|
counts[idx] -= 1
|
||||||
|
elif sum(counts.itervalues()) < 1:
|
||||||
|
buf.append(char)
|
||||||
|
return u''.join(buf)
|
||||||
|
|
||||||
if isosx:
|
if isosx:
|
||||||
import glob, shutil
|
import glob, shutil
|
||||||
fdir = os.path.expanduser('~/.fonts')
|
fdir = os.path.expanduser('~/.fonts')
|
||||||
|
@ -4,7 +4,7 @@ __license__ = 'GPL v3'
|
|||||||
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
__appname__ = u'calibre'
|
__appname__ = u'calibre'
|
||||||
numeric_version = (0, 8, 1)
|
numeric_version = (0, 8, 2)
|
||||||
__version__ = u'.'.join(map(unicode, numeric_version))
|
__version__ = u'.'.join(map(unicode, numeric_version))
|
||||||
__author__ = u"Kovid Goyal <kovid@kovidgoyal.net>"
|
__author__ = u"Kovid Goyal <kovid@kovidgoyal.net>"
|
||||||
|
|
||||||
@ -27,7 +27,9 @@ iswindows = 'win32' in _plat or 'win64' in _plat
|
|||||||
isosx = 'darwin' in _plat
|
isosx = 'darwin' in _plat
|
||||||
isnewosx = isosx and getattr(sys, 'new_app_bundle', False)
|
isnewosx = isosx and getattr(sys, 'new_app_bundle', False)
|
||||||
isfreebsd = 'freebsd' in _plat
|
isfreebsd = 'freebsd' in _plat
|
||||||
islinux = not(iswindows or isosx or isfreebsd)
|
isnetbsd = 'netbsd' in _plat
|
||||||
|
isbsd = isfreebsd or isnetbsd
|
||||||
|
islinux = not(iswindows or isosx or isbsd)
|
||||||
isfrozen = hasattr(sys, 'frozen')
|
isfrozen = hasattr(sys, 'frozen')
|
||||||
isunix = isosx or islinux
|
isunix = isosx or islinux
|
||||||
|
|
||||||
|
@ -607,10 +607,23 @@ class StoreBase(Plugin): # {{{
|
|||||||
supported_platforms = ['windows', 'osx', 'linux']
|
supported_platforms = ['windows', 'osx', 'linux']
|
||||||
author = 'John Schember'
|
author = 'John Schember'
|
||||||
type = _('Store')
|
type = _('Store')
|
||||||
|
# Information about the store. Should be in the primary language
|
||||||
|
# of the store. This should not be translatable when set by
|
||||||
|
# a subclass.
|
||||||
|
description = _('An ebook store.')
|
||||||
minimum_calibre_version = (0, 8, 0)
|
minimum_calibre_version = (0, 8, 0)
|
||||||
|
version = (1, 0, 1)
|
||||||
|
|
||||||
actual_plugin = None
|
actual_plugin = None
|
||||||
|
|
||||||
|
# Does the store only distribute ebooks without DRM.
|
||||||
|
drm_free_only = False
|
||||||
|
# This is the 2 letter country code for the corporate
|
||||||
|
# headquarters of the store.
|
||||||
|
headquarters = ''
|
||||||
|
# All formats the store distributes ebooks in.
|
||||||
|
formats = []
|
||||||
|
|
||||||
def load_actual_plugin(self, gui):
|
def load_actual_plugin(self, gui):
|
||||||
'''
|
'''
|
||||||
This method must return the actual interface action plugin object.
|
This method must return the actual interface action plugin object.
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import os.path
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||||
|
|
||||||
@ -593,7 +594,7 @@ from calibre.devices.iliad.driver import ILIAD
|
|||||||
from calibre.devices.irexdr.driver import IREXDR1000, IREXDR800
|
from calibre.devices.irexdr.driver import IREXDR1000, IREXDR800
|
||||||
from calibre.devices.jetbook.driver import JETBOOK, MIBUK, JETBOOK_MINI
|
from calibre.devices.jetbook.driver import JETBOOK, MIBUK, JETBOOK_MINI
|
||||||
from calibre.devices.kindle.driver import KINDLE, KINDLE2, KINDLE_DX
|
from calibre.devices.kindle.driver import KINDLE, KINDLE2, KINDLE_DX
|
||||||
from calibre.devices.nook.driver import NOOK, NOOK_COLOR
|
from calibre.devices.nook.driver import NOOK, NOOK_COLOR, NOOK_TSR
|
||||||
from calibre.devices.prs505.driver import PRS505
|
from calibre.devices.prs505.driver import PRS505
|
||||||
from calibre.devices.user_defined.driver import USER_DEFINED
|
from calibre.devices.user_defined.driver import USER_DEFINED
|
||||||
from calibre.devices.android.driver import ANDROID, S60
|
from calibre.devices.android.driver import ANDROID, S60
|
||||||
@ -692,8 +693,7 @@ plugins += [
|
|||||||
KINDLE,
|
KINDLE,
|
||||||
KINDLE2,
|
KINDLE2,
|
||||||
KINDLE_DX,
|
KINDLE_DX,
|
||||||
NOOK,
|
NOOK, NOOK_COLOR, NOOK_TSR,
|
||||||
NOOK_COLOR,
|
|
||||||
PRS505,
|
PRS505,
|
||||||
ANDROID,
|
ANDROID,
|
||||||
S60,
|
S60,
|
||||||
@ -854,6 +854,17 @@ class ActionStore(InterfaceActionBase):
|
|||||||
author = 'John Schember'
|
author = 'John Schember'
|
||||||
actual_plugin = 'calibre.gui2.actions.store:StoreAction'
|
actual_plugin = 'calibre.gui2.actions.store:StoreAction'
|
||||||
|
|
||||||
|
def customization_help(self, gui=False):
|
||||||
|
return 'Customize the behavior of the store search.'
|
||||||
|
|
||||||
|
def config_widget(self):
|
||||||
|
from calibre.gui2.store.config.store import config_widget as get_cw
|
||||||
|
return get_cw()
|
||||||
|
|
||||||
|
def save_settings(self, config_widget):
|
||||||
|
from calibre.gui2.store.config.store import save_settings as save
|
||||||
|
save(config_widget)
|
||||||
|
|
||||||
plugins += [ActionAdd, ActionFetchAnnotations, ActionGenerateCatalog,
|
plugins += [ActionAdd, ActionFetchAnnotations, ActionGenerateCatalog,
|
||||||
ActionConvert, ActionDelete, ActionEditMetadata, ActionView,
|
ActionConvert, ActionDelete, ActionEditMetadata, ActionView,
|
||||||
ActionFetchNews, ActionSaveToDisk, ActionShowBookDetails,
|
ActionFetchNews, ActionSaveToDisk, ActionShowBookDetails,
|
||||||
@ -1094,121 +1105,326 @@ plugins += [LookAndFeel, Behavior, Columns, Toolbar, Search, InputOptions,
|
|||||||
# Store plugins {{{
|
# Store plugins {{{
|
||||||
class StoreAmazonKindleStore(StoreBase):
|
class StoreAmazonKindleStore(StoreBase):
|
||||||
name = 'Amazon Kindle'
|
name = 'Amazon Kindle'
|
||||||
description = _('Kindle books from Amazon')
|
description = u'Kindle books from Amazon.'
|
||||||
actual_plugin = 'calibre.gui2.store.amazon_plugin:AmazonKindleStore'
|
actual_plugin = 'calibre.gui2.store.amazon_plugin:AmazonKindleStore'
|
||||||
|
|
||||||
|
drm_free_only = False
|
||||||
|
headquarters = 'US'
|
||||||
|
formats = ['KINDLE']
|
||||||
|
|
||||||
class StoreAmazonDEKindleStore(StoreBase):
|
class StoreAmazonDEKindleStore(StoreBase):
|
||||||
name = 'Amazon DE Kindle'
|
name = 'Amazon DE Kindle'
|
||||||
description = _('Kindle eBooks')
|
author = 'Charles Haley'
|
||||||
|
description = u'Kindle Bücher von Amazon.'
|
||||||
actual_plugin = 'calibre.gui2.store.amazon_de_plugin:AmazonDEKindleStore'
|
actual_plugin = 'calibre.gui2.store.amazon_de_plugin:AmazonDEKindleStore'
|
||||||
|
|
||||||
|
drm_free_only = False
|
||||||
|
headquarters = 'DE'
|
||||||
|
formats = ['KINDLE']
|
||||||
|
|
||||||
class StoreAmazonUKKindleStore(StoreBase):
|
class StoreAmazonUKKindleStore(StoreBase):
|
||||||
name = 'Amazon UK Kindle'
|
name = 'Amazon UK Kindle'
|
||||||
description = _('Kindle books from Amazon.uk')
|
author = 'Charles Haley'
|
||||||
|
description = u'Kindle books from Amazon\'s UK web site. Also, includes French language ebooks.'
|
||||||
actual_plugin = 'calibre.gui2.store.amazon_uk_plugin:AmazonUKKindleStore'
|
actual_plugin = 'calibre.gui2.store.amazon_uk_plugin:AmazonUKKindleStore'
|
||||||
|
|
||||||
|
drm_free_only = False
|
||||||
|
headquarters = 'UK'
|
||||||
|
formats = ['KINDLE']
|
||||||
|
|
||||||
|
class StoreArchiveOrgStore(StoreBase):
|
||||||
|
name = 'Archive.org'
|
||||||
|
description = u'An Internet library offering permanent access for researchers, historians, scholars, people with disabilities, and the general public to historical collections that exist in digital format.'
|
||||||
|
actual_plugin = 'calibre.gui2.store.archive_org_plugin:ArchiveOrgStore'
|
||||||
|
|
||||||
|
drm_free_only = True
|
||||||
|
headquarters = 'US'
|
||||||
|
formats = ['DAISY', 'DJVU', 'EPUB', 'MOBI', 'PDF', 'TXT']
|
||||||
|
|
||||||
class StoreBaenWebScriptionStore(StoreBase):
|
class StoreBaenWebScriptionStore(StoreBase):
|
||||||
name = 'Baen WebScription'
|
name = 'Baen WebScription'
|
||||||
description = _('Ebooks for readers.')
|
description = u'Sci-Fi & Fantasy brought to you by Jim Baen.'
|
||||||
actual_plugin = 'calibre.gui2.store.baen_webscription_plugin:BaenWebScriptionStore'
|
actual_plugin = 'calibre.gui2.store.baen_webscription_plugin:BaenWebScriptionStore'
|
||||||
|
|
||||||
|
drm_free_only = True
|
||||||
|
headquarters = 'US'
|
||||||
|
formats = ['EPUB', 'LIT', 'LRF', 'MOBI', 'RB', 'RTF', 'ZIP']
|
||||||
|
|
||||||
class StoreBNStore(StoreBase):
|
class StoreBNStore(StoreBase):
|
||||||
name = 'Barnes and Noble'
|
name = 'Barnes and Noble'
|
||||||
description = _('Books, Textbooks, eBooks, Toys, Games and More.')
|
description = u'The world\'s largest book seller. As the ultimate destination for book lovers, Barnes & Noble.com offers an incredible array of content.'
|
||||||
actual_plugin = 'calibre.gui2.store.bn_plugin:BNStore'
|
actual_plugin = 'calibre.gui2.store.bn_plugin:BNStore'
|
||||||
|
|
||||||
|
drm_free_only = False
|
||||||
|
headquarters = 'US'
|
||||||
|
formats = ['NOOK']
|
||||||
|
|
||||||
class StoreBeamEBooksDEStore(StoreBase):
|
class StoreBeamEBooksDEStore(StoreBase):
|
||||||
name = 'Beam EBooks DE'
|
name = 'Beam EBooks DE'
|
||||||
description = _('der eBook Shop')
|
author = 'Charles Haley'
|
||||||
|
description = u'Bei uns finden Sie: Tausende deutschsprachige eBooks; Alle eBooks ohne hartes DRM; PDF, ePub und Mobipocket Format; Sofortige Verfügbarkeit - 24 Stunden am Tag; Günstige Preise; eBooks für viele Lesegeräte, PC,Mac und Smartphones; Viele Gratis eBooks'
|
||||||
actual_plugin = 'calibre.gui2.store.beam_ebooks_de_plugin:BeamEBooksDEStore'
|
actual_plugin = 'calibre.gui2.store.beam_ebooks_de_plugin:BeamEBooksDEStore'
|
||||||
|
|
||||||
|
drm_free_only = True
|
||||||
|
headquarters = 'DE'
|
||||||
|
formats = ['EPUB', 'MOBI', 'PDF']
|
||||||
|
|
||||||
class StoreBeWriteStore(StoreBase):
|
class StoreBeWriteStore(StoreBase):
|
||||||
name = 'BeWrite Books'
|
name = 'BeWrite Books'
|
||||||
description = _('Publishers of fine books.')
|
description = u'Publishers of fine books. Highly selective and editorially driven. Does not offer: books for children or exclusively YA, erotica, swords-and-sorcery fantasy and space-opera-style science fiction. All other genres are represented.'
|
||||||
actual_plugin = 'calibre.gui2.store.bewrite_plugin:BeWriteStore'
|
actual_plugin = 'calibre.gui2.store.bewrite_plugin:BeWriteStore'
|
||||||
|
|
||||||
|
drm_free_only = True
|
||||||
|
headquarters = 'US'
|
||||||
|
formats = ['EPUB', 'MOBI', 'PDF']
|
||||||
|
|
||||||
class StoreDieselEbooksStore(StoreBase):
|
class StoreDieselEbooksStore(StoreBase):
|
||||||
name = 'Diesel eBooks'
|
name = 'Diesel eBooks'
|
||||||
description = _('World Famous eBook Store.')
|
description = u'Instant access to over 2.4 million titles from hundreds of publishers including Harlequin, HarperCollins, John Wiley & Sons, McGraw-Hill, Simon & Schuster and Random House.'
|
||||||
actual_plugin = 'calibre.gui2.store.diesel_ebooks_plugin:DieselEbooksStore'
|
actual_plugin = 'calibre.gui2.store.diesel_ebooks_plugin:DieselEbooksStore'
|
||||||
|
|
||||||
|
drm_free_only = False
|
||||||
|
headquarters = 'US'
|
||||||
|
formats = ['EPUB', 'PDF']
|
||||||
|
|
||||||
class StoreEbookscomStore(StoreBase):
|
class StoreEbookscomStore(StoreBase):
|
||||||
name = 'eBooks.com'
|
name = 'eBooks.com'
|
||||||
description = _('The digital bookstore.')
|
description = u'Sells books in multiple electronic formats in all categories. Technical infrastructure is cutting edge, robust and scalable, with servers in the US and Europe.'
|
||||||
actual_plugin = 'calibre.gui2.store.ebooks_com_plugin:EbookscomStore'
|
actual_plugin = 'calibre.gui2.store.ebooks_com_plugin:EbookscomStore'
|
||||||
|
|
||||||
|
drm_free_only = False
|
||||||
|
headquarters = 'US'
|
||||||
|
formats = ['EPUB', 'LIT', 'MOBI', 'PDF']
|
||||||
|
|
||||||
class StoreEPubBuyDEStore(StoreBase):
|
class StoreEPubBuyDEStore(StoreBase):
|
||||||
name = 'EPUBBuy DE'
|
name = 'EPUBBuy DE'
|
||||||
description = _('EPUBReaders eBook Shop')
|
author = 'Charles Haley'
|
||||||
|
description = u'Bei EPUBBuy.com finden Sie ausschliesslich eBooks im weitverbreiteten EPUB-Format und ohne DRM. So haben Sie die freie Wahl, wo Sie Ihr eBook lesen: Tablet, eBook-Reader, Smartphone oder einfach auf Ihrem PC. So macht eBook-Lesen Spaß!'
|
||||||
actual_plugin = 'calibre.gui2.store.epubbuy_de_plugin:EPubBuyDEStore'
|
actual_plugin = 'calibre.gui2.store.epubbuy_de_plugin:EPubBuyDEStore'
|
||||||
|
|
||||||
|
drm_free_only = True
|
||||||
|
headquarters = 'DE'
|
||||||
|
formats = ['EPUB']
|
||||||
|
|
||||||
class StoreEHarlequinStore(StoreBase):
|
class StoreEHarlequinStore(StoreBase):
|
||||||
name = 'eHarlequin'
|
name = 'eHarlequin'
|
||||||
description = _('entertain, enrich, inspire.')
|
description = u'A global leader in series romance and one of the world\'s leading publishers of books for women. Offers women a broad range of reading from romance to bestseller fiction, from young adult novels to erotic literature, from nonfiction to fantasy, from African-American novels to inspirational romance, and more.'
|
||||||
actual_plugin = 'calibre.gui2.store.eharlequin_plugin:EHarlequinStore'
|
actual_plugin = 'calibre.gui2.store.eharlequin_plugin:EHarlequinStore'
|
||||||
|
|
||||||
|
drm_free_only = False
|
||||||
|
headquarters = 'CA'
|
||||||
|
formats = ['EPUB', 'PDF']
|
||||||
|
|
||||||
class StoreFeedbooksStore(StoreBase):
|
class StoreFeedbooksStore(StoreBase):
|
||||||
name = 'Feedbooks'
|
name = 'Feedbooks'
|
||||||
description = _('Read anywhere.')
|
description = u'Feedbooks is a cloud publishing and distribution service, connected to a large ecosystem of reading systems and social networks. Provides a variety of genres from independent and classic books.'
|
||||||
actual_plugin = 'calibre.gui2.store.feedbooks_plugin:FeedbooksStore'
|
actual_plugin = 'calibre.gui2.store.feedbooks_plugin:FeedbooksStore'
|
||||||
|
|
||||||
|
drm_free_only = False
|
||||||
|
headquarters = 'FR'
|
||||||
|
formats = ['EPUB', 'MOBI', 'PDF']
|
||||||
|
|
||||||
class StoreFoylesUKStore(StoreBase):
|
class StoreFoylesUKStore(StoreBase):
|
||||||
name = 'Foyles UK'
|
name = 'Foyles UK'
|
||||||
description = _('Foyles of London, online')
|
author = 'Charles Haley'
|
||||||
|
description = u'Foyles of London\'s ebook store. Provides extensive range covering all subjects.'
|
||||||
actual_plugin = 'calibre.gui2.store.foyles_uk_plugin:FoylesUKStore'
|
actual_plugin = 'calibre.gui2.store.foyles_uk_plugin:FoylesUKStore'
|
||||||
|
|
||||||
|
drm_free_only = False
|
||||||
|
headquarters = 'UK'
|
||||||
|
formats = ['EPUB', 'PDF']
|
||||||
|
|
||||||
|
class StoreGandalfStore(StoreBase):
|
||||||
|
name = 'Gandalf'
|
||||||
|
author = u'Tomasz Długosz'
|
||||||
|
description = u'Księgarnia internetowa Gandalf.'
|
||||||
|
actual_plugin = 'calibre.gui2.store.gandalf_plugin:GandalfStore'
|
||||||
|
|
||||||
|
drm_free_only = False
|
||||||
|
headquarters = 'PL'
|
||||||
|
formats = ['EPUB', 'PDF']
|
||||||
|
|
||||||
|
class StoreGoogleBooksStore(StoreBase):
|
||||||
|
name = 'Google Books'
|
||||||
|
description = u'Google Books'
|
||||||
|
actual_plugin = 'calibre.gui2.store.google_books_plugin:GoogleBooksStore'
|
||||||
|
|
||||||
|
drm_free_only = False
|
||||||
|
headquarters = 'US'
|
||||||
|
formats = ['EPUB', 'PDF', 'TXT']
|
||||||
|
|
||||||
class StoreGutenbergStore(StoreBase):
|
class StoreGutenbergStore(StoreBase):
|
||||||
name = 'Project Gutenberg'
|
name = 'Project Gutenberg'
|
||||||
description = _('The first producer of free ebooks.')
|
description = u'The first producer of free ebooks. Free in the United States because their copyright has expired. They may not be free of copyright in other countries. Readers outside of the United States must check the copyright laws of their countries before downloading or redistributing our ebooks.'
|
||||||
actual_plugin = 'calibre.gui2.store.gutenberg_plugin:GutenbergStore'
|
actual_plugin = 'calibre.gui2.store.gutenberg_plugin:GutenbergStore'
|
||||||
|
|
||||||
|
drm_free_only = True
|
||||||
|
headquarters = 'US'
|
||||||
|
formats = ['EPUB', 'HTML', 'MOBI', 'PDB', 'TXT']
|
||||||
|
|
||||||
class StoreKoboStore(StoreBase):
|
class StoreKoboStore(StoreBase):
|
||||||
name = 'Kobo'
|
name = 'Kobo'
|
||||||
description = _('eReading: anytime. anyplace.')
|
description = u'With over 2.3 million eBooks to browse we have engaged readers in over 200 countries in Kobo eReading. Our eBook listings include New York Times Bestsellers, award winners, classics and more!'
|
||||||
actual_plugin = 'calibre.gui2.store.kobo_plugin:KoboStore'
|
actual_plugin = 'calibre.gui2.store.kobo_plugin:KoboStore'
|
||||||
|
|
||||||
|
drm_free_only = False
|
||||||
|
headquarters = 'CA'
|
||||||
|
formats = ['EPUB']
|
||||||
|
|
||||||
|
class StoreLegimiStore(StoreBase):
|
||||||
|
name = 'Legimi'
|
||||||
|
author = u'Tomasz Długosz'
|
||||||
|
description = u'Tanie oraz darmowe ebooki, egazety i blogi w formacie EPUB, wprost na Twój e-czytnik, iPhone, iPad, Android i komputer'
|
||||||
|
actual_plugin = 'calibre.gui2.store.legimi_plugin:LegimiStore'
|
||||||
|
|
||||||
|
drm_free_only = False
|
||||||
|
headquarters = 'PL'
|
||||||
|
formats = ['EPUB']
|
||||||
|
|
||||||
class StoreManyBooksStore(StoreBase):
|
class StoreManyBooksStore(StoreBase):
|
||||||
name = 'ManyBooks'
|
name = 'ManyBooks'
|
||||||
description = _('The best ebooks at the best price: free!')
|
description = u'Public domain and creative commons works from many sources.'
|
||||||
actual_plugin = 'calibre.gui2.store.manybooks_plugin:ManyBooksStore'
|
actual_plugin = 'calibre.gui2.store.manybooks_plugin:ManyBooksStore'
|
||||||
|
|
||||||
|
drm_free_only = True
|
||||||
|
headquarters = 'US'
|
||||||
|
formats = ['EPUB', 'FB2', 'JAR', 'LIT', 'LRF', 'MOBI', 'PDB', 'PDF', 'RB', 'RTF', 'TCR', 'TXT', 'ZIP']
|
||||||
|
|
||||||
class StoreMobileReadStore(StoreBase):
|
class StoreMobileReadStore(StoreBase):
|
||||||
name = 'MobileRead'
|
name = 'MobileRead'
|
||||||
description = _('Ebooks handcrafted with the utmost care')
|
description = u'Ebooks handcrafted with the utmost care.'
|
||||||
actual_plugin = 'calibre.gui2.store.mobileread.mobileread_plugin:MobileReadStore'
|
actual_plugin = 'calibre.gui2.store.mobileread.mobileread_plugin:MobileReadStore'
|
||||||
|
|
||||||
|
drm_free_only = True
|
||||||
|
headquarters = 'CH'
|
||||||
|
formats = ['EPUB', 'IMP', 'LRF', 'LIT', 'MOBI', 'PDF']
|
||||||
|
|
||||||
|
class StoreNextoStore(StoreBase):
|
||||||
|
name = 'Nexto'
|
||||||
|
author = u'Tomasz Długosz'
|
||||||
|
description = u'Największy w Polsce sklep internetowy z audiobookami mp3, ebookami pdf oraz prasą do pobrania on-line.'
|
||||||
|
actual_plugin = 'calibre.gui2.store.nexto_plugin:NextoStore'
|
||||||
|
|
||||||
|
drm_free_only = False
|
||||||
|
headquarters = 'PL'
|
||||||
|
formats = ['EPUB', 'PDF']
|
||||||
|
|
||||||
class StoreOpenLibraryStore(StoreBase):
|
class StoreOpenLibraryStore(StoreBase):
|
||||||
name = 'Open Library'
|
name = 'Open Library'
|
||||||
description = _('One web page for every book.')
|
description = u'One web page for every book ever published. The goal is to be a true online library. Over 20 million records from a variety of large catalogs as well as single contributions, with more on the way.'
|
||||||
actual_plugin = 'calibre.gui2.store.open_library_plugin:OpenLibraryStore'
|
actual_plugin = 'calibre.gui2.store.open_library_plugin:OpenLibraryStore'
|
||||||
|
|
||||||
|
drm_free_only = True
|
||||||
|
headquarters = 'US'
|
||||||
|
formats = ['DAISY', 'DJVU', 'EPUB', 'MOBI', 'PDF', 'TXT']
|
||||||
|
|
||||||
|
class StoreOReillyStore(StoreBase):
|
||||||
|
name = 'OReilly'
|
||||||
|
description = u'Programming and tech ebooks from OReilly.'
|
||||||
|
actual_plugin = 'calibre.gui2.store.oreilly_plugin:OReillyStore'
|
||||||
|
|
||||||
|
drm_free_only = True
|
||||||
|
headquarters = 'US'
|
||||||
|
formats = ['APK', 'DAISY', 'EPUB', 'MOBI', 'PDF']
|
||||||
|
|
||||||
|
class StorePragmaticBookshelfStore(StoreBase):
|
||||||
|
name = 'Pragmatic Bookshelf'
|
||||||
|
description = u'The Pragmatic Bookshelf\'s collection of programming and tech books avaliable as ebooks.'
|
||||||
|
actual_plugin = 'calibre.gui2.store.pragmatic_bookshelf_plugin:PragmaticBookshelfStore'
|
||||||
|
|
||||||
|
drm_free_only = True
|
||||||
|
headquarters = 'US'
|
||||||
|
formats = ['EPUB', 'MOBI', 'PDF']
|
||||||
|
|
||||||
class StoreSmashwordsStore(StoreBase):
|
class StoreSmashwordsStore(StoreBase):
|
||||||
name = 'Smashwords'
|
name = 'Smashwords'
|
||||||
description = _('Your ebook. Your way.')
|
description = u'An ebook publishing and distribution platform for ebook authors, publishers and readers. Covers many genres and formats.'
|
||||||
actual_plugin = 'calibre.gui2.store.smashwords_plugin:SmashwordsStore'
|
actual_plugin = 'calibre.gui2.store.smashwords_plugin:SmashwordsStore'
|
||||||
|
|
||||||
|
drm_free_only = True
|
||||||
|
headquarters = 'US'
|
||||||
|
formats = ['EPUB', 'HTML', 'LRF', 'MOBI', 'PDB', 'RTF', 'TXT']
|
||||||
|
|
||||||
|
class StoreVirtualoStore(StoreBase):
|
||||||
|
name = 'Virtualo'
|
||||||
|
author = u'Tomasz Długosz'
|
||||||
|
description = u'Księgarnia internetowa, która oferuje bezpieczny i szeroki dostęp do książek w formie cyfrowej.'
|
||||||
|
actual_plugin = 'calibre.gui2.store.virtualo_plugin:VirtualoStore'
|
||||||
|
|
||||||
|
drm_free_only = False
|
||||||
|
headquarters = 'PL'
|
||||||
|
formats = ['EPUB', 'PDF']
|
||||||
|
|
||||||
class StoreWaterstonesUKStore(StoreBase):
|
class StoreWaterstonesUKStore(StoreBase):
|
||||||
name = 'Waterstones UK'
|
name = 'Waterstones UK'
|
||||||
description = _('Feel every word')
|
author = 'Charles Haley'
|
||||||
|
description = u'Waterstone\'s mission is to be the leading Bookseller on the High Street and online providing customers the widest choice, great value and expert advice from a team passionate about Bookselling.'
|
||||||
actual_plugin = 'calibre.gui2.store.waterstones_uk_plugin:WaterstonesUKStore'
|
actual_plugin = 'calibre.gui2.store.waterstones_uk_plugin:WaterstonesUKStore'
|
||||||
|
|
||||||
|
drm_free_only = False
|
||||||
|
headquarters = 'UK'
|
||||||
|
formats = ['EPUB', 'PDF']
|
||||||
|
|
||||||
class StoreWeightlessBooksStore(StoreBase):
|
class StoreWeightlessBooksStore(StoreBase):
|
||||||
name = 'Weightless Books'
|
name = 'Weightless Books'
|
||||||
description = '(e)Books That Don\'t Weigh You Down'
|
description = u'An independent DRM-free ebooksite devoted to ebooks of all sorts.'
|
||||||
actual_plugin = 'calibre.gui2.store.weightless_books_plugin:WeightlessBooksStore'
|
actual_plugin = 'calibre.gui2.store.weightless_books_plugin:WeightlessBooksStore'
|
||||||
|
|
||||||
|
drm_free_only = True
|
||||||
|
headquarters = 'US'
|
||||||
|
formats = ['EPUB', 'HTML', 'LIT', 'MOBI', 'PDF']
|
||||||
|
|
||||||
class StoreWizardsTowerBooksStore(StoreBase):
|
class StoreWizardsTowerBooksStore(StoreBase):
|
||||||
name = 'Wizards Tower Books'
|
name = 'Wizards Tower Books'
|
||||||
description = 'Wizard\'s Tower Press'
|
description = u'A science fiction and fantasy publisher. Concentrates mainly on making out-of-print works available once more as e-books, and helping other small presses exploit the e-book market. Also publishes a small number of limited-print-run anthologies with a view to encouraging diversity in the science fiction and fantasy field.'
|
||||||
actual_plugin = 'calibre.gui2.store.wizards_tower_books_plugin:WizardsTowerBooksStore'
|
actual_plugin = 'calibre.gui2.store.wizards_tower_books_plugin:WizardsTowerBooksStore'
|
||||||
|
|
||||||
plugins += [StoreAmazonKindleStore, StoreAmazonDEKindleStore, StoreAmazonUKKindleStore,
|
drm_free_only = True
|
||||||
StoreBaenWebScriptionStore, StoreBNStore,
|
headquarters = 'UK'
|
||||||
StoreBeamEBooksDEStore, StoreBeWriteStore,
|
formats = ['EPUB', 'MOBI']
|
||||||
StoreDieselEbooksStore, StoreEbookscomStore, StoreEPubBuyDEStore,
|
|
||||||
StoreEHarlequinStore, StoreFeedbooksStore,
|
class StoreWoblinkStore(StoreBase):
|
||||||
StoreFoylesUKStore, StoreGutenbergStore, StoreKoboStore, StoreManyBooksStore,
|
name = 'Woblink'
|
||||||
StoreMobileReadStore, StoreOpenLibraryStore, StoreSmashwordsStore,
|
author = u'Tomasz Długosz'
|
||||||
StoreWaterstonesUKStore, StoreWeightlessBooksStore, StoreWizardsTowerBooksStore]
|
description = u'Czytanie zdarza się wszędzie!'
|
||||||
|
actual_plugin = 'calibre.gui2.store.woblink_plugin:WoblinkStore'
|
||||||
|
|
||||||
|
drm_free_only = False
|
||||||
|
headquarters = 'PL'
|
||||||
|
formats = ['EPUB']
|
||||||
|
|
||||||
|
plugins += [
|
||||||
|
StoreArchiveOrgStore,
|
||||||
|
StoreAmazonKindleStore,
|
||||||
|
StoreAmazonDEKindleStore,
|
||||||
|
StoreAmazonUKKindleStore,
|
||||||
|
StoreBaenWebScriptionStore,
|
||||||
|
StoreBNStore,
|
||||||
|
StoreBeamEBooksDEStore,
|
||||||
|
StoreBeWriteStore,
|
||||||
|
StoreDieselEbooksStore,
|
||||||
|
StoreEbookscomStore,
|
||||||
|
StoreEPubBuyDEStore,
|
||||||
|
StoreEHarlequinStore,
|
||||||
|
StoreFeedbooksStore,
|
||||||
|
StoreFoylesUKStore,
|
||||||
|
StoreGandalfStore,
|
||||||
|
StoreGoogleBooksStore,
|
||||||
|
StoreGutenbergStore,
|
||||||
|
StoreKoboStore,
|
||||||
|
StoreLegimiStore,
|
||||||
|
StoreManyBooksStore,
|
||||||
|
StoreMobileReadStore,
|
||||||
|
StoreNextoStore,
|
||||||
|
StoreOpenLibraryStore,
|
||||||
|
StoreOReillyStore,
|
||||||
|
StorePragmaticBookshelfStore,
|
||||||
|
StoreSmashwordsStore,
|
||||||
|
StoreVirtualoStore,
|
||||||
|
#StoreWaterstonesUKStore,
|
||||||
|
StoreWeightlessBooksStore,
|
||||||
|
StoreWizardsTowerBooksStore,
|
||||||
|
StoreWoblinkStore
|
||||||
|
]
|
||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
@ -216,9 +216,26 @@ def store_plugins():
|
|||||||
customization = config['plugin_customization']
|
customization = config['plugin_customization']
|
||||||
for plugin in _initialized_plugins:
|
for plugin in _initialized_plugins:
|
||||||
if isinstance(plugin, Store):
|
if isinstance(plugin, Store):
|
||||||
if not is_disabled(plugin):
|
plugin.site_customization = customization.get(plugin.name, '')
|
||||||
plugin.site_customization = customization.get(plugin.name, '')
|
yield plugin
|
||||||
yield plugin
|
|
||||||
|
def available_store_plugins():
|
||||||
|
for plugin in store_plugins():
|
||||||
|
if not is_disabled(plugin):
|
||||||
|
yield plugin
|
||||||
|
|
||||||
|
def stores():
|
||||||
|
stores = set([])
|
||||||
|
for plugin in store_plugins():
|
||||||
|
stores.add(plugin.name)
|
||||||
|
return stores
|
||||||
|
|
||||||
|
def available_stores():
|
||||||
|
stores = set([])
|
||||||
|
for plugin in available_store_plugins():
|
||||||
|
stores.add(plugin.name)
|
||||||
|
return stores
|
||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
# Metadata read/write {{{
|
# Metadata read/write {{{
|
||||||
|
@ -59,7 +59,7 @@ class ANDROID(USBMS):
|
|||||||
0x0489 : { 0xc001 : [0x0226], 0xc004 : [0x0226], },
|
0x0489 : { 0xc001 : [0x0226], 0xc004 : [0x0226], },
|
||||||
|
|
||||||
# Acer
|
# Acer
|
||||||
0x502 : { 0x3203 : [0x0100]},
|
0x502 : { 0x3203 : [0x0100, 0x224]},
|
||||||
|
|
||||||
# Dell
|
# Dell
|
||||||
0x413c : { 0xb007 : [0x0100, 0x0224, 0x0226]},
|
0x413c : { 0xb007 : [0x0100, 0x0224, 0x0226]},
|
||||||
|
@ -941,7 +941,7 @@ class ITUNES(DriverBase):
|
|||||||
# declared in use_plugboard_ext and a device name of ITUNES
|
# declared in use_plugboard_ext and a device name of ITUNES
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
self.log.info("ITUNES.set_plugboard()")
|
self.log.info("ITUNES.set_plugboard()")
|
||||||
#self.log.info(' using plugboard %s' % plugboards)
|
#self.log.info(' plugboard: %s' % plugboards)
|
||||||
self.plugboards = plugboards
|
self.plugboards = plugboards
|
||||||
self.plugboard_func = pb_func
|
self.plugboard_func = pb_func
|
||||||
|
|
||||||
@ -1052,7 +1052,6 @@ class ITUNES(DriverBase):
|
|||||||
'title': metadata[i].title,
|
'title': metadata[i].title,
|
||||||
'uuid': metadata[i].uuid }
|
'uuid': metadata[i].uuid }
|
||||||
|
|
||||||
|
|
||||||
# Report progress
|
# Report progress
|
||||||
if self.report_progress is not None:
|
if self.report_progress is not None:
|
||||||
self.report_progress((i+1)/file_count, _('%d of %d') % (i+1, file_count))
|
self.report_progress((i+1)/file_count, _('%d of %d') % (i+1, file_count))
|
||||||
@ -2744,7 +2743,7 @@ class ITUNES(DriverBase):
|
|||||||
# Update metadata from plugboard
|
# Update metadata from plugboard
|
||||||
# If self.plugboard is None (no transforms), original metadata is returned intact
|
# If self.plugboard is None (no transforms), original metadata is returned intact
|
||||||
metadata_x = self._xform_metadata_via_plugboard(metadata, this_book.format)
|
metadata_x = self._xform_metadata_via_plugboard(metadata, this_book.format)
|
||||||
|
self.log("metadata.title_sort: %s metadata_x.title_sort: %s" % (metadata.title_sort, metadata_x.title_sort))
|
||||||
if isosx:
|
if isosx:
|
||||||
if lb_added:
|
if lb_added:
|
||||||
lb_added.name.set(metadata_x.title)
|
lb_added.name.set(metadata_x.title)
|
||||||
@ -2754,8 +2753,7 @@ class ITUNES(DriverBase):
|
|||||||
lb_added.description.set("%s %s" % (self.description_prefix,strftime('%Y-%m-%d %H:%M:%S')))
|
lb_added.description.set("%s %s" % (self.description_prefix,strftime('%Y-%m-%d %H:%M:%S')))
|
||||||
lb_added.enabled.set(True)
|
lb_added.enabled.set(True)
|
||||||
lb_added.sort_artist.set(icu_title(metadata_x.author_sort))
|
lb_added.sort_artist.set(icu_title(metadata_x.author_sort))
|
||||||
lb_added.sort_name.set(metadata.title_sort)
|
lb_added.sort_name.set(metadata_x.title_sort)
|
||||||
|
|
||||||
|
|
||||||
if db_added:
|
if db_added:
|
||||||
db_added.name.set(metadata_x.title)
|
db_added.name.set(metadata_x.title)
|
||||||
@ -2765,7 +2763,7 @@ class ITUNES(DriverBase):
|
|||||||
db_added.description.set("%s %s" % (self.description_prefix,strftime('%Y-%m-%d %H:%M:%S')))
|
db_added.description.set("%s %s" % (self.description_prefix,strftime('%Y-%m-%d %H:%M:%S')))
|
||||||
db_added.enabled.set(True)
|
db_added.enabled.set(True)
|
||||||
db_added.sort_artist.set(icu_title(metadata_x.author_sort))
|
db_added.sort_artist.set(icu_title(metadata_x.author_sort))
|
||||||
db_added.sort_name.set(metadata.title_sort)
|
db_added.sort_name.set(metadata_x.title_sort)
|
||||||
|
|
||||||
if metadata_x.comments:
|
if metadata_x.comments:
|
||||||
if lb_added:
|
if lb_added:
|
||||||
@ -2785,6 +2783,7 @@ class ITUNES(DriverBase):
|
|||||||
|
|
||||||
# Set genre from series if available, else first alpha tag
|
# Set genre from series if available, else first alpha tag
|
||||||
# Otherwise iTunes grabs the first dc:subject from the opf metadata
|
# Otherwise iTunes grabs the first dc:subject from the opf metadata
|
||||||
|
# If title_sort applied in plugboard, that overrides using series/index as title_sort
|
||||||
if metadata_x.series and self.settings().extra_customization[self.USE_SERIES_AS_CATEGORY]:
|
if metadata_x.series and self.settings().extra_customization[self.USE_SERIES_AS_CATEGORY]:
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
self.log.info(" ITUNES._update_iTunes_metadata()")
|
self.log.info(" ITUNES._update_iTunes_metadata()")
|
||||||
@ -2796,7 +2795,9 @@ class ITUNES(DriverBase):
|
|||||||
fraction = index-integer
|
fraction = index-integer
|
||||||
series_index = '%04d%s' % (integer, str('%0.4f' % fraction).lstrip('0'))
|
series_index = '%04d%s' % (integer, str('%0.4f' % fraction).lstrip('0'))
|
||||||
if lb_added:
|
if lb_added:
|
||||||
lb_added.sort_name.set("%s %s" % (self.title_sorter(metadata_x.series), series_index))
|
# If no title_sort plugboard tweak, create sort_name from series/index
|
||||||
|
if metadata.title_sort == metadata_x.title_sort:
|
||||||
|
lb_added.sort_name.set("%s %s" % (self.title_sorter(metadata_x.series), series_index))
|
||||||
lb_added.episode_ID.set(metadata_x.series)
|
lb_added.episode_ID.set(metadata_x.series)
|
||||||
lb_added.episode_number.set(metadata_x.series_index)
|
lb_added.episode_number.set(metadata_x.series_index)
|
||||||
|
|
||||||
@ -2810,7 +2811,9 @@ class ITUNES(DriverBase):
|
|||||||
break
|
break
|
||||||
|
|
||||||
if db_added:
|
if db_added:
|
||||||
db_added.sort_name.set("%s %s" % (self.title_sorter(metadata_x.series), series_index))
|
# If no title_sort plugboard tweak, create sort_name from series/index
|
||||||
|
if metadata.title_sort == metadata_x.title_sort:
|
||||||
|
db_added.sort_name.set("%s %s" % (self.title_sorter(metadata_x.series), series_index))
|
||||||
db_added.episode_ID.set(metadata_x.series)
|
db_added.episode_ID.set(metadata_x.series)
|
||||||
db_added.episode_number.set(metadata_x.series_index)
|
db_added.episode_number.set(metadata_x.series_index)
|
||||||
|
|
||||||
@ -2845,7 +2848,7 @@ class ITUNES(DriverBase):
|
|||||||
lb_added.Description = ("%s %s" % (self.description_prefix,strftime('%Y-%m-%d %H:%M:%S')))
|
lb_added.Description = ("%s %s" % (self.description_prefix,strftime('%Y-%m-%d %H:%M:%S')))
|
||||||
lb_added.Enabled = True
|
lb_added.Enabled = True
|
||||||
lb_added.SortArtist = icu_title(metadata_x.author_sort)
|
lb_added.SortArtist = icu_title(metadata_x.author_sort)
|
||||||
lb_added.SortName = metadata.title_sort
|
lb_added.SortName = metadata_x.title_sort
|
||||||
|
|
||||||
if db_added:
|
if db_added:
|
||||||
db_added.Name = metadata_x.title
|
db_added.Name = metadata_x.title
|
||||||
@ -2855,7 +2858,7 @@ class ITUNES(DriverBase):
|
|||||||
db_added.Description = ("%s %s" % (self.description_prefix,strftime('%Y-%m-%d %H:%M:%S')))
|
db_added.Description = ("%s %s" % (self.description_prefix,strftime('%Y-%m-%d %H:%M:%S')))
|
||||||
db_added.Enabled = True
|
db_added.Enabled = True
|
||||||
db_added.SortArtist = icu_title(metadata_x.author_sort)
|
db_added.SortArtist = icu_title(metadata_x.author_sort)
|
||||||
db_added.SortName = metadata.title_sort
|
db_added.SortName = metadata_x.title_sort
|
||||||
|
|
||||||
if metadata_x.comments:
|
if metadata_x.comments:
|
||||||
if lb_added:
|
if lb_added:
|
||||||
@ -2888,7 +2891,9 @@ class ITUNES(DriverBase):
|
|||||||
fraction = index-integer
|
fraction = index-integer
|
||||||
series_index = '%04d%s' % (integer, str('%0.4f' % fraction).lstrip('0'))
|
series_index = '%04d%s' % (integer, str('%0.4f' % fraction).lstrip('0'))
|
||||||
if lb_added:
|
if lb_added:
|
||||||
lb_added.SortName = "%s %s" % (self.title_sorter(metadata_x.series), series_index)
|
# If no title_sort plugboard tweak, create sort_name from series/index
|
||||||
|
if metadata.title_sort == metadata_x.title_sort:
|
||||||
|
lb_added.SortName = "%s %s" % (self.title_sorter(metadata_x.series), series_index)
|
||||||
lb_added.EpisodeID = metadata_x.series
|
lb_added.EpisodeID = metadata_x.series
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -2914,7 +2919,9 @@ class ITUNES(DriverBase):
|
|||||||
break
|
break
|
||||||
|
|
||||||
if db_added:
|
if db_added:
|
||||||
db_added.SortName = "%s %s" % (self.title_sorter(metadata_x.series), series_index)
|
# If no title_sort plugboard tweak, create sort_name from series/index
|
||||||
|
if metadata.title_sort == metadata_x.title_sort:
|
||||||
|
db_added.SortName = "%s %s" % (self.title_sorter(metadata_x.series), series_index)
|
||||||
db_added.EpisodeID = metadata_x.series
|
db_added.EpisodeID = metadata_x.series
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -2975,6 +2982,9 @@ class ITUNES(DriverBase):
|
|||||||
newmi.publisher if book.publisher != newmi.publisher else ''))
|
newmi.publisher if book.publisher != newmi.publisher else ''))
|
||||||
self.log.info(" tags: %s %s" % (book.tags, ">>> %s" %
|
self.log.info(" tags: %s %s" % (book.tags, ">>> %s" %
|
||||||
newmi.tags if book.tags != newmi.tags else ''))
|
newmi.tags if book.tags != newmi.tags else ''))
|
||||||
|
else:
|
||||||
|
self.log(" matching plugboard not found")
|
||||||
|
|
||||||
else:
|
else:
|
||||||
newmi = book
|
newmi = book
|
||||||
return newmi
|
return newmi
|
||||||
|
@ -95,9 +95,8 @@ class POCKETBOOK360(EB600):
|
|||||||
|
|
||||||
FORMATS = ['epub', 'fb2', 'prc', 'mobi', 'pdf', 'djvu', 'rtf', 'chm', 'txt']
|
FORMATS = ['epub', 'fb2', 'prc', 'mobi', 'pdf', 'djvu', 'rtf', 'chm', 'txt']
|
||||||
|
|
||||||
VENDOR_NAME = 'PHILIPS'
|
VENDOR_NAME = ['PHILIPS', '__POCKET']
|
||||||
WINDOWS_MAIN_MEM = 'MASS_STORGE'
|
WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = ['MASS_STORGE', 'BOOK_USB_STORAGE']
|
||||||
WINDOWS_CARD_A_MEM = 'MASS_STORGE'
|
|
||||||
|
|
||||||
OSX_MAIN_MEM = 'Philips Mass Storge Media'
|
OSX_MAIN_MEM = 'Philips Mass Storge Media'
|
||||||
OSX_CARD_A_MEM = 'Philips Mass Storge Media'
|
OSX_CARD_A_MEM = 'Philips Mass Storge Media'
|
||||||
|
@ -8,10 +8,10 @@ from ctypes import cdll, POINTER, byref, pointer, Structure as _Structure, \
|
|||||||
c_ubyte, c_ushort, c_int, c_char, c_void_p, c_byte, c_uint
|
c_ubyte, c_ushort, c_int, c_char, c_void_p, c_byte, c_uint
|
||||||
from errno import EBUSY, ENOMEM
|
from errno import EBUSY, ENOMEM
|
||||||
|
|
||||||
from calibre import iswindows, isosx, isfreebsd, load_library
|
from calibre import iswindows, isosx, isbsd, load_library
|
||||||
|
|
||||||
_libusb_name = 'libusb'
|
_libusb_name = 'libusb'
|
||||||
PATH_MAX = 511 if iswindows else 1024 if (isosx or isfreebsd) else 4096
|
PATH_MAX = 511 if iswindows else 1024 if (isosx or isbsd) else 4096
|
||||||
if iswindows:
|
if iswindows:
|
||||||
class Structure(_Structure):
|
class Structure(_Structure):
|
||||||
_pack_ = 1
|
_pack_ = 1
|
||||||
|
@ -269,8 +269,8 @@ class NEXTBOOK(USBMS):
|
|||||||
|
|
||||||
EBOOK_DIR_MAIN = ''
|
EBOOK_DIR_MAIN = ''
|
||||||
|
|
||||||
VENDOR_NAME = 'NEXT2'
|
VENDOR_NAME = ['NEXT2', 'BK7005']
|
||||||
WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = '1.0.14'
|
WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = ['1.0.14', 'PLAYER']
|
||||||
SUPPORTS_SUB_DIRS = True
|
SUPPORTS_SUB_DIRS = True
|
||||||
THUMBNAIL_HEIGHT = 120
|
THUMBNAIL_HEIGHT = 120
|
||||||
|
|
||||||
|
@ -107,3 +107,13 @@ class NOOK_COLOR(NOOK):
|
|||||||
|
|
||||||
return filepath
|
return filepath
|
||||||
|
|
||||||
|
class NOOK_TSR(NOOK):
|
||||||
|
gui_name = _('Nook Simple')
|
||||||
|
description = _('Communicate with the Nook TSR eBook reader.')
|
||||||
|
|
||||||
|
PRODUCT_ID = [0x003]
|
||||||
|
BCD = [0x216]
|
||||||
|
|
||||||
|
EBOOK_DIR_MAIN = EBOOK_DIR_CARD_A = 'My Files/Books'
|
||||||
|
|
||||||
|
|
||||||
|
@ -926,8 +926,8 @@ class Device(DeviceConfig, DevicePlugin):
|
|||||||
if not isinstance(template, unicode):
|
if not isinstance(template, unicode):
|
||||||
template = template.decode('utf-8')
|
template = template.decode('utf-8')
|
||||||
app_id = str(getattr(mdata, 'application_id', ''))
|
app_id = str(getattr(mdata, 'application_id', ''))
|
||||||
# The db id will be in the created filename
|
id_ = mdata.get('id', fname)
|
||||||
extra_components = get_components(template, mdata, fname,
|
extra_components = get_components(template, mdata, id_,
|
||||||
timefmt=opts.send_timefmt, length=maxlen-len(app_id)-1)
|
timefmt=opts.send_timefmt, length=maxlen-len(app_id)-1)
|
||||||
if not extra_components:
|
if not extra_components:
|
||||||
extra_components.append(sanitize(self.filename_callback(fname,
|
extra_components.append(sanitize(self.filename_callback(fname,
|
||||||
|
@ -413,6 +413,13 @@ class EPUBOutput(OutputFormatPlugin):
|
|||||||
rule.style.removeProperty('margin-left')
|
rule.style.removeProperty('margin-left')
|
||||||
# padding-left breaks rendering in webkit and gecko
|
# padding-left breaks rendering in webkit and gecko
|
||||||
rule.style.removeProperty('padding-left')
|
rule.style.removeProperty('padding-left')
|
||||||
|
# Change whitespace:pre to pre-line to accommodate readers that
|
||||||
|
# cannot scroll horizontally
|
||||||
|
for rule in stylesheet.data.cssRules.rulesOfType(CSSRule.STYLE_RULE):
|
||||||
|
style = rule.style
|
||||||
|
ws = style.getPropertyValue('white-space')
|
||||||
|
if ws == 'pre':
|
||||||
|
style.setProperty('white-space', 'pre-wrap')
|
||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ from itertools import izip
|
|||||||
from calibre.customize.conversion import InputFormatPlugin
|
from calibre.customize.conversion import InputFormatPlugin
|
||||||
from calibre.ebooks.chardet import xml_to_unicode
|
from calibre.ebooks.chardet import xml_to_unicode
|
||||||
from calibre.customize.conversion import OptionRecommendation
|
from calibre.customize.conversion import OptionRecommendation
|
||||||
from calibre.constants import islinux, isfreebsd, iswindows
|
from calibre.constants import islinux, isbsd, iswindows
|
||||||
from calibre import unicode_path, as_unicode
|
from calibre import unicode_path, as_unicode
|
||||||
from calibre.utils.localization import get_lang
|
from calibre.utils.localization import get_lang
|
||||||
from calibre.utils.filenames import ascii_filename
|
from calibre.utils.filenames import ascii_filename
|
||||||
@ -302,7 +302,7 @@ class HTMLInput(InputFormatPlugin):
|
|||||||
if getattr(self, '_is_case_sensitive', None) is not None:
|
if getattr(self, '_is_case_sensitive', None) is not None:
|
||||||
return self._is_case_sensitive
|
return self._is_case_sensitive
|
||||||
if not path or not os.path.exists(path):
|
if not path or not os.path.exists(path):
|
||||||
return islinux or isfreebsd
|
return islinux or isbsd
|
||||||
self._is_case_sensitive = not (os.path.exists(path.lower()) \
|
self._is_case_sensitive = not (os.path.exists(path.lower()) \
|
||||||
and os.path.exists(path.upper()))
|
and os.path.exists(path.upper()))
|
||||||
return self._is_case_sensitive
|
return self._is_case_sensitive
|
||||||
|
@ -10,7 +10,7 @@ import os, sys, re
|
|||||||
from urllib import unquote, quote
|
from urllib import unquote, quote
|
||||||
from urlparse import urlparse
|
from urlparse import urlparse
|
||||||
|
|
||||||
from calibre import relpath, guess_type
|
from calibre import relpath, guess_type, remove_bracketed_text
|
||||||
|
|
||||||
from calibre.utils.config import tweaks
|
from calibre.utils.config import tweaks
|
||||||
|
|
||||||
@ -27,20 +27,37 @@ def authors_to_string(authors):
|
|||||||
else:
|
else:
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
_bracket_pat = re.compile(r'[\[({].*?[})\]]')
|
def author_to_author_sort(author, method=None):
|
||||||
def author_to_author_sort(author):
|
|
||||||
if not author:
|
if not author:
|
||||||
return ''
|
return u''
|
||||||
method = tweaks['author_sort_copy_method']
|
sauthor = remove_bracketed_text(author).strip()
|
||||||
if method == 'copy' or (method == 'comma' and ',' in author):
|
tokens = sauthor.split()
|
||||||
|
if len(tokens) < 2:
|
||||||
return author
|
return author
|
||||||
author = _bracket_pat.sub('', author).strip()
|
if method is None:
|
||||||
tokens = author.split()
|
method = tweaks['author_sort_copy_method']
|
||||||
if tokens and tokens[-1] not in ('Inc.', 'Inc'):
|
if method == u'copy':
|
||||||
tokens = tokens[-1:] + tokens[:-1]
|
return author
|
||||||
if len(tokens) > 1 and method != 'nocomma':
|
suffixes = set([x.lower() for x in tweaks['author_name_suffixes']])
|
||||||
tokens[0] += ','
|
suffixes |= set([x+u'.' for x in suffixes])
|
||||||
return ' '.join(tokens)
|
|
||||||
|
last = tokens[-1].lower()
|
||||||
|
suffix = None
|
||||||
|
if last in suffixes:
|
||||||
|
suffix = tokens[-1]
|
||||||
|
tokens = tokens[:-1]
|
||||||
|
|
||||||
|
if method == u'comma' and u',' in u''.join(tokens):
|
||||||
|
return author
|
||||||
|
|
||||||
|
atokens = tokens[-1:] + tokens[:-1]
|
||||||
|
if suffix:
|
||||||
|
atokens.append(suffix)
|
||||||
|
|
||||||
|
if method != u'nocomma' and len(atokens) > 1:
|
||||||
|
atokens[0] += u','
|
||||||
|
|
||||||
|
return u' '.join(atokens)
|
||||||
|
|
||||||
def authors_to_sort_string(authors):
|
def authors_to_sort_string(authors):
|
||||||
return ' & '.join(map(author_to_author_sort, authors))
|
return ' & '.join(map(author_to_author_sort, authors))
|
||||||
|
@ -29,7 +29,7 @@ class Worker(Thread): # Get details {{{
|
|||||||
Get book details from amazons book page in a separate thread
|
Get book details from amazons book page in a separate thread
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def __init__(self, url, result_queue, browser, log, relevance, plugin, timeout=20):
|
def __init__(self, url, result_queue, browser, log, relevance, domain, plugin, timeout=20):
|
||||||
Thread.__init__(self)
|
Thread.__init__(self)
|
||||||
self.daemon = True
|
self.daemon = True
|
||||||
self.url, self.result_queue = url, result_queue
|
self.url, self.result_queue = url, result_queue
|
||||||
@ -37,7 +37,7 @@ class Worker(Thread): # Get details {{{
|
|||||||
self.relevance, self.plugin = relevance, plugin
|
self.relevance, self.plugin = relevance, plugin
|
||||||
self.browser = browser.clone_browser()
|
self.browser = browser.clone_browser()
|
||||||
self.cover_url = self.amazon_id = self.isbn = None
|
self.cover_url = self.amazon_id = self.isbn = None
|
||||||
self.domain = self.plugin.domain
|
self.domain = domain
|
||||||
|
|
||||||
months = {
|
months = {
|
||||||
'de': {
|
'de': {
|
||||||
@ -199,7 +199,8 @@ class Worker(Thread): # Get details {{{
|
|||||||
return
|
return
|
||||||
|
|
||||||
mi = Metadata(title, authors)
|
mi = Metadata(title, authors)
|
||||||
mi.set_identifier('amazon', asin)
|
idtype = 'amazon' if self.domain == 'com' else 'amazon_'+self.domain
|
||||||
|
mi.set_identifier(idtype, asin)
|
||||||
self.amazon_id = asin
|
self.amazon_id = asin
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -404,12 +405,30 @@ class Amazon(Source):
|
|||||||
'country\'s Amazon website.'), choices=AMAZON_DOMAINS),
|
'country\'s Amazon website.'), choices=AMAZON_DOMAINS),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_domain_and_asin(self, identifiers):
|
||||||
|
for key, val in identifiers.iteritems():
|
||||||
|
key = key.lower()
|
||||||
|
if key in ('amazon', 'asin'):
|
||||||
|
return 'com', val
|
||||||
|
if key.startswith('amazon_'):
|
||||||
|
domain = key.split('_')[-1]
|
||||||
|
if domain and domain in self.AMAZON_DOMAINS:
|
||||||
|
return domain, val
|
||||||
|
return None, None
|
||||||
|
|
||||||
def get_book_url(self, identifiers): # {{{
|
def get_book_url(self, identifiers): # {{{
|
||||||
asin = identifiers.get('amazon', None)
|
domain, asin = self.get_domain_and_asin(identifiers)
|
||||||
if asin is None:
|
if domain and asin:
|
||||||
asin = identifiers.get('asin', None)
|
url = None
|
||||||
if asin:
|
if domain == 'com':
|
||||||
return ('amazon', asin, 'http://amzn.com/%s'%asin)
|
url = 'http://amzn.com/'+asin
|
||||||
|
elif domain == 'uk':
|
||||||
|
url = 'http://www.amazon.co.uk/dp/'+asin
|
||||||
|
else:
|
||||||
|
url = 'http://www.amazon.%s/dp/%s'%(domain, asin)
|
||||||
|
if url:
|
||||||
|
idtype = 'amazon' if self.domain == 'com' else 'amazon_'+self.domain
|
||||||
|
return (idtype, asin, url)
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -420,8 +439,14 @@ class Amazon(Source):
|
|||||||
|
|
||||||
return domain
|
return domain
|
||||||
|
|
||||||
def create_query(self, log, title=None, authors=None, identifiers={}): # {{{
|
def create_query(self, log, title=None, authors=None, identifiers={}, # {{{
|
||||||
domain = self.domain
|
domain=None):
|
||||||
|
if domain is None:
|
||||||
|
domain = self.domain
|
||||||
|
|
||||||
|
idomain, asin = self.get_domain_and_asin(identifiers)
|
||||||
|
if idomain is not None:
|
||||||
|
domain = idomain
|
||||||
|
|
||||||
# See the amazon detailed search page to get all options
|
# See the amazon detailed search page to get all options
|
||||||
q = { 'search-alias' : 'aps',
|
q = { 'search-alias' : 'aps',
|
||||||
@ -433,7 +458,6 @@ class Amazon(Source):
|
|||||||
else:
|
else:
|
||||||
q['sort'] = 'relevancerank'
|
q['sort'] = 'relevancerank'
|
||||||
|
|
||||||
asin = identifiers.get('amazon', None)
|
|
||||||
isbn = check_isbn(identifiers.get('isbn', None))
|
isbn = check_isbn(identifiers.get('isbn', None))
|
||||||
|
|
||||||
if asin is not None:
|
if asin is not None:
|
||||||
@ -456,23 +480,22 @@ class Amazon(Source):
|
|||||||
if not ('field-keywords' in q or 'field-isbn' in q or
|
if not ('field-keywords' in q or 'field-isbn' in q or
|
||||||
('field-title' in q)):
|
('field-title' in q)):
|
||||||
# Insufficient metadata to make an identify query
|
# Insufficient metadata to make an identify query
|
||||||
return None
|
return None, None
|
||||||
|
|
||||||
latin1q = dict([(x.encode('latin1', 'ignore'), y.encode('latin1',
|
latin1q = dict([(x.encode('latin1', 'ignore'), y.encode('latin1',
|
||||||
'ignore')) for x, y in
|
'ignore')) for x, y in
|
||||||
q.iteritems()])
|
q.iteritems()])
|
||||||
|
udomain = domain
|
||||||
if domain == 'uk':
|
if domain == 'uk':
|
||||||
domain = 'co.uk'
|
udomain = 'co.uk'
|
||||||
url = 'http://www.amazon.%s/s/?'%domain + urlencode(latin1q)
|
url = 'http://www.amazon.%s/s/?'%udomain + urlencode(latin1q)
|
||||||
return url
|
return url, domain
|
||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
def get_cached_cover_url(self, identifiers): # {{{
|
def get_cached_cover_url(self, identifiers): # {{{
|
||||||
url = None
|
url = None
|
||||||
asin = identifiers.get('amazon', None)
|
domain, asin = self.get_domain_and_asin(identifiers)
|
||||||
if asin is None:
|
|
||||||
asin = identifiers.get('asin', None)
|
|
||||||
if asin is None:
|
if asin is None:
|
||||||
isbn = identifiers.get('isbn', None)
|
isbn = identifiers.get('isbn', None)
|
||||||
if isbn is not None:
|
if isbn is not None:
|
||||||
@ -489,7 +512,7 @@ class Amazon(Source):
|
|||||||
Note this method will retry without identifiers automatically if no
|
Note this method will retry without identifiers automatically if no
|
||||||
match is found with identifiers.
|
match is found with identifiers.
|
||||||
'''
|
'''
|
||||||
query = self.create_query(log, title=title, authors=authors,
|
query, domain = self.create_query(log, title=title, authors=authors,
|
||||||
identifiers=identifiers)
|
identifiers=identifiers)
|
||||||
if query is None:
|
if query is None:
|
||||||
log.error('Insufficient metadata to construct query')
|
log.error('Insufficient metadata to construct query')
|
||||||
@ -549,7 +572,8 @@ class Amazon(Source):
|
|||||||
r'//div[@id="Results"]/descendant::td[starts-with(@id, "search:Td:")]'):
|
r'//div[@id="Results"]/descendant::td[starts-with(@id, "search:Td:")]'):
|
||||||
for a in td.xpath(r'descendant::td[@class="dataColumn"]/descendant::a[@href]/span[@class="srTitle"]/..'):
|
for a in td.xpath(r'descendant::td[@class="dataColumn"]/descendant::a[@href]/span[@class="srTitle"]/..'):
|
||||||
title = tostring(a, method='text', encoding=unicode).lower()
|
title = tostring(a, method='text', encoding=unicode).lower()
|
||||||
if 'bulk pack' not in title:
|
if ('bulk pack' not in title and '[audiobook]' not in
|
||||||
|
title and '[audio cd]' not in title):
|
||||||
matches.append(a.get('href'))
|
matches.append(a.get('href'))
|
||||||
break
|
break
|
||||||
|
|
||||||
@ -570,7 +594,7 @@ class Amazon(Source):
|
|||||||
log.error('No matches found with query: %r'%query)
|
log.error('No matches found with query: %r'%query)
|
||||||
return
|
return
|
||||||
|
|
||||||
workers = [Worker(url, result_queue, br, log, i, self) for i, url in
|
workers = [Worker(url, result_queue, br, log, i, domain, self) for i, url in
|
||||||
enumerate(matches)]
|
enumerate(matches)]
|
||||||
|
|
||||||
for w in workers:
|
for w in workers:
|
||||||
|
@ -313,7 +313,7 @@ class Source(Plugin):
|
|||||||
title_patterns = [(re.compile(pat, re.IGNORECASE), repl) for pat, repl in
|
title_patterns = [(re.compile(pat, re.IGNORECASE), repl) for pat, repl in
|
||||||
[
|
[
|
||||||
# Remove things like: (2010) (Omnibus) etc.
|
# Remove things like: (2010) (Omnibus) etc.
|
||||||
(r'(?i)[({\[](\d{4}|omnibus|anthology|hardcover|paperback|turtleback|mass\s*market|edition|ed\.)[\])}]', ''),
|
(r'(?i)[({\[](\d{4}|omnibus|anthology|hardcover|audiobook|audio\scd|paperback|turtleback|mass\s*market|edition|ed\.)[\])}]', ''),
|
||||||
# Remove any strings that contain the substring edition inside
|
# Remove any strings that contain the substring edition inside
|
||||||
# parentheses
|
# parentheses
|
||||||
(r'(?i)[({\[].*?(edition|ed.).*?[\]})]', ''),
|
(r'(?i)[({\[].*?(edition|ed.).*?[\]})]', ''),
|
||||||
|
@ -211,7 +211,10 @@ class Douban(Source):
|
|||||||
'q': q,
|
'q': q,
|
||||||
})
|
})
|
||||||
if self.DOUBAN_API_KEY and self.DOUBAN_API_KEY != '':
|
if self.DOUBAN_API_KEY and self.DOUBAN_API_KEY != '':
|
||||||
url = url + "?apikey=" + self.DOUBAN_API_KEY
|
if t == "isbn" or t == "subject":
|
||||||
|
url = url + "?apikey=" + self.DOUBAN_API_KEY
|
||||||
|
else:
|
||||||
|
url = url + "&apikey=" + self.DOUBAN_API_KEY
|
||||||
return url
|
return url
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
|
@ -297,9 +297,11 @@ class MobiMLizer(object):
|
|||||||
if id_:
|
if id_:
|
||||||
# Keep anchors so people can use display:none
|
# Keep anchors so people can use display:none
|
||||||
# to generate hidden TOCs
|
# to generate hidden TOCs
|
||||||
|
tail = elem.tail
|
||||||
elem.clear()
|
elem.clear()
|
||||||
elem.text = None
|
elem.text = None
|
||||||
elem.set('id', id_)
|
elem.set('id', id_)
|
||||||
|
elem.tail = tail
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
tag = barename(elem.tag)
|
tag = barename(elem.tag)
|
||||||
@ -309,7 +311,8 @@ class MobiMLizer(object):
|
|||||||
istates.append(istate)
|
istates.append(istate)
|
||||||
left = 0
|
left = 0
|
||||||
display = style['display']
|
display = style['display']
|
||||||
isblock = not display.startswith('inline')
|
isblock = (not display.startswith('inline') and style['display'] !=
|
||||||
|
'none')
|
||||||
isblock = isblock and style['float'] == 'none'
|
isblock = isblock and style['float'] == 'none'
|
||||||
isblock = isblock and tag != 'br'
|
isblock = isblock and tag != 'br'
|
||||||
if isblock:
|
if isblock:
|
||||||
|
@ -690,6 +690,14 @@ class MobiReader(object):
|
|||||||
lm = unit_convert('2em', 12, 500, 166)
|
lm = unit_convert('2em', 12, 500, 166)
|
||||||
lm = self.left_margins.get(tag, lm)
|
lm = self.left_margins.get(tag, lm)
|
||||||
ti = self.text_indents.get(tag, ti)
|
ti = self.text_indents.get(tag, ti)
|
||||||
|
try:
|
||||||
|
lm = float(lm)
|
||||||
|
except:
|
||||||
|
lm = 0.0
|
||||||
|
try:
|
||||||
|
ti = float(ti)
|
||||||
|
except:
|
||||||
|
ti = 0.0
|
||||||
return lm + ti
|
return lm + ti
|
||||||
|
|
||||||
parent = tag
|
parent = tag
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
#include <math.h>
|
#include <math.h>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <wand/MagickWand.h>
|
#include <wand/MagickWand.h>
|
||||||
|
#include <zlib.h>
|
||||||
|
|
||||||
#include "images.h"
|
#include "images.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
@ -13,7 +13,7 @@ from functools import partial
|
|||||||
|
|
||||||
from calibre.ebooks import ConversionError, DRMError
|
from calibre.ebooks import ConversionError, DRMError
|
||||||
from calibre.ptempfile import PersistentTemporaryFile
|
from calibre.ptempfile import PersistentTemporaryFile
|
||||||
from calibre.constants import isosx, iswindows, islinux, isfreebsd
|
from calibre.constants import isosx, iswindows, islinux, isbsd
|
||||||
from calibre import CurrentDir
|
from calibre import CurrentDir
|
||||||
|
|
||||||
PDFTOHTML = 'pdftohtml'
|
PDFTOHTML = 'pdftohtml'
|
||||||
@ -23,7 +23,7 @@ if isosx and hasattr(sys, 'frameworks_dir'):
|
|||||||
if iswindows and hasattr(sys, 'frozen'):
|
if iswindows and hasattr(sys, 'frozen'):
|
||||||
PDFTOHTML = os.path.join(os.path.dirname(sys.executable), 'pdftohtml.exe')
|
PDFTOHTML = os.path.join(os.path.dirname(sys.executable), 'pdftohtml.exe')
|
||||||
popen = partial(subprocess.Popen, creationflags=0x08) # CREATE_NO_WINDOW=0x08 so that no ugly console is popped up
|
popen = partial(subprocess.Popen, creationflags=0x08) # CREATE_NO_WINDOW=0x08 so that no ugly console is popped up
|
||||||
if (islinux or isfreebsd) and getattr(sys, 'frozen', False):
|
if (islinux or isbsd) and getattr(sys, 'frozen', False):
|
||||||
PDFTOHTML = os.path.join(sys.executables_location, 'bin', 'pdftohtml')
|
PDFTOHTML = os.path.join(sys.executables_location, 'bin', 'pdftohtml')
|
||||||
|
|
||||||
def pdftohtml(output_dir, pdf_path, no_images):
|
def pdftohtml(output_dir, pdf_path, no_images):
|
||||||
@ -43,7 +43,7 @@ def pdftohtml(output_dir, pdf_path, no_images):
|
|||||||
# This is neccessary as pdftohtml doesn't always (linux) respect absolute paths
|
# This is neccessary as pdftohtml doesn't always (linux) respect absolute paths
|
||||||
pdf_path = os.path.abspath(pdf_path)
|
pdf_path = os.path.abspath(pdf_path)
|
||||||
cmd = [PDFTOHTML, '-enc', 'UTF-8', '-noframes', '-p', '-nomerge', '-nodrm', '-q', pdf_path, os.path.basename(index)]
|
cmd = [PDFTOHTML, '-enc', 'UTF-8', '-noframes', '-p', '-nomerge', '-nodrm', '-q', pdf_path, os.path.basename(index)]
|
||||||
if isfreebsd:
|
if isbsd:
|
||||||
cmd.remove('-nodrm')
|
cmd.remove('-nodrm')
|
||||||
if no_images:
|
if no_images:
|
||||||
cmd.append('-i')
|
cmd.append('-i')
|
||||||
|
@ -2,12 +2,8 @@
|
|||||||
# jisyo.py
|
# jisyo.py
|
||||||
#
|
#
|
||||||
# Copyright 2011 Hiroshi Miura <miurahr@linux.com>
|
# Copyright 2011 Hiroshi Miura <miurahr@linux.com>
|
||||||
from cPickle import load
|
import cPickle, marshal
|
||||||
import anydbm,marshal
|
|
||||||
from zlib import decompress
|
from zlib import decompress
|
||||||
import os
|
|
||||||
|
|
||||||
import calibre.utils.resources as resources
|
|
||||||
|
|
||||||
class jisyo (object):
|
class jisyo (object):
|
||||||
kanwadict = None
|
kanwadict = None
|
||||||
@ -25,16 +21,14 @@ class jisyo (object):
|
|||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
if self.kanwadict is None:
|
if self.kanwadict is None:
|
||||||
dictpath = resources.get_path(os.path.join('localization','pykakasi','kanwadict2.db'))
|
self.kanwadict = cPickle.loads(
|
||||||
self.kanwadict = anydbm.open(dictpath,'r')
|
P('localization/pykakasi/kanwadict2.pickle', data=True))
|
||||||
if self.itaijidict is None:
|
if self.itaijidict is None:
|
||||||
itaijipath = resources.get_path(os.path.join('localization','pykakasi','itaijidict2.pickle'))
|
self.itaijidict = cPickle.loads(
|
||||||
itaiji_pkl = open(itaijipath, 'rb')
|
P('localization/pykakasi/itaijidict2.pickle', data=True))
|
||||||
self.itaijidict = load(itaiji_pkl)
|
|
||||||
if self.kanadict is None:
|
if self.kanadict is None:
|
||||||
kanadictpath = resources.get_path(os.path.join('localization','pykakasi','kanadict2.pickle'))
|
self.kanadict = cPickle.loads(
|
||||||
kanadict_pkl = open(kanadictpath, 'rb')
|
P('localization/pykakasi/kanadict2.pickle', data=True))
|
||||||
self.kanadict = load(kanadict_pkl)
|
|
||||||
|
|
||||||
def load_jisyo(self, char):
|
def load_jisyo(self, char):
|
||||||
try:#python2
|
try:#python2
|
||||||
|
@ -12,7 +12,7 @@ from PyQt4.Qt import (QVariant, QFileInfo, QObject, SIGNAL, QBuffer, Qt,
|
|||||||
|
|
||||||
ORG_NAME = 'KovidsBrain'
|
ORG_NAME = 'KovidsBrain'
|
||||||
APP_UID = 'libprs500'
|
APP_UID = 'libprs500'
|
||||||
from calibre.constants import islinux, iswindows, isfreebsd, isfrozen, isosx
|
from calibre.constants import islinux, iswindows, isbsd, isfrozen, isosx
|
||||||
from calibre.utils.config import Config, ConfigProxy, dynamic, JSONConfig
|
from calibre.utils.config import Config, ConfigProxy, dynamic, JSONConfig
|
||||||
from calibre.utils.localization import set_qt_translator
|
from calibre.utils.localization import set_qt_translator
|
||||||
from calibre.ebooks.metadata import MetaInformation
|
from calibre.ebooks.metadata import MetaInformation
|
||||||
@ -628,13 +628,14 @@ class Application(QApplication):
|
|||||||
st = self.style()
|
st = self.style()
|
||||||
if st is not None:
|
if st is not None:
|
||||||
st = unicode(st.objectName()).lower()
|
st = unicode(st.objectName()).lower()
|
||||||
if (islinux or isfreebsd) and st in ('windows', 'motif', 'cde'):
|
if (islinux or isbsd) and st in ('windows', 'motif', 'cde'):
|
||||||
from PyQt4.Qt import QStyleFactory
|
from PyQt4.Qt import QStyleFactory
|
||||||
styles = set(map(unicode, QStyleFactory.keys()))
|
styles = set(map(unicode, QStyleFactory.keys()))
|
||||||
if 'Cleanlooks' in styles:
|
if 'Plastique' in styles and os.environ.get('KDE_FULL_SESSION',
|
||||||
self.setStyle('Cleanlooks')
|
False):
|
||||||
else:
|
|
||||||
self.setStyle('Plastique')
|
self.setStyle('Plastique')
|
||||||
|
elif 'Cleanlooks' in styles:
|
||||||
|
self.setStyle('Cleanlooks')
|
||||||
|
|
||||||
def _send_file_open_events(self):
|
def _send_file_open_events(self):
|
||||||
with self._file_open_lock:
|
with self._file_open_lock:
|
||||||
@ -696,7 +697,7 @@ def open_local_file(path):
|
|||||||
|
|
||||||
def is_ok_to_use_qt():
|
def is_ok_to_use_qt():
|
||||||
global gui_thread, _store_app
|
global gui_thread, _store_app
|
||||||
if (islinux or isfreebsd) and ':' not in os.environ.get('DISPLAY', ''):
|
if (islinux or isbsd) and ':' not in os.environ.get('DISPLAY', ''):
|
||||||
return False
|
return False
|
||||||
if _store_app is None and QApplication.instance() is None:
|
if _store_app is None and QApplication.instance() is None:
|
||||||
_store_app = QApplication([])
|
_store_app = QApplication([])
|
||||||
|
@ -16,7 +16,7 @@ from calibre.gui2 import error_dialog, Dispatcher, warning_dialog
|
|||||||
from calibre.gui2.dialogs.progress import ProgressDialog
|
from calibre.gui2.dialogs.progress import ProgressDialog
|
||||||
from calibre.utils.config import prefs, tweaks
|
from calibre.utils.config import prefs, tweaks
|
||||||
|
|
||||||
class Worker(Thread):
|
class Worker(Thread): # {{{
|
||||||
|
|
||||||
def __init__(self, ids, db, loc, progress, done, delete_after):
|
def __init__(self, ids, db, loc, progress, done, delete_after):
|
||||||
Thread.__init__(self)
|
Thread.__init__(self)
|
||||||
@ -75,7 +75,7 @@ class Worker(Thread):
|
|||||||
if co is not None:
|
if co is not None:
|
||||||
newdb.set_conversion_options(x, 'PIPE', co)
|
newdb.set_conversion_options(x, 'PIPE', co)
|
||||||
self.processed.add(x)
|
self.processed.add(x)
|
||||||
|
# }}}
|
||||||
|
|
||||||
class CopyToLibraryAction(InterfaceAction):
|
class CopyToLibraryAction(InterfaceAction):
|
||||||
|
|
||||||
|
@ -9,11 +9,12 @@ from functools import partial
|
|||||||
|
|
||||||
from PyQt4.Qt import QMenu, QObject, QTimer
|
from PyQt4.Qt import QMenu, QObject, QTimer
|
||||||
|
|
||||||
from calibre.gui2 import error_dialog
|
from calibre.gui2 import error_dialog, question_dialog
|
||||||
from calibre.gui2.dialogs.delete_matching_from_device import DeleteMatchingFromDeviceDialog
|
from calibre.gui2.dialogs.delete_matching_from_device import DeleteMatchingFromDeviceDialog
|
||||||
from calibre.gui2.dialogs.confirm_delete import confirm
|
from calibre.gui2.dialogs.confirm_delete import confirm
|
||||||
from calibre.gui2.dialogs.confirm_delete_location import confirm_location
|
from calibre.gui2.dialogs.confirm_delete_location import confirm_location
|
||||||
from calibre.gui2.actions import InterfaceAction
|
from calibre.gui2.actions import InterfaceAction
|
||||||
|
from calibre.utils.recycle_bin import can_recycle
|
||||||
|
|
||||||
single_shot = partial(QTimer.singleShot, 10)
|
single_shot = partial(QTimer.singleShot, 10)
|
||||||
|
|
||||||
@ -24,6 +25,15 @@ class MultiDeleter(QObject):
|
|||||||
QObject.__init__(self, gui)
|
QObject.__init__(self, gui)
|
||||||
self.model = gui.library_view.model()
|
self.model = gui.library_view.model()
|
||||||
self.ids = ids
|
self.ids = ids
|
||||||
|
self.permanent = False
|
||||||
|
if can_recycle and len(ids) > 100:
|
||||||
|
if question_dialog(gui, _('Are you sure?'), '<p>'+
|
||||||
|
_('You are trying to delete %d books. '
|
||||||
|
'Sending so many files to the Recycle'
|
||||||
|
' Bin <b>can be slow</b>. Should calibre skip the'
|
||||||
|
' Recycle Bin? If you click Yes the files'
|
||||||
|
' will be <b>permanently deleted</b>.')%len(ids)):
|
||||||
|
self.permanent = True
|
||||||
self.gui = gui
|
self.gui = gui
|
||||||
self.failures = []
|
self.failures = []
|
||||||
self.deleted_ids = []
|
self.deleted_ids = []
|
||||||
@ -44,7 +54,8 @@ class MultiDeleter(QObject):
|
|||||||
title_ = self.model.db.title(id_, index_is_id=True)
|
title_ = self.model.db.title(id_, index_is_id=True)
|
||||||
if title_:
|
if title_:
|
||||||
title = title_
|
title = title_
|
||||||
self.model.db.delete_book(id_, notify=False, commit=False)
|
self.model.db.delete_book(id_, notify=False, commit=False,
|
||||||
|
permanent=self.permanent)
|
||||||
self.deleted_ids.append(id_)
|
self.deleted_ids.append(id_)
|
||||||
except:
|
except:
|
||||||
import traceback
|
import traceback
|
||||||
|
@ -19,8 +19,9 @@ class PreferencesAction(InterfaceAction):
|
|||||||
|
|
||||||
def genesis(self):
|
def genesis(self):
|
||||||
pm = QMenu()
|
pm = QMenu()
|
||||||
acname = _('Change calibre behavior') if isosx else _('Preferences')
|
pm.addAction(QIcon(I('config.png')), _('Preferences'), self.do_config)
|
||||||
pm.addAction(QIcon(I('config.png')), acname, self.do_config)
|
if isosx:
|
||||||
|
pm.addAction(QIcon(I('config.png')), _('Change calibre behavior'), self.do_config)
|
||||||
pm.addAction(QIcon(I('wizard.png')), _('Run welcome wizard'),
|
pm.addAction(QIcon(I('wizard.png')), _('Run welcome wizard'),
|
||||||
self.gui.run_wizard)
|
self.gui.run_wizard)
|
||||||
if not DEBUG:
|
if not DEBUG:
|
||||||
|
@ -34,6 +34,8 @@ class StoreAction(InterfaceAction):
|
|||||||
self.store_list_menu = self.store_menu.addMenu(_('Stores'))
|
self.store_list_menu = self.store_menu.addMenu(_('Stores'))
|
||||||
for n, p in sorted(self.gui.istores.items(), key=lambda x: x[0].lower()):
|
for n, p in sorted(self.gui.istores.items(), key=lambda x: x[0].lower()):
|
||||||
self.store_list_menu.addAction(n, partial(self.open_store, p))
|
self.store_list_menu.addAction(n, partial(self.open_store, p))
|
||||||
|
self.store_menu.addSeparator()
|
||||||
|
self.store_menu.addAction(_('Choose stores'), self.choose)
|
||||||
self.qaction.setMenu(self.store_menu)
|
self.qaction.setMenu(self.store_menu)
|
||||||
|
|
||||||
def do_search(self):
|
def do_search(self):
|
||||||
@ -42,7 +44,7 @@ class StoreAction(InterfaceAction):
|
|||||||
def search(self, query=''):
|
def search(self, query=''):
|
||||||
self.show_disclaimer()
|
self.show_disclaimer()
|
||||||
from calibre.gui2.store.search.search import SearchDialog
|
from calibre.gui2.store.search.search import SearchDialog
|
||||||
sd = SearchDialog(self.gui.istores, self.gui, query)
|
sd = SearchDialog(self.gui, self.gui, query)
|
||||||
sd.exec_()
|
sd.exec_()
|
||||||
|
|
||||||
def _get_selected_row(self):
|
def _get_selected_row(self):
|
||||||
@ -52,16 +54,23 @@ class StoreAction(InterfaceAction):
|
|||||||
return rows[0].row()
|
return rows[0].row()
|
||||||
|
|
||||||
def _get_author(self, row):
|
def _get_author(self, row):
|
||||||
author = ''
|
authors = []
|
||||||
|
|
||||||
if self.gui.current_view() is self.gui.library_view:
|
if self.gui.current_view() is self.gui.library_view:
|
||||||
author = self.gui.library_view.model().authors(row)
|
a = self.gui.library_view.model().authors(row)
|
||||||
if author:
|
authors = a.split(',')
|
||||||
author = author.replace('|', ' ')
|
|
||||||
else:
|
else:
|
||||||
mi = self.gui.current_view().model().get_book_display_info(row)
|
mi = self.gui.current_view().model().get_book_display_info(row)
|
||||||
author = ' & '.join(mi.authors)
|
authors = mi.authors
|
||||||
|
|
||||||
return author
|
corrected_authors = []
|
||||||
|
for x in authors:
|
||||||
|
a = x.split('|')
|
||||||
|
a.reverse()
|
||||||
|
a = ' '.join(a)
|
||||||
|
corrected_authors.append(a)
|
||||||
|
|
||||||
|
return ' & '.join(corrected_authors).strip()
|
||||||
|
|
||||||
def search_author(self):
|
def search_author(self):
|
||||||
row = self._get_selected_row()
|
row = self._get_selected_row()
|
||||||
@ -80,7 +89,7 @@ class StoreAction(InterfaceAction):
|
|||||||
mi = self.gui.current_view().model().get_book_display_info(row)
|
mi = self.gui.current_view().model().get_book_display_info(row)
|
||||||
title = mi.title
|
title = mi.title
|
||||||
|
|
||||||
return title
|
return title.strip()
|
||||||
|
|
||||||
def search_title(self):
|
def search_title(self):
|
||||||
row = self._get_selected_row()
|
row = self._get_selected_row()
|
||||||
@ -100,6 +109,13 @@ class StoreAction(InterfaceAction):
|
|||||||
query = 'author:"%s" title:"%s"' % (self._get_author(row), self._get_title(row))
|
query = 'author:"%s" title:"%s"' % (self._get_author(row), self._get_title(row))
|
||||||
self.search(query)
|
self.search(query)
|
||||||
|
|
||||||
|
def choose(self):
|
||||||
|
from calibre.gui2.store.config.chooser.chooser_dialog import StoreChooserDialog
|
||||||
|
d = StoreChooserDialog(self.gui)
|
||||||
|
d.exec_()
|
||||||
|
self.gui.load_store_plugins()
|
||||||
|
self.load_menu()
|
||||||
|
|
||||||
def open_store(self, store_plugin):
|
def open_store(self, store_plugin):
|
||||||
self.show_disclaimer()
|
self.show_disclaimer()
|
||||||
store_plugin.open(self.gui)
|
store_plugin.open(self.gui)
|
||||||
|
316
src/calibre/gui2/bars.py
Normal file
@ -0,0 +1,316 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||||
|
from __future__ import (unicode_literals, division, absolute_import,
|
||||||
|
print_function)
|
||||||
|
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||||
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
|
||||||
|
from PyQt4.Qt import (QObject, QToolBar, Qt, QSize, QToolButton, QVBoxLayout,
|
||||||
|
QLabel, QWidget, QAction, QMenuBar, QMenu)
|
||||||
|
|
||||||
|
from calibre.constants import isosx
|
||||||
|
from calibre.gui2 import gprefs
|
||||||
|
|
||||||
|
class ToolBar(QToolBar): # {{{
|
||||||
|
|
||||||
|
def __init__(self, donate, location_manager, parent):
|
||||||
|
QToolBar.__init__(self, parent)
|
||||||
|
self.setContextMenuPolicy(Qt.PreventContextMenu)
|
||||||
|
self.setMovable(False)
|
||||||
|
self.setFloatable(False)
|
||||||
|
self.setOrientation(Qt.Horizontal)
|
||||||
|
self.setAllowedAreas(Qt.TopToolBarArea|Qt.BottomToolBarArea)
|
||||||
|
self.setStyleSheet('QToolButton:checked { font-weight: bold }')
|
||||||
|
self.preferred_width = self.sizeHint().width()
|
||||||
|
self.gui = parent
|
||||||
|
self.donate_button = donate
|
||||||
|
self.added_actions = []
|
||||||
|
|
||||||
|
self.location_manager = location_manager
|
||||||
|
donate.setAutoRaise(True)
|
||||||
|
donate.setCursor(Qt.PointingHandCursor)
|
||||||
|
self.setAcceptDrops(True)
|
||||||
|
self.showing_donate = False
|
||||||
|
|
||||||
|
def resizeEvent(self, ev):
|
||||||
|
QToolBar.resizeEvent(self, ev)
|
||||||
|
style = self.get_text_style()
|
||||||
|
self.setToolButtonStyle(style)
|
||||||
|
if hasattr(self, 'd_widget') and hasattr(self.d_widget, 'filler'):
|
||||||
|
self.d_widget.filler.setVisible(style != Qt.ToolButtonIconOnly)
|
||||||
|
|
||||||
|
def get_text_style(self):
|
||||||
|
style = Qt.ToolButtonTextUnderIcon
|
||||||
|
s = gprefs['toolbar_icon_size']
|
||||||
|
if s != 'off':
|
||||||
|
p = gprefs['toolbar_text']
|
||||||
|
if p == 'never':
|
||||||
|
style = Qt.ToolButtonIconOnly
|
||||||
|
elif p == 'auto' and self.preferred_width > self.width()+35:
|
||||||
|
style = Qt.ToolButtonIconOnly
|
||||||
|
return style
|
||||||
|
|
||||||
|
def contextMenuEvent(self, *args):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def update_lm_actions(self):
|
||||||
|
for ac in self.added_actions:
|
||||||
|
if ac in self.location_manager.all_actions:
|
||||||
|
ac.setVisible(ac in self.location_manager.available_actions)
|
||||||
|
|
||||||
|
def init_bar(self, actions):
|
||||||
|
self.showing_donate = False
|
||||||
|
for ac in self.added_actions:
|
||||||
|
m = ac.menu()
|
||||||
|
if m is not None:
|
||||||
|
m.setVisible(False)
|
||||||
|
|
||||||
|
self.clear()
|
||||||
|
self.added_actions = []
|
||||||
|
|
||||||
|
bar = self
|
||||||
|
|
||||||
|
for what in actions:
|
||||||
|
if what is None:
|
||||||
|
bar.addSeparator()
|
||||||
|
elif what == 'Location Manager':
|
||||||
|
for ac in self.location_manager.all_actions:
|
||||||
|
bar.addAction(ac)
|
||||||
|
bar.added_actions.append(ac)
|
||||||
|
bar.setup_tool_button(bar, ac, QToolButton.MenuButtonPopup)
|
||||||
|
ac.setVisible(False)
|
||||||
|
elif what == 'Donate':
|
||||||
|
self.d_widget = QWidget()
|
||||||
|
self.d_widget.setLayout(QVBoxLayout())
|
||||||
|
self.d_widget.layout().addWidget(self.donate_button)
|
||||||
|
if isosx:
|
||||||
|
self.d_widget.setStyleSheet('QWidget, QToolButton {background-color: none; border: none; }')
|
||||||
|
self.d_widget.layout().setContentsMargins(0,0,0,0)
|
||||||
|
self.d_widget.setContentsMargins(0,0,0,0)
|
||||||
|
self.d_widget.filler = QLabel(u'\u00a0')
|
||||||
|
self.d_widget.layout().addWidget(self.d_widget.filler)
|
||||||
|
bar.addWidget(self.d_widget)
|
||||||
|
self.showing_donate = True
|
||||||
|
elif what in self.gui.iactions:
|
||||||
|
action = self.gui.iactions[what]
|
||||||
|
bar.addAction(action.qaction)
|
||||||
|
self.added_actions.append(action.qaction)
|
||||||
|
self.setup_tool_button(bar, action.qaction, action.popup_type)
|
||||||
|
self.preferred_width = self.sizeHint().width()
|
||||||
|
|
||||||
|
def setup_tool_button(self, bar, ac, menu_mode=None):
|
||||||
|
ch = bar.widgetForAction(ac)
|
||||||
|
if ch is None:
|
||||||
|
ch = self.child_bar.widgetForAction(ac)
|
||||||
|
ch.setCursor(Qt.PointingHandCursor)
|
||||||
|
ch.setAutoRaise(True)
|
||||||
|
if ac.menu() is not None and menu_mode is not None:
|
||||||
|
ch.setPopupMode(menu_mode)
|
||||||
|
return ch
|
||||||
|
|
||||||
|
#support drag&drop from/to library from/to reader/card
|
||||||
|
def dragEnterEvent(self, event):
|
||||||
|
md = event.mimeData()
|
||||||
|
if md.hasFormat("application/calibre+from_library") or \
|
||||||
|
md.hasFormat("application/calibre+from_device"):
|
||||||
|
event.setDropAction(Qt.CopyAction)
|
||||||
|
event.accept()
|
||||||
|
else:
|
||||||
|
event.ignore()
|
||||||
|
|
||||||
|
def dragMoveEvent(self, event):
|
||||||
|
allowed = False
|
||||||
|
md = event.mimeData()
|
||||||
|
#Drop is only allowed in the location manager widget's different from the selected one
|
||||||
|
for ac in self.location_manager.available_actions:
|
||||||
|
w = self.widgetForAction(ac)
|
||||||
|
if w is not None:
|
||||||
|
if ( md.hasFormat("application/calibre+from_library") or \
|
||||||
|
md.hasFormat("application/calibre+from_device") ) and \
|
||||||
|
w.geometry().contains(event.pos()) and \
|
||||||
|
isinstance(w, QToolButton) and not w.isChecked():
|
||||||
|
allowed = True
|
||||||
|
break
|
||||||
|
if allowed:
|
||||||
|
event.acceptProposedAction()
|
||||||
|
else:
|
||||||
|
event.ignore()
|
||||||
|
|
||||||
|
def dropEvent(self, event):
|
||||||
|
data = event.mimeData()
|
||||||
|
|
||||||
|
mime = 'application/calibre+from_library'
|
||||||
|
if data.hasFormat(mime):
|
||||||
|
ids = list(map(int, str(data.data(mime)).split()))
|
||||||
|
tgt = None
|
||||||
|
for ac in self.location_manager.available_actions:
|
||||||
|
w = self.widgetForAction(ac)
|
||||||
|
if w is not None and w.geometry().contains(event.pos()):
|
||||||
|
tgt = ac.calibre_name
|
||||||
|
if tgt is not None:
|
||||||
|
if tgt == 'main':
|
||||||
|
tgt = None
|
||||||
|
self.gui.sync_to_device(tgt, False, send_ids=ids)
|
||||||
|
event.accept()
|
||||||
|
|
||||||
|
mime = 'application/calibre+from_device'
|
||||||
|
if data.hasFormat(mime):
|
||||||
|
paths = [unicode(u.toLocalFile()) for u in data.urls()]
|
||||||
|
if paths:
|
||||||
|
self.gui.iactions['Add Books'].add_books_from_device(
|
||||||
|
self.gui.current_view(), paths=paths)
|
||||||
|
event.accept()
|
||||||
|
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
class MenuAction(QAction): # {{{
|
||||||
|
|
||||||
|
def __init__(self, clone, parent):
|
||||||
|
QAction.__init__(self, clone.text(), parent)
|
||||||
|
self.clone = clone
|
||||||
|
clone.changed.connect(self.clone_changed)
|
||||||
|
|
||||||
|
def clone_changed(self):
|
||||||
|
self.setText(self.clone.text())
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
class MenuBar(QMenuBar): # {{{
|
||||||
|
|
||||||
|
def __init__(self, location_manager, parent):
|
||||||
|
QMenuBar.__init__(self, parent)
|
||||||
|
self.gui = parent
|
||||||
|
self.setNativeMenuBar(True)
|
||||||
|
|
||||||
|
self.location_manager = location_manager
|
||||||
|
self.added_actions = []
|
||||||
|
|
||||||
|
self.donate_action = QAction(_('Donate'), self)
|
||||||
|
self.donate_menu = QMenu()
|
||||||
|
self.donate_menu.addAction(self.gui.donate_action)
|
||||||
|
self.donate_action.setMenu(self.donate_menu)
|
||||||
|
|
||||||
|
def update_lm_actions(self):
|
||||||
|
for ac in self.added_actions:
|
||||||
|
if ac in self.location_manager.all_actions:
|
||||||
|
ac.setVisible(ac in self.location_manager.available_actions)
|
||||||
|
|
||||||
|
def init_bar(self, actions):
|
||||||
|
for ac in self.added_actions:
|
||||||
|
m = ac.menu()
|
||||||
|
if m is not None:
|
||||||
|
m.setVisible(False)
|
||||||
|
|
||||||
|
self.clear()
|
||||||
|
self.added_actions = []
|
||||||
|
|
||||||
|
for what in actions:
|
||||||
|
if what is None:
|
||||||
|
continue
|
||||||
|
elif what == 'Location Manager':
|
||||||
|
for ac in self.location_manager.all_actions:
|
||||||
|
ac = self.build_menu(ac)
|
||||||
|
self.addAction(ac)
|
||||||
|
self.added_actions.append(ac)
|
||||||
|
ac.setVisible(False)
|
||||||
|
elif what == 'Donate':
|
||||||
|
self.addAction(self.donate_action)
|
||||||
|
elif what in self.gui.iactions:
|
||||||
|
action = self.gui.iactions[what]
|
||||||
|
ac = self.build_menu(action.qaction)
|
||||||
|
self.addAction(ac)
|
||||||
|
self.added_actions.append(ac)
|
||||||
|
|
||||||
|
def build_menu(self, action):
|
||||||
|
m = action.menu()
|
||||||
|
ac = MenuAction(action, self)
|
||||||
|
if m is None:
|
||||||
|
m = QMenu()
|
||||||
|
m.addAction(action)
|
||||||
|
ac.setMenu(m)
|
||||||
|
return ac
|
||||||
|
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
class BarsManager(QObject):
|
||||||
|
|
||||||
|
def __init__(self, donate_button, location_manager, parent):
|
||||||
|
QObject.__init__(self, parent)
|
||||||
|
self.donate_button, self.location_manager = (donate_button,
|
||||||
|
location_manager)
|
||||||
|
|
||||||
|
bars = [ToolBar(donate_button, location_manager, parent) for i in
|
||||||
|
range(3)]
|
||||||
|
self.main_bars = tuple(bars[:2])
|
||||||
|
self.child_bars = tuple(bars[2:])
|
||||||
|
|
||||||
|
self.menu_bar = MenuBar(self.location_manager, self.parent())
|
||||||
|
self.parent().setMenuBar(self.menu_bar)
|
||||||
|
|
||||||
|
self.apply_settings()
|
||||||
|
self.init_bars()
|
||||||
|
|
||||||
|
def database_changed(self, db):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@property
|
||||||
|
def bars(self):
|
||||||
|
for x in self.main_bars + self.child_bars:
|
||||||
|
yield x
|
||||||
|
|
||||||
|
@property
|
||||||
|
def showing_donate(self):
|
||||||
|
for b in self.bars:
|
||||||
|
if b.isVisible() and b.showing_donate:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def init_bars(self):
|
||||||
|
self.bar_actions = tuple(
|
||||||
|
[gprefs['action-layout-toolbar'+x] for x in ('', '-device')] +
|
||||||
|
[gprefs['action-layout-toolbar-child']] +
|
||||||
|
[gprefs['action-layout-menubar']] +
|
||||||
|
[gprefs['action-layout-menubar-device']]
|
||||||
|
)
|
||||||
|
|
||||||
|
for bar, actions in zip(self.bars, self.bar_actions[:3]):
|
||||||
|
bar.init_bar(actions)
|
||||||
|
|
||||||
|
def update_bars(self):
|
||||||
|
'''
|
||||||
|
This shows the correct main toolbar and rebuilds the menubar based on
|
||||||
|
whether a device is connected or not. Note that the toolbars are
|
||||||
|
explicitly not rebuilt, this is to workaround a Qt limitation iwth
|
||||||
|
QToolButton's popup menus and modal dialogs. If you want the toolbars
|
||||||
|
rebuilt, call init_bars().
|
||||||
|
'''
|
||||||
|
showing_device = self.location_manager.has_device
|
||||||
|
main_bar = self.main_bars[1 if showing_device else 0]
|
||||||
|
child_bar = self.child_bars[0]
|
||||||
|
for bar in self.bars:
|
||||||
|
bar.setVisible(False)
|
||||||
|
bar.update_lm_actions()
|
||||||
|
if main_bar.added_actions:
|
||||||
|
main_bar.setVisible(True)
|
||||||
|
if child_bar.added_actions:
|
||||||
|
child_bar.setVisible(True)
|
||||||
|
|
||||||
|
self.menu_bar.init_bar(self.bar_actions[4 if showing_device else 3])
|
||||||
|
self.menu_bar.update_lm_actions()
|
||||||
|
self.menu_bar.setVisible(bool(self.menu_bar.added_actions))
|
||||||
|
|
||||||
|
def apply_settings(self):
|
||||||
|
sz = gprefs['toolbar_icon_size']
|
||||||
|
sz = {'off':0, 'small':24, 'medium':48, 'large':64}[sz]
|
||||||
|
style = Qt.ToolButtonTextUnderIcon
|
||||||
|
if sz > 0 and gprefs['toolbar_text'] == 'never':
|
||||||
|
style = Qt.ToolButtonIconOnly
|
||||||
|
|
||||||
|
for bar in self.bars:
|
||||||
|
bar.setIconSize(QSize(sz, sz))
|
||||||
|
bar.setToolButtonStyle(style)
|
||||||
|
self.donate_button.set_normal_icon_size(sz, sz)
|
||||||
|
|
||||||
|
|
@ -751,6 +751,7 @@ class DeviceMixin(object): # {{{
|
|||||||
if self.current_view() != self.library_view:
|
if self.current_view() != self.library_view:
|
||||||
self.book_details.reset_info()
|
self.book_details.reset_info()
|
||||||
self.location_manager.update_devices()
|
self.location_manager.update_devices()
|
||||||
|
self.bars_manager.update_bars()
|
||||||
self.library_view.set_device_connected(self.device_connected)
|
self.library_view.set_device_connected(self.device_connected)
|
||||||
self.refresh_ondevice()
|
self.refresh_ondevice()
|
||||||
device_signals.device_connection_changed.emit(connected)
|
device_signals.device_connection_changed.emit(connected)
|
||||||
@ -764,6 +765,7 @@ class DeviceMixin(object): # {{{
|
|||||||
info, cp, fs = job.result
|
info, cp, fs = job.result
|
||||||
self.location_manager.update_devices(cp, fs,
|
self.location_manager.update_devices(cp, fs,
|
||||||
self.device_manager.device.icon)
|
self.device_manager.device.icon)
|
||||||
|
self.bars_manager.update_bars()
|
||||||
self.status_bar.device_connected(info[0])
|
self.status_bar.device_connected(info[0])
|
||||||
self.device_manager.books(Dispatcher(self.metadata_downloaded))
|
self.device_manager.books(Dispatcher(self.metadata_downloaded))
|
||||||
|
|
||||||
|
@ -78,22 +78,22 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
<layout class="QGridLayout">
|
||||||
<item>
|
<item row="0" column="0">
|
||||||
<widget class="QPushButton" name="sort_by_author">
|
<widget class="QPushButton" name="sort_by_author">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Sort by author</string>
|
<string>Sort by author</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item row="0" column="1">
|
||||||
<widget class="QPushButton" name="sort_by_author_sort">
|
<widget class="QPushButton" name="sort_by_author_sort">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Sort by author sort</string>
|
<string>Sort by author sort</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item row="1" column="0">
|
||||||
<widget class="QPushButton" name="recalc_author_sort">
|
<widget class="QPushButton" name="recalc_author_sort">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string>Reset all the author sort values to a value automatically
|
<string>Reset all the author sort values to a value automatically
|
||||||
@ -105,7 +105,7 @@ generated can be controlled via Preferences->Advanced->Tweaks</string>
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item row="1" column="1">
|
||||||
<widget class="QPushButton" name="auth_sort_to_author">
|
<widget class="QPushButton" name="auth_sort_to_author">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string>Copy author sort to author for every author. You typically use this button
|
<string>Copy author sort to author for every author. You typically use this button
|
||||||
@ -116,20 +116,7 @@ after changing Preferences->Advanced->Tweaks->Author sort name algorith
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item row="1" column="2">
|
||||||
<spacer name="horizontalSpacer_3">
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Horizontal</enum>
|
|
||||||
</property>
|
|
||||||
<property name="sizeHint" stdset="0">
|
|
||||||
<size>
|
|
||||||
<width>40</width>
|
|
||||||
<height>20</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
</spacer>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QDialogButtonBox" name="buttonBox">
|
<widget class="QDialogButtonBox" name="buttonBox">
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||||
|
@ -207,8 +207,9 @@ class SchedulerDialog(QDialog, Ui_Dialog):
|
|||||||
self.recipe_model.searched.connect(self.search.search_done,
|
self.recipe_model.searched.connect(self.search.search_done,
|
||||||
type=Qt.QueuedConnection)
|
type=Qt.QueuedConnection)
|
||||||
self.recipe_model.searched.connect(self.search_done)
|
self.recipe_model.searched.connect(self.search_done)
|
||||||
self.search.setFocus(Qt.OtherFocusReason)
|
self.recipes.setFocus(Qt.OtherFocusReason)
|
||||||
self.commit_on_change = True
|
self.commit_on_change = True
|
||||||
|
self.previous_urn = None
|
||||||
|
|
||||||
self.recipes.setModel(self.recipe_model)
|
self.recipes.setModel(self.recipe_model)
|
||||||
self.detail_box.setVisible(False)
|
self.detail_box.setVisible(False)
|
||||||
@ -228,6 +229,9 @@ class SchedulerDialog(QDialog, Ui_Dialog):
|
|||||||
|
|
||||||
self.old_news.setValue(gconf['oldest_news'])
|
self.old_news.setValue(gconf['oldest_news'])
|
||||||
|
|
||||||
|
self.go_button.clicked.connect(self.search.do_search)
|
||||||
|
self.clear_search_button.clicked.connect(self.search.clear_clicked)
|
||||||
|
|
||||||
def set_pw_echo_mode(self, state):
|
def set_pw_echo_mode(self, state):
|
||||||
self.password.setEchoMode(self.password.Normal
|
self.password.setEchoMode(self.password.Normal
|
||||||
if state == Qt.Checked else self.password.Password)
|
if state == Qt.Checked else self.password.Password)
|
||||||
@ -265,14 +269,9 @@ class SchedulerDialog(QDialog, Ui_Dialog):
|
|||||||
self.last_downloaded.setVisible(enabled)
|
self.last_downloaded.setVisible(enabled)
|
||||||
|
|
||||||
def current_changed(self, current, previous):
|
def current_changed(self, current, previous):
|
||||||
if self.commit_on_change:
|
if self.previous_urn is not None:
|
||||||
if previous.isValid():
|
self.commit(urn=self.previous_urn)
|
||||||
if not self.commit(urn=getattr(previous.internalPointer(),
|
self.previous_urn = None
|
||||||
'urn', None)):
|
|
||||||
self.commit_on_change = False
|
|
||||||
self.recipes.setCurrentIndex(previous)
|
|
||||||
else:
|
|
||||||
self.commit_on_change = True
|
|
||||||
|
|
||||||
urn = self.current_urn
|
urn = self.current_urn
|
||||||
if urn is not None:
|
if urn is not None:
|
||||||
@ -332,6 +331,7 @@ class SchedulerDialog(QDialog, Ui_Dialog):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def initialize_detail_box(self, urn):
|
def initialize_detail_box(self, urn):
|
||||||
|
self.previous_urn = urn
|
||||||
self.detail_box.setVisible(True)
|
self.detail_box.setVisible(True)
|
||||||
self.download_button.setVisible(True)
|
self.download_button.setVisible(True)
|
||||||
self.detail_box.setCurrentIndex(0)
|
self.detail_box.setCurrentIndex(0)
|
||||||
|
@ -17,21 +17,30 @@
|
|||||||
<iconset resource="../../../../resources/images.qrc">
|
<iconset resource="../../../../resources/images.qrc">
|
||||||
<normaloff>:/images/scheduler.png</normaloff>:/images/scheduler.png</iconset>
|
<normaloff>:/images/scheduler.png</normaloff>:/images/scheduler.png</iconset>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QGridLayout" name="gridLayout" columnstretch="0,1,2">
|
<layout class="QGridLayout" name="gridLayout">
|
||||||
<item row="0" column="0">
|
<item row="0" column="0">
|
||||||
<widget class="QLabel" name="label_8">
|
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||||
<property name="text">
|
<item>
|
||||||
<string>&Search:</string>
|
<widget class="SearchBox2" name="search"/>
|
||||||
</property>
|
</item>
|
||||||
<property name="buddy">
|
<item>
|
||||||
<cstring>search</cstring>
|
<widget class="QToolButton" name="go_button">
|
||||||
</property>
|
<property name="text">
|
||||||
</widget>
|
<string>Go</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QToolButton" name="clear_search_button">
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="../../../../resources/images.qrc">
|
||||||
|
<normaloff>:/images/clear_left.png</normaloff>:/images/clear_left.png</iconset>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
<item row="0" column="1">
|
<item row="0" column="1" rowspan="2">
|
||||||
<widget class="SearchBox2" name="search"/>
|
|
||||||
</item>
|
|
||||||
<item row="0" column="2" rowspan="3">
|
|
||||||
<widget class="QScrollArea" name="scrollArea">
|
<widget class="QScrollArea" name="scrollArea">
|
||||||
<property name="frameShape">
|
<property name="frameShape">
|
||||||
<enum>QFrame::NoFrame</enum>
|
<enum>QFrame::NoFrame</enum>
|
||||||
@ -44,7 +53,7 @@
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>486</width>
|
<width>524</width>
|
||||||
<height>504</height>
|
<height>504</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
@ -320,7 +329,7 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="1" column="0" colspan="2">
|
<item row="1" column="0">
|
||||||
<widget class="QTreeView" name="recipes">
|
<widget class="QTreeView" name="recipes">
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
<sizepolicy hsizetype="Minimum" vsizetype="Expanding">
|
<sizepolicy hsizetype="Minimum" vsizetype="Expanding">
|
||||||
@ -345,7 +354,17 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="3" column="2">
|
<item row="2" column="0">
|
||||||
|
<widget class="QLabel" name="count_label">
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignCenter</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="1">
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLabel" name="label_7">
|
<widget class="QLabel" name="label_7">
|
||||||
@ -376,17 +395,7 @@
|
|||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
<item row="4" column="2">
|
<item row="3" column="0">
|
||||||
<widget class="QDialogButtonBox" name="buttonBox">
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Horizontal</enum>
|
|
||||||
</property>
|
|
||||||
<property name="standardButtons">
|
|
||||||
<set>QDialogButtonBox::Save</set>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="4" column="0" colspan="2">
|
|
||||||
<widget class="QPushButton" name="download_all_button">
|
<widget class="QPushButton" name="download_all_button">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string>Download all scheduled news sources at once</string>
|
<string>Download all scheduled news sources at once</string>
|
||||||
@ -394,15 +403,19 @@
|
|||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Download &all scheduled</string>
|
<string>Download &all scheduled</string>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="../../../../resources/images.qrc">
|
||||||
|
<normaloff>:/images/news.png</normaloff>:/images/news.png</iconset>
|
||||||
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="3" column="0" colspan="2">
|
<item row="3" column="1">
|
||||||
<widget class="QLabel" name="count_label">
|
<widget class="QDialogButtonBox" name="buttonBox">
|
||||||
<property name="text">
|
<property name="orientation">
|
||||||
<string/>
|
<enum>Qt::Horizontal</enum>
|
||||||
</property>
|
</property>
|
||||||
<property name="alignment">
|
<property name="standardButtons">
|
||||||
<set>Qt::AlignCenter</set>
|
<set>QDialogButtonBox::Save</set>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
@ -5,10 +5,193 @@ __license__ = 'GPL v3'
|
|||||||
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from PyQt4.Qt import Qt, QDialog, QDialogButtonBox
|
from PyQt4.Qt import (Qt, QDialog, QDialogButtonBox, QSyntaxHighlighter,
|
||||||
|
QRegExp, QApplication,
|
||||||
|
QTextCharFormat, QFont, QColor, QCursor)
|
||||||
|
|
||||||
from calibre.gui2.dialogs.template_dialog_ui import Ui_TemplateDialog
|
from calibre.gui2.dialogs.template_dialog_ui import Ui_TemplateDialog
|
||||||
from calibre.utils.formatter_functions import formatter_functions
|
from calibre.utils.formatter_functions import formatter_functions
|
||||||
|
|
||||||
|
class ParenPosition:
|
||||||
|
|
||||||
|
def __init__(self, block, pos, paren):
|
||||||
|
self.block = block
|
||||||
|
self.pos = pos
|
||||||
|
self.paren = paren
|
||||||
|
self.highlight = False
|
||||||
|
|
||||||
|
def set_highlight(self, to_what):
|
||||||
|
self.highlight = to_what
|
||||||
|
|
||||||
|
class TemplateHighlighter(QSyntaxHighlighter):
|
||||||
|
|
||||||
|
Config = {}
|
||||||
|
Rules = []
|
||||||
|
Formats = {}
|
||||||
|
BN_FACTOR = 1000
|
||||||
|
|
||||||
|
KEYWORDS = ["program"]
|
||||||
|
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
super(TemplateHighlighter, self).__init__(parent)
|
||||||
|
|
||||||
|
self.initializeFormats()
|
||||||
|
|
||||||
|
TemplateHighlighter.Rules.append((QRegExp(
|
||||||
|
"|".join([r"\b%s\b" % keyword for keyword in self.KEYWORDS])),
|
||||||
|
"keyword"))
|
||||||
|
TemplateHighlighter.Rules.append((QRegExp(
|
||||||
|
"|".join([r"\b%s\b" % builtin for builtin in
|
||||||
|
formatter_functions.get_builtins()])),
|
||||||
|
"builtin"))
|
||||||
|
|
||||||
|
TemplateHighlighter.Rules.append((QRegExp(
|
||||||
|
r"\b[+-]?[0-9]+[lL]?\b"
|
||||||
|
r"|\b[+-]?0[xX][0-9A-Fa-f]+[lL]?\b"
|
||||||
|
r"|\b[+-]?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\b"),
|
||||||
|
"number"))
|
||||||
|
|
||||||
|
stringRe = QRegExp(r"""(?:[^:]'[^']*'|"[^"]*")""")
|
||||||
|
stringRe.setMinimal(True)
|
||||||
|
TemplateHighlighter.Rules.append((stringRe, "string"))
|
||||||
|
|
||||||
|
lparenRe = QRegExp(r'\(')
|
||||||
|
lparenRe.setMinimal(True)
|
||||||
|
TemplateHighlighter.Rules.append((lparenRe, "lparen"))
|
||||||
|
rparenRe = QRegExp(r'\)')
|
||||||
|
rparenRe.setMinimal(True)
|
||||||
|
TemplateHighlighter.Rules.append((rparenRe, "rparen"))
|
||||||
|
|
||||||
|
self.regenerate_paren_positions()
|
||||||
|
self.highlighted_paren = False
|
||||||
|
|
||||||
|
def initializeFormats(self):
|
||||||
|
Config = self.Config
|
||||||
|
Config["fontfamily"] = "monospace"
|
||||||
|
#Config["fontsize"] = 10
|
||||||
|
for name, color, bold, italic in (
|
||||||
|
("normal", "#000000", False, False),
|
||||||
|
("keyword", "#000080", True, False),
|
||||||
|
("builtin", "#0000A0", False, False),
|
||||||
|
("comment", "#007F00", False, True),
|
||||||
|
("string", "#808000", False, False),
|
||||||
|
("number", "#924900", False, False),
|
||||||
|
("lparen", "#000000", True, True),
|
||||||
|
("rparen", "#000000", True, True)):
|
||||||
|
Config["%sfontcolor" % name] = color
|
||||||
|
Config["%sfontbold" % name] = bold
|
||||||
|
Config["%sfontitalic" % name] = italic
|
||||||
|
|
||||||
|
baseFormat = QTextCharFormat()
|
||||||
|
baseFormat.setFontFamily(Config["fontfamily"])
|
||||||
|
#baseFormat.setFontPointSize(Config["fontsize"])
|
||||||
|
|
||||||
|
for name in ("normal", "keyword", "builtin", "comment",
|
||||||
|
"string", "number", "lparen", "rparen"):
|
||||||
|
format = QTextCharFormat(baseFormat)
|
||||||
|
format.setForeground(QColor(Config["%sfontcolor" % name]))
|
||||||
|
if Config["%sfontbold" % name]:
|
||||||
|
format.setFontWeight(QFont.Bold)
|
||||||
|
format.setFontItalic(Config["%sfontitalic" % name])
|
||||||
|
self.Formats[name] = format
|
||||||
|
|
||||||
|
def find_paren(self, bn, pos):
|
||||||
|
dex = bn * self.BN_FACTOR + pos
|
||||||
|
return self.paren_pos_map.get(dex, None)
|
||||||
|
|
||||||
|
def highlightBlock(self, text):
|
||||||
|
bn = self.currentBlock().blockNumber()
|
||||||
|
textLength = text.length()
|
||||||
|
|
||||||
|
self.setFormat(0, textLength, self.Formats["normal"])
|
||||||
|
|
||||||
|
if text.isEmpty():
|
||||||
|
pass
|
||||||
|
elif text[0] == "#":
|
||||||
|
self.setFormat(0, text.length(), self.Formats["comment"])
|
||||||
|
return
|
||||||
|
|
||||||
|
for regex, format in TemplateHighlighter.Rules:
|
||||||
|
i = regex.indexIn(text)
|
||||||
|
while i >= 0:
|
||||||
|
length = regex.matchedLength()
|
||||||
|
if format in ['lparen', 'rparen']:
|
||||||
|
pp = self.find_paren(bn, i)
|
||||||
|
if pp and pp.highlight:
|
||||||
|
self.setFormat(i, length, self.Formats[format])
|
||||||
|
else:
|
||||||
|
self.setFormat(i, length, self.Formats[format])
|
||||||
|
i = regex.indexIn(text, i + length)
|
||||||
|
|
||||||
|
if self.generate_paren_positions:
|
||||||
|
t = unicode(text)
|
||||||
|
i = 0
|
||||||
|
foundQuote = False
|
||||||
|
while i < len(t):
|
||||||
|
c = t[i]
|
||||||
|
if c == ':':
|
||||||
|
# Deal with the funky syntax of template program mode.
|
||||||
|
# This won't work if there are more than one template
|
||||||
|
# expression in the document.
|
||||||
|
if not foundQuote and i+1 < len(t) and t[i+1] == "'":
|
||||||
|
i += 2
|
||||||
|
elif c in ["'", '"']:
|
||||||
|
foundQuote = True
|
||||||
|
i += 1
|
||||||
|
j = t[i:].find(c)
|
||||||
|
if j < 0:
|
||||||
|
i = len(t)
|
||||||
|
else:
|
||||||
|
i = i + j
|
||||||
|
elif c in ['(', ')']:
|
||||||
|
pp = ParenPosition(bn, i, c)
|
||||||
|
self.paren_positions.append(pp)
|
||||||
|
self.paren_pos_map[bn*self.BN_FACTOR+i] = pp
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
def rehighlight(self):
|
||||||
|
QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
|
||||||
|
QSyntaxHighlighter.rehighlight(self)
|
||||||
|
QApplication.restoreOverrideCursor()
|
||||||
|
|
||||||
|
def check_cursor_pos(self, chr, block, pos_in_block):
|
||||||
|
found_pp = -1
|
||||||
|
for i, pp in enumerate(self.paren_positions):
|
||||||
|
pp.set_highlight(False)
|
||||||
|
if pp.block == block and pp.pos == pos_in_block:
|
||||||
|
found_pp = i
|
||||||
|
|
||||||
|
if chr not in ['(', ')']:
|
||||||
|
if self.highlighted_paren:
|
||||||
|
self.rehighlight()
|
||||||
|
self.highlighted_paren = False
|
||||||
|
return
|
||||||
|
|
||||||
|
if found_pp >= 0:
|
||||||
|
stack = 0
|
||||||
|
if chr == '(':
|
||||||
|
list = self.paren_positions[found_pp+1:]
|
||||||
|
else:
|
||||||
|
list = reversed(self.paren_positions[0:found_pp])
|
||||||
|
for pp in list:
|
||||||
|
if pp.paren == chr:
|
||||||
|
stack += 1;
|
||||||
|
elif stack:
|
||||||
|
stack -= 1
|
||||||
|
else:
|
||||||
|
pp.set_highlight(True)
|
||||||
|
self.paren_positions[found_pp].set_highlight(True)
|
||||||
|
break
|
||||||
|
self.highlighted_paren = True
|
||||||
|
self.rehighlight()
|
||||||
|
|
||||||
|
def regenerate_paren_positions(self):
|
||||||
|
self.generate_paren_positions = True
|
||||||
|
self.paren_positions = []
|
||||||
|
self.paren_pos_map = {}
|
||||||
|
self.rehighlight()
|
||||||
|
self.generate_paren_positions = False
|
||||||
|
|
||||||
class TemplateDialog(QDialog, Ui_TemplateDialog):
|
class TemplateDialog(QDialog, Ui_TemplateDialog):
|
||||||
|
|
||||||
def __init__(self, parent, text):
|
def __init__(self, parent, text):
|
||||||
@ -20,6 +203,11 @@ class TemplateDialog(QDialog, Ui_TemplateDialog):
|
|||||||
self.setWindowFlags(self.windowFlags()&(~Qt.WindowContextHelpButtonHint))
|
self.setWindowFlags(self.windowFlags()&(~Qt.WindowContextHelpButtonHint))
|
||||||
self.setWindowIcon(icon)
|
self.setWindowIcon(icon)
|
||||||
|
|
||||||
|
self.last_text = ''
|
||||||
|
self.highlighter = TemplateHighlighter(self.textbox.document())
|
||||||
|
self.textbox.cursorPositionChanged.connect(self.text_cursor_changed)
|
||||||
|
self.textbox.textChanged.connect(self.textbox_changed)
|
||||||
|
|
||||||
self.textbox.setTabStopWidth(10)
|
self.textbox.setTabStopWidth(10)
|
||||||
self.source_code.setTabStopWidth(10)
|
self.source_code.setTabStopWidth(10)
|
||||||
self.documentation.setReadOnly(True)
|
self.documentation.setReadOnly(True)
|
||||||
@ -46,6 +234,22 @@ class TemplateDialog(QDialog, Ui_TemplateDialog):
|
|||||||
self.function.setCurrentIndex(0)
|
self.function.setCurrentIndex(0)
|
||||||
self.function.currentIndexChanged[str].connect(self.function_changed)
|
self.function.currentIndexChanged[str].connect(self.function_changed)
|
||||||
|
|
||||||
|
def textbox_changed(self):
|
||||||
|
cur_text = unicode(self.textbox.toPlainText())
|
||||||
|
if self.last_text != cur_text:
|
||||||
|
self.last_text = cur_text
|
||||||
|
self.highlighter.regenerate_paren_positions()
|
||||||
|
|
||||||
|
def text_cursor_changed(self):
|
||||||
|
cursor = self.textbox.textCursor()
|
||||||
|
block_number = cursor.blockNumber()
|
||||||
|
pos_in_block = cursor.positionInBlock()
|
||||||
|
position = cursor.position()
|
||||||
|
t = unicode(self.textbox.toPlainText())
|
||||||
|
if position < len(t):
|
||||||
|
self.highlighter.check_cursor_pos(t[position], block_number,
|
||||||
|
pos_in_block)
|
||||||
|
|
||||||
def function_changed(self, toWhat):
|
def function_changed(self, toWhat):
|
||||||
name = unicode(toWhat)
|
name = unicode(toWhat)
|
||||||
self.source_code.clear()
|
self.source_code.clear()
|
||||||
|
132
src/calibre/gui2/dialogs/template_line_editor.py
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||||
|
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||||
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
from PyQt4.Qt import (QLineEdit, QDialog, QGridLayout, QLabel,
|
||||||
|
QDialogButtonBox, QColor, QComboBox, QIcon)
|
||||||
|
|
||||||
|
from calibre.gui2.dialogs.template_dialog import TemplateDialog
|
||||||
|
from calibre.gui2.complete import MultiCompleteLineEdit
|
||||||
|
from calibre.gui2 import error_dialog
|
||||||
|
|
||||||
|
class TemplateLineEditor(QLineEdit):
|
||||||
|
|
||||||
|
'''
|
||||||
|
Extend the context menu of a QLineEdit to include more actions.
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self, parent):
|
||||||
|
QLineEdit.__init__(self, parent)
|
||||||
|
self.tags = None
|
||||||
|
|
||||||
|
def set_tags(self, tags):
|
||||||
|
self.tags = tags
|
||||||
|
|
||||||
|
def contextMenuEvent(self, event):
|
||||||
|
menu = self.createStandardContextMenu()
|
||||||
|
menu.addSeparator()
|
||||||
|
|
||||||
|
action_open_editor = menu.addAction(_('Open Template Editor'))
|
||||||
|
action_open_editor.triggered.connect(self.open_editor)
|
||||||
|
if self.tags:
|
||||||
|
action_tag_wizard = menu.addAction(_('Open Tag Wizard'))
|
||||||
|
action_tag_wizard.triggered.connect(self.tag_wizard)
|
||||||
|
menu.exec_(event.globalPos())
|
||||||
|
|
||||||
|
def open_editor(self):
|
||||||
|
t = TemplateDialog(self, self.text())
|
||||||
|
t.setWindowTitle(_('Edit template'))
|
||||||
|
if t.exec_():
|
||||||
|
self.setText(t.textbox.toPlainText())
|
||||||
|
|
||||||
|
def tag_wizard(self):
|
||||||
|
txt = unicode(self.text())
|
||||||
|
if txt and not txt.startswith('program:\n#tag wizard'):
|
||||||
|
error_dialog(self, _('Invalid text'),
|
||||||
|
_('The text in the box was not generated by this wizard'),
|
||||||
|
show=True, show_copy_button=False)
|
||||||
|
return
|
||||||
|
d = TagWizard(self, self.tags, unicode(self.text()))
|
||||||
|
if d.exec_():
|
||||||
|
self.setText(d.template)
|
||||||
|
|
||||||
|
class TagWizard(QDialog):
|
||||||
|
|
||||||
|
def __init__(self, parent, tags, txt):
|
||||||
|
QDialog.__init__(self, parent)
|
||||||
|
self.setWindowTitle(_('Tag Wizard'))
|
||||||
|
self.setWindowIcon(QIcon(I('wizard.png')))
|
||||||
|
|
||||||
|
self.tags = tags
|
||||||
|
l = QGridLayout()
|
||||||
|
self.setLayout(l)
|
||||||
|
l.setColumnStretch(0, 1)
|
||||||
|
l.setColumnMinimumWidth(0, 300)
|
||||||
|
l.addWidget(QLabel(_('Tags (more than one per box permitted)')), 0, 0, 1, 1)
|
||||||
|
l.addWidget(QLabel(_('Color')), 0, 1, 1, 1)
|
||||||
|
self.tagboxes = []
|
||||||
|
self.colorboxes = []
|
||||||
|
self.colors = [unicode(s) for s in list(QColor.colorNames())]
|
||||||
|
self.colors.insert(0, '')
|
||||||
|
for i in range(0, 10):
|
||||||
|
tb = MultiCompleteLineEdit(self)
|
||||||
|
tb.set_separator(', ')
|
||||||
|
tb.update_items_cache(self.tags)
|
||||||
|
self.tagboxes.append(tb)
|
||||||
|
l.addWidget(tb, i+1, 0, 1, 1)
|
||||||
|
cb = QComboBox(self)
|
||||||
|
cb.addItems(self.colors)
|
||||||
|
self.colorboxes.append(cb)
|
||||||
|
l.addWidget(cb, i+1, 1, 1, 1)
|
||||||
|
|
||||||
|
if txt:
|
||||||
|
lines = txt.split('\n')[3:]
|
||||||
|
i = 0
|
||||||
|
for line in lines:
|
||||||
|
if line.startswith('#'):
|
||||||
|
t,c = line[1:].split(':|:')
|
||||||
|
try:
|
||||||
|
self.colorboxes[i].setCurrentIndex(self.colorboxes[i].findText(c))
|
||||||
|
self.tagboxes[i].setText(t)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
bb = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel, parent=self)
|
||||||
|
l.addWidget(bb, 100, 1, 1, 1)
|
||||||
|
bb.accepted.connect(self.accepted)
|
||||||
|
bb.rejected.connect(self.reject)
|
||||||
|
self.template = ''
|
||||||
|
|
||||||
|
def accepted(self):
|
||||||
|
res = ("program:\n#tag wizard -- do not directly edit\n"
|
||||||
|
" t = field('tags');\n first_non_empty(\n")
|
||||||
|
lines = []
|
||||||
|
for tb, cb in zip(self.tagboxes, self.colorboxes):
|
||||||
|
tags = [t.strip() for t in unicode(tb.text()).split(',') if t.strip()]
|
||||||
|
tags = '$|^'.join(tags)
|
||||||
|
c = unicode(cb.currentText()).strip()
|
||||||
|
if not tags or not c:
|
||||||
|
continue
|
||||||
|
if c not in self.colors:
|
||||||
|
error_dialog(self, _('Invalid color'),
|
||||||
|
_('The color {0} is not valid').format(c),
|
||||||
|
show=True, show_copy_button=False)
|
||||||
|
return False
|
||||||
|
lines.append(" in_list(t, ',', '^{0}$', '{1}', '')".format(tags, c))
|
||||||
|
res += ',\n'.join(lines)
|
||||||
|
res += ')\n'
|
||||||
|
self.template = res
|
||||||
|
res = ''
|
||||||
|
for tb, cb in zip(self.tagboxes, self.colorboxes):
|
||||||
|
t = unicode(tb.text()).strip()
|
||||||
|
if t.endswith(','):
|
||||||
|
t = t[:-1]
|
||||||
|
c = unicode(cb.currentText()).strip()
|
||||||
|
if t and c:
|
||||||
|
res += '#' + t + ':|:' + c + '\n'
|
||||||
|
self.template += res
|
||||||
|
self.accept()
|
@ -7,16 +7,17 @@ __docformat__ = 'restructuredtext en'
|
|||||||
|
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
|
||||||
from PyQt4.Qt import (QIcon, Qt, QWidget, QToolBar, QSize,
|
from PyQt4.Qt import (QIcon, Qt, QWidget, QSize,
|
||||||
pyqtSignal, QToolButton, QMenu, QMenuBar, QAction,
|
pyqtSignal, QToolButton, QMenu,
|
||||||
QObject, QVBoxLayout, QSizePolicy, QLabel, QHBoxLayout, QActionGroup)
|
QObject, QVBoxLayout, QSizePolicy, QLabel, QHBoxLayout, QActionGroup)
|
||||||
|
|
||||||
|
|
||||||
from calibre.constants import __appname__, isosx
|
from calibre.constants import __appname__
|
||||||
from calibre.gui2.search_box import SearchBox2, SavedSearchBox
|
from calibre.gui2.search_box import SearchBox2, SavedSearchBox
|
||||||
from calibre.gui2.throbber import ThrobbingButton
|
from calibre.gui2.throbber import ThrobbingButton
|
||||||
from calibre.gui2 import gprefs
|
from calibre.gui2.bars import BarsManager
|
||||||
from calibre.gui2.widgets import ComboBoxWithHelp
|
from calibre.gui2.widgets import ComboBoxWithHelp
|
||||||
|
from calibre.utils.config_base import tweaks
|
||||||
from calibre import human_readable
|
from calibre import human_readable
|
||||||
|
|
||||||
class LocationManager(QObject): # {{{
|
class LocationManager(QObject): # {{{
|
||||||
@ -35,6 +36,8 @@ class LocationManager(QObject): # {{{
|
|||||||
self._mem = []
|
self._mem = []
|
||||||
self.tooltips = {}
|
self.tooltips = {}
|
||||||
|
|
||||||
|
self.all_actions = []
|
||||||
|
|
||||||
def ac(name, text, icon, tooltip):
|
def ac(name, text, icon, tooltip):
|
||||||
icon = QIcon(I(icon))
|
icon = QIcon(I(icon))
|
||||||
ac = self.location_actions.addAction(icon, text)
|
ac = self.location_actions.addAction(icon, text)
|
||||||
@ -59,6 +62,7 @@ class LocationManager(QObject): # {{{
|
|||||||
ac.setMenu(m)
|
ac.setMenu(m)
|
||||||
ac.calibre_name = name
|
ac.calibre_name = name
|
||||||
|
|
||||||
|
self.all_actions.append(ac)
|
||||||
return ac
|
return ac
|
||||||
|
|
||||||
self.library_action = ac('library', _('Library'), 'lt.png',
|
self.library_action = ac('library', _('Library'), 'lt.png',
|
||||||
@ -234,259 +238,6 @@ class Spacer(QWidget): # {{{
|
|||||||
self.l.addStretch(10)
|
self.l.addStretch(10)
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
class MenuAction(QAction): # {{{
|
|
||||||
|
|
||||||
def __init__(self, clone, parent):
|
|
||||||
QAction.__init__(self, clone.text(), parent)
|
|
||||||
self.clone = clone
|
|
||||||
clone.changed.connect(self.clone_changed)
|
|
||||||
|
|
||||||
def clone_changed(self):
|
|
||||||
self.setText(self.clone.text())
|
|
||||||
# }}}
|
|
||||||
|
|
||||||
class MenuBar(QMenuBar): # {{{
|
|
||||||
|
|
||||||
def __init__(self, location_manager, parent):
|
|
||||||
QMenuBar.__init__(self, parent)
|
|
||||||
self.gui = parent
|
|
||||||
self.setNativeMenuBar(True)
|
|
||||||
|
|
||||||
self.location_manager = location_manager
|
|
||||||
self.location_manager.locations_changed.connect(self.build_bar)
|
|
||||||
self.added_actions = []
|
|
||||||
|
|
||||||
self.donate_action = QAction(_('Donate'), self)
|
|
||||||
self.donate_menu = QMenu()
|
|
||||||
self.donate_menu.addAction(self.gui.donate_action)
|
|
||||||
self.donate_action.setMenu(self.donate_menu)
|
|
||||||
self.build_bar()
|
|
||||||
|
|
||||||
def build_bar(self, changed_action=None):
|
|
||||||
showing_device = self.location_manager.has_device
|
|
||||||
actions = '-device' if showing_device else ''
|
|
||||||
actions = gprefs['action-layout-menubar'+actions]
|
|
||||||
|
|
||||||
show_main = len(actions) > 0
|
|
||||||
self.setVisible(show_main)
|
|
||||||
|
|
||||||
for ac in self.added_actions:
|
|
||||||
m = ac.menu()
|
|
||||||
if m is not None:
|
|
||||||
m.setVisible(False)
|
|
||||||
|
|
||||||
self.clear()
|
|
||||||
self.added_actions = []
|
|
||||||
self.action_map = {}
|
|
||||||
|
|
||||||
for what in actions:
|
|
||||||
if what is None:
|
|
||||||
continue
|
|
||||||
elif what == 'Location Manager':
|
|
||||||
for ac in self.location_manager.available_actions:
|
|
||||||
ac = self.build_menu(ac)
|
|
||||||
self.addAction(ac)
|
|
||||||
self.added_actions.append(ac)
|
|
||||||
elif what == 'Donate':
|
|
||||||
self.addAction(self.donate_action)
|
|
||||||
elif what in self.gui.iactions:
|
|
||||||
action = self.gui.iactions[what]
|
|
||||||
ac = self.build_menu(action.qaction)
|
|
||||||
self.addAction(ac)
|
|
||||||
self.added_actions.append(ac)
|
|
||||||
|
|
||||||
def build_menu(self, action):
|
|
||||||
m = action.menu()
|
|
||||||
ac = MenuAction(action, self)
|
|
||||||
if m is None:
|
|
||||||
m = QMenu()
|
|
||||||
m.addAction(action)
|
|
||||||
ac.setMenu(m)
|
|
||||||
return ac
|
|
||||||
|
|
||||||
# }}}
|
|
||||||
|
|
||||||
class BaseToolBar(QToolBar): # {{{
|
|
||||||
|
|
||||||
def __init__(self, parent):
|
|
||||||
QToolBar.__init__(self, parent)
|
|
||||||
self.setContextMenuPolicy(Qt.PreventContextMenu)
|
|
||||||
self.setMovable(False)
|
|
||||||
self.setFloatable(False)
|
|
||||||
self.setOrientation(Qt.Horizontal)
|
|
||||||
self.setAllowedAreas(Qt.TopToolBarArea|Qt.BottomToolBarArea)
|
|
||||||
self.setStyleSheet('QToolButton:checked { font-weight: bold }')
|
|
||||||
self.preferred_width = self.sizeHint().width()
|
|
||||||
|
|
||||||
def resizeEvent(self, ev):
|
|
||||||
QToolBar.resizeEvent(self, ev)
|
|
||||||
style = self.get_text_style()
|
|
||||||
self.setToolButtonStyle(style)
|
|
||||||
if hasattr(self, 'd_widget') and hasattr(self.d_widget, 'filler'):
|
|
||||||
self.d_widget.filler.setVisible(style != Qt.ToolButtonIconOnly)
|
|
||||||
|
|
||||||
def get_text_style(self):
|
|
||||||
style = Qt.ToolButtonTextUnderIcon
|
|
||||||
s = gprefs['toolbar_icon_size']
|
|
||||||
if s != 'off':
|
|
||||||
p = gprefs['toolbar_text']
|
|
||||||
if p == 'never':
|
|
||||||
style = Qt.ToolButtonIconOnly
|
|
||||||
elif p == 'auto' and self.preferred_width > self.width()+35:
|
|
||||||
style = Qt.ToolButtonIconOnly
|
|
||||||
return style
|
|
||||||
|
|
||||||
def contextMenuEvent(self, *args):
|
|
||||||
pass
|
|
||||||
|
|
||||||
# }}}
|
|
||||||
|
|
||||||
class ToolBar(BaseToolBar): # {{{
|
|
||||||
|
|
||||||
def __init__(self, donate, location_manager, child_bar, parent):
|
|
||||||
BaseToolBar.__init__(self, parent)
|
|
||||||
self.gui = parent
|
|
||||||
self.child_bar = child_bar
|
|
||||||
self.donate_button = donate
|
|
||||||
self.apply_settings()
|
|
||||||
|
|
||||||
self.location_manager = location_manager
|
|
||||||
self.location_manager.locations_changed.connect(self.build_bar)
|
|
||||||
donate.setAutoRaise(True)
|
|
||||||
donate.setCursor(Qt.PointingHandCursor)
|
|
||||||
self.added_actions = []
|
|
||||||
self.build_bar()
|
|
||||||
self.setAcceptDrops(True)
|
|
||||||
|
|
||||||
def apply_settings(self):
|
|
||||||
sz = gprefs['toolbar_icon_size']
|
|
||||||
sz = {'off':0, 'small':24, 'medium':48, 'large':64}[sz]
|
|
||||||
self.setIconSize(QSize(sz, sz))
|
|
||||||
self.child_bar.setIconSize(QSize(sz, sz))
|
|
||||||
style = Qt.ToolButtonTextUnderIcon
|
|
||||||
if sz > 0 and gprefs['toolbar_text'] == 'never':
|
|
||||||
style = Qt.ToolButtonIconOnly
|
|
||||||
self.setToolButtonStyle(style)
|
|
||||||
self.child_bar.setToolButtonStyle(style)
|
|
||||||
self.donate_button.set_normal_icon_size(sz, sz)
|
|
||||||
|
|
||||||
def build_bar(self):
|
|
||||||
self.showing_donate = False
|
|
||||||
showing_device = self.location_manager.has_device
|
|
||||||
mactions = '-device' if showing_device else ''
|
|
||||||
mactions = gprefs['action-layout-toolbar'+mactions]
|
|
||||||
cactions = gprefs['action-layout-toolbar-child']
|
|
||||||
|
|
||||||
show_main = len(mactions) > 0
|
|
||||||
self.setVisible(show_main)
|
|
||||||
show_child = len(cactions) > 0
|
|
||||||
self.child_bar.setVisible(show_child)
|
|
||||||
|
|
||||||
for ac in self.added_actions:
|
|
||||||
m = ac.menu()
|
|
||||||
if m is not None:
|
|
||||||
m.setVisible(False)
|
|
||||||
|
|
||||||
self.clear()
|
|
||||||
self.child_bar.clear()
|
|
||||||
self.added_actions = []
|
|
||||||
|
|
||||||
for bar, actions in ((self, mactions), (self.child_bar, cactions)):
|
|
||||||
for what in actions:
|
|
||||||
if what is None:
|
|
||||||
bar.addSeparator()
|
|
||||||
elif what == 'Location Manager':
|
|
||||||
for ac in self.location_manager.available_actions:
|
|
||||||
bar.addAction(ac)
|
|
||||||
bar.added_actions.append(ac)
|
|
||||||
bar.setup_tool_button(bar, ac, QToolButton.MenuButtonPopup)
|
|
||||||
elif what == 'Donate':
|
|
||||||
self.d_widget = QWidget()
|
|
||||||
self.d_widget.setLayout(QVBoxLayout())
|
|
||||||
self.d_widget.layout().addWidget(self.donate_button)
|
|
||||||
if isosx:
|
|
||||||
self.d_widget.setStyleSheet('QWidget, QToolButton {background-color: none; border: none; }')
|
|
||||||
self.d_widget.layout().setContentsMargins(0,0,0,0)
|
|
||||||
self.d_widget.setContentsMargins(0,0,0,0)
|
|
||||||
self.d_widget.filler = QLabel(u'\u00a0')
|
|
||||||
self.d_widget.layout().addWidget(self.d_widget.filler)
|
|
||||||
bar.addWidget(self.d_widget)
|
|
||||||
self.showing_donate = True
|
|
||||||
elif what in self.gui.iactions:
|
|
||||||
action = self.gui.iactions[what]
|
|
||||||
bar.addAction(action.qaction)
|
|
||||||
self.added_actions.append(action.qaction)
|
|
||||||
self.setup_tool_button(bar, action.qaction, action.popup_type)
|
|
||||||
self.preferred_width = self.sizeHint().width()
|
|
||||||
self.child_bar.preferred_width = self.child_bar.sizeHint().width()
|
|
||||||
|
|
||||||
def setup_tool_button(self, bar, ac, menu_mode=None):
|
|
||||||
ch = bar.widgetForAction(ac)
|
|
||||||
if ch is None:
|
|
||||||
ch = self.child_bar.widgetForAction(ac)
|
|
||||||
ch.setCursor(Qt.PointingHandCursor)
|
|
||||||
ch.setAutoRaise(True)
|
|
||||||
if ac.menu() is not None and menu_mode is not None:
|
|
||||||
ch.setPopupMode(menu_mode)
|
|
||||||
return ch
|
|
||||||
|
|
||||||
def database_changed(self, db):
|
|
||||||
pass
|
|
||||||
|
|
||||||
#support drag&drop from/to library from/to reader/card
|
|
||||||
def dragEnterEvent(self, event):
|
|
||||||
md = event.mimeData()
|
|
||||||
if md.hasFormat("application/calibre+from_library") or \
|
|
||||||
md.hasFormat("application/calibre+from_device"):
|
|
||||||
event.setDropAction(Qt.CopyAction)
|
|
||||||
event.accept()
|
|
||||||
else:
|
|
||||||
event.ignore()
|
|
||||||
|
|
||||||
def dragMoveEvent(self, event):
|
|
||||||
allowed = False
|
|
||||||
md = event.mimeData()
|
|
||||||
#Drop is only allowed in the location manager widget's different from the selected one
|
|
||||||
for ac in self.location_manager.available_actions:
|
|
||||||
w = self.widgetForAction(ac)
|
|
||||||
if w is not None:
|
|
||||||
if ( md.hasFormat("application/calibre+from_library") or \
|
|
||||||
md.hasFormat("application/calibre+from_device") ) and \
|
|
||||||
w.geometry().contains(event.pos()) and \
|
|
||||||
isinstance(w, QToolButton) and not w.isChecked():
|
|
||||||
allowed = True
|
|
||||||
break
|
|
||||||
if allowed:
|
|
||||||
event.acceptProposedAction()
|
|
||||||
else:
|
|
||||||
event.ignore()
|
|
||||||
|
|
||||||
def dropEvent(self, event):
|
|
||||||
data = event.mimeData()
|
|
||||||
|
|
||||||
mime = 'application/calibre+from_library'
|
|
||||||
if data.hasFormat(mime):
|
|
||||||
ids = list(map(int, str(data.data(mime)).split()))
|
|
||||||
tgt = None
|
|
||||||
for ac in self.location_manager.available_actions:
|
|
||||||
w = self.widgetForAction(ac)
|
|
||||||
if w is not None and w.geometry().contains(event.pos()):
|
|
||||||
tgt = ac.calibre_name
|
|
||||||
if tgt is not None:
|
|
||||||
if tgt == 'main':
|
|
||||||
tgt = None
|
|
||||||
self.gui.sync_to_device(tgt, False, send_ids=ids)
|
|
||||||
event.accept()
|
|
||||||
|
|
||||||
mime = 'application/calibre+from_device'
|
|
||||||
if data.hasFormat(mime):
|
|
||||||
paths = [unicode(u.toLocalFile()) for u in data.urls()]
|
|
||||||
if paths:
|
|
||||||
self.gui.iactions['Add Books'].add_books_from_device(
|
|
||||||
self.gui.current_view(), paths=paths)
|
|
||||||
event.accept()
|
|
||||||
|
|
||||||
# }}}
|
|
||||||
|
|
||||||
class MainWindowMixin(object): # {{{
|
class MainWindowMixin(object): # {{{
|
||||||
|
|
||||||
@ -507,14 +258,17 @@ class MainWindowMixin(object): # {{{
|
|||||||
self.iactions['Fetch News'].init_scheduler(db)
|
self.iactions['Fetch News'].init_scheduler(db)
|
||||||
|
|
||||||
self.search_bar = SearchBar(self)
|
self.search_bar = SearchBar(self)
|
||||||
self.child_bar = BaseToolBar(self)
|
self.bars_manager = BarsManager(self.donate_button,
|
||||||
self.tool_bar = ToolBar(self.donate_button,
|
self.location_manager, self)
|
||||||
self.location_manager, self.child_bar, self)
|
for bar in self.bars_manager.main_bars:
|
||||||
self.addToolBar(Qt.TopToolBarArea, self.tool_bar)
|
self.addToolBar(Qt.TopToolBarArea, bar)
|
||||||
self.addToolBar(Qt.BottomToolBarArea, self.child_bar)
|
for bar in self.bars_manager.child_bars:
|
||||||
self.menu_bar = MenuBar(self.location_manager, self)
|
self.addToolBar(Qt.BottomToolBarArea, bar)
|
||||||
self.setMenuBar(self.menu_bar)
|
self.bars_manager.update_bars()
|
||||||
self.setUnifiedTitleAndToolBarOnMac(True)
|
# This is disabled because it introduces various toolbar related bugs
|
||||||
|
# The width of the toolbar becomes the sum of both toolbars
|
||||||
|
if tweaks['unified_title_toolbar_on_osx']:
|
||||||
|
self.setUnifiedTitleAndToolBarOnMac(True)
|
||||||
|
|
||||||
l = self.centralwidget.layout()
|
l = self.centralwidget.layout()
|
||||||
l.addWidget(self.search_bar)
|
l.addWidget(self.search_bar)
|
||||||
|
@ -7,11 +7,12 @@ __docformat__ = 'restructuredtext en'
|
|||||||
|
|
||||||
from math import cos, sin, pi
|
from math import cos, sin, pi
|
||||||
|
|
||||||
from PyQt4.Qt import QColor, Qt, QModelIndex, QSize, \
|
from PyQt4.Qt import (QColor, Qt, QModelIndex, QSize, QApplication,
|
||||||
QPainterPath, QLinearGradient, QBrush, \
|
QPainterPath, QLinearGradient, QBrush,
|
||||||
QPen, QStyle, QPainter, QStyleOptionViewItemV4, \
|
QPen, QStyle, QPainter, QStyleOptionViewItemV4,
|
||||||
QIcon, QDoubleSpinBox, QVariant, QSpinBox, \
|
QIcon, QDoubleSpinBox, QVariant, QSpinBox,
|
||||||
QStyledItemDelegate, QComboBox, QTextDocument
|
QStyledItemDelegate, QComboBox, QTextDocument,
|
||||||
|
QAbstractTextDocumentLayout)
|
||||||
|
|
||||||
from calibre.gui2 import UNDEFINED_QDATE, error_dialog
|
from calibre.gui2 import UNDEFINED_QDATE, error_dialog
|
||||||
from calibre.gui2.widgets import EnLineEdit
|
from calibre.gui2.widgets import EnLineEdit
|
||||||
@ -27,7 +28,6 @@ from calibre.gui2.dialogs.template_dialog import TemplateDialog
|
|||||||
class RatingDelegate(QStyledItemDelegate): # {{{
|
class RatingDelegate(QStyledItemDelegate): # {{{
|
||||||
COLOR = QColor("blue")
|
COLOR = QColor("blue")
|
||||||
SIZE = 16
|
SIZE = 16
|
||||||
PEN = QPen(COLOR, 1, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)
|
|
||||||
|
|
||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
QStyledItemDelegate.__init__(self, parent)
|
QStyledItemDelegate.__init__(self, parent)
|
||||||
@ -40,10 +40,7 @@ class RatingDelegate(QStyledItemDelegate): # {{{
|
|||||||
50 + 40 * sin(0.8 * i * pi))
|
50 + 40 * sin(0.8 * i * pi))
|
||||||
self.star_path.closeSubpath()
|
self.star_path.closeSubpath()
|
||||||
self.star_path.setFillRule(Qt.WindingFill)
|
self.star_path.setFillRule(Qt.WindingFill)
|
||||||
gradient = QLinearGradient(0, 0, 0, 100)
|
self.gradient = QLinearGradient(0, 0, 0, 100)
|
||||||
gradient.setColorAt(0.0, self.COLOR)
|
|
||||||
gradient.setColorAt(1.0, self.COLOR)
|
|
||||||
self.brush = QBrush(gradient)
|
|
||||||
self.factor = self.SIZE/100.
|
self.factor = self.SIZE/100.
|
||||||
|
|
||||||
def sizeHint(self, option, index):
|
def sizeHint(self, option, index):
|
||||||
@ -53,7 +50,8 @@ class RatingDelegate(QStyledItemDelegate): # {{{
|
|||||||
def paint(self, painter, option, index):
|
def paint(self, painter, option, index):
|
||||||
style = self._parent.style()
|
style = self._parent.style()
|
||||||
option = QStyleOptionViewItemV4(option)
|
option = QStyleOptionViewItemV4(option)
|
||||||
self.initStyleOption(option, self.dummy)
|
self.initStyleOption(option, index)
|
||||||
|
option.text = u''
|
||||||
num = index.model().data(index, Qt.DisplayRole).toInt()[0]
|
num = index.model().data(index, Qt.DisplayRole).toInt()[0]
|
||||||
def draw_star():
|
def draw_star():
|
||||||
painter.save()
|
painter.save()
|
||||||
@ -70,13 +68,23 @@ class RatingDelegate(QStyledItemDelegate): # {{{
|
|||||||
painter, self._parent)
|
painter, self._parent)
|
||||||
elif option.state & QStyle.State_Selected:
|
elif option.state & QStyle.State_Selected:
|
||||||
painter.fillRect(option.rect, option.palette.highlight())
|
painter.fillRect(option.rect, option.palette.highlight())
|
||||||
|
else:
|
||||||
|
painter.fillRect(option.rect, option.backgroundBrush)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
painter.setRenderHint(QPainter.Antialiasing)
|
painter.setRenderHint(QPainter.Antialiasing)
|
||||||
painter.setClipRect(option.rect)
|
painter.setClipRect(option.rect)
|
||||||
y = option.rect.center().y()-self.SIZE/2.
|
y = option.rect.center().y()-self.SIZE/2.
|
||||||
x = option.rect.left()
|
x = option.rect.left()
|
||||||
painter.setPen(self.PEN)
|
color = index.data(Qt.ForegroundRole)
|
||||||
painter.setBrush(self.brush)
|
if color.isNull() or not color.isValid():
|
||||||
|
color = self.COLOR
|
||||||
|
else:
|
||||||
|
color = QColor(color)
|
||||||
|
painter.setPen(QPen(color, 1, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
|
||||||
|
self.gradient.setColorAt(0.0, color)
|
||||||
|
self.gradient.setColorAt(1.0, color)
|
||||||
|
painter.setBrush(QBrush(self.gradient))
|
||||||
painter.translate(x, y)
|
painter.translate(x, y)
|
||||||
i = 0
|
i = 0
|
||||||
while i < num:
|
while i < num:
|
||||||
@ -311,18 +319,22 @@ class CcCommentsDelegate(QStyledItemDelegate): # {{{
|
|||||||
self.document = QTextDocument()
|
self.document = QTextDocument()
|
||||||
|
|
||||||
def paint(self, painter, option, index):
|
def paint(self, painter, option, index):
|
||||||
style = self.parent().style()
|
self.initStyleOption(option, index)
|
||||||
self.document.setHtml(index.data(Qt.DisplayRole).toString())
|
style = QApplication.style() if option.widget is None \
|
||||||
painter.save()
|
else option.widget.style()
|
||||||
|
self.document.setHtml(option.text)
|
||||||
|
option.text = u''
|
||||||
if hasattr(QStyle, 'CE_ItemViewItem'):
|
if hasattr(QStyle, 'CE_ItemViewItem'):
|
||||||
style.drawControl(QStyle.CE_ItemViewItem, option,
|
style.drawControl(QStyle.CE_ItemViewItem, option, painter)
|
||||||
painter, self.parent())
|
ctx = QAbstractTextDocumentLayout.PaintContext()
|
||||||
elif option.state & QStyle.State_Selected:
|
ctx.palette = option.palette #.setColor(QPalette.Text, QColor("red"));
|
||||||
painter.fillRect(option.rect, option.palette.highlight())
|
if hasattr(QStyle, 'SE_ItemViewItemText'):
|
||||||
painter.setClipRect(option.rect)
|
textRect = style.subElementRect(QStyle.SE_ItemViewItemText, option)
|
||||||
painter.translate(option.rect.topLeft())
|
painter.save()
|
||||||
self.document.drawContents(painter)
|
painter.translate(textRect.topLeft())
|
||||||
painter.restore()
|
painter.setClipRect(textRect.translated(-textRect.topLeft()))
|
||||||
|
self.document.documentLayout().draw(painter, ctx)
|
||||||
|
painter.restore()
|
||||||
|
|
||||||
def createEditor(self, parent, option, index):
|
def createEditor(self, parent, option, index):
|
||||||
m = index.model()
|
m = index.model()
|
||||||
|
@ -14,6 +14,7 @@ from PyQt4.Qt import (QAbstractTableModel, Qt, pyqtSignal, QIcon, QImage,
|
|||||||
from calibre.gui2 import NONE, UNDEFINED_QDATE
|
from calibre.gui2 import NONE, UNDEFINED_QDATE
|
||||||
from calibre.utils.pyparsing import ParseException
|
from calibre.utils.pyparsing import ParseException
|
||||||
from calibre.ebooks.metadata import fmt_sidx, authors_to_string, string_to_authors
|
from calibre.ebooks.metadata import fmt_sidx, authors_to_string, string_to_authors
|
||||||
|
from calibre.ebooks.metadata.book.base import composite_formatter
|
||||||
from calibre.ptempfile import PersistentTemporaryFile
|
from calibre.ptempfile import PersistentTemporaryFile
|
||||||
from calibre.utils.config import tweaks, prefs
|
from calibre.utils.config import tweaks, prefs
|
||||||
from calibre.utils.date import dt_factory, qt_to_dt
|
from calibre.utils.date import dt_factory, qt_to_dt
|
||||||
@ -96,6 +97,8 @@ class BooksModel(QAbstractTableModel): # {{{
|
|||||||
self.ids_to_highlight_set = set()
|
self.ids_to_highlight_set = set()
|
||||||
self.current_highlighted_idx = None
|
self.current_highlighted_idx = None
|
||||||
self.highlight_only = False
|
self.highlight_only = False
|
||||||
|
self.column_color_map = {}
|
||||||
|
self.colors = [unicode(c) for c in QColor.colorNames()]
|
||||||
self.read_config()
|
self.read_config()
|
||||||
|
|
||||||
def change_alignment(self, colname, alignment):
|
def change_alignment(self, colname, alignment):
|
||||||
@ -151,6 +154,7 @@ class BooksModel(QAbstractTableModel): # {{{
|
|||||||
self.headers[col] = self.custom_columns[col]['name']
|
self.headers[col] = self.custom_columns[col]['name']
|
||||||
|
|
||||||
self.build_data_convertors()
|
self.build_data_convertors()
|
||||||
|
self.set_color_templates(reset=False)
|
||||||
self.reset()
|
self.reset()
|
||||||
self.database_changed.emit(db)
|
self.database_changed.emit(db)
|
||||||
self.stop_metadata_backup()
|
self.stop_metadata_backup()
|
||||||
@ -532,6 +536,15 @@ class BooksModel(QAbstractTableModel): # {{{
|
|||||||
img = self.default_image
|
img = self.default_image
|
||||||
return img
|
return img
|
||||||
|
|
||||||
|
def set_color_templates(self, reset=True):
|
||||||
|
self.column_color_map = {}
|
||||||
|
for i in range(1,self.db.column_color_count+1):
|
||||||
|
name = self.db.prefs.get('column_color_name_'+str(i))
|
||||||
|
if name:
|
||||||
|
self.column_color_map[name] = \
|
||||||
|
self.db.prefs.get('column_color_template_'+str(i))
|
||||||
|
if reset:
|
||||||
|
self.reset()
|
||||||
|
|
||||||
def build_data_convertors(self):
|
def build_data_convertors(self):
|
||||||
def authors(r, idx=-1):
|
def authors(r, idx=-1):
|
||||||
@ -693,9 +706,36 @@ class BooksModel(QAbstractTableModel): # {{{
|
|||||||
return NONE
|
return NONE
|
||||||
if role in (Qt.DisplayRole, Qt.EditRole):
|
if role in (Qt.DisplayRole, Qt.EditRole):
|
||||||
return self.column_to_dc_map[col](index.row())
|
return self.column_to_dc_map[col](index.row())
|
||||||
elif role == Qt.BackgroundColorRole:
|
elif role == Qt.BackgroundRole:
|
||||||
if self.id(index) in self.ids_to_highlight_set:
|
if self.id(index) in self.ids_to_highlight_set:
|
||||||
return QColor('lightgreen')
|
return QVariant(QColor('lightgreen'))
|
||||||
|
elif role == Qt.ForegroundRole:
|
||||||
|
key = self.column_map[col]
|
||||||
|
if key in self.column_color_map:
|
||||||
|
mi = self.db.get_metadata(self.id(index), index_is_id=True)
|
||||||
|
fmt = self.column_color_map[key]
|
||||||
|
try:
|
||||||
|
color = composite_formatter.safe_format(fmt, mi, '', mi)
|
||||||
|
if color in self.colors:
|
||||||
|
color = QColor(color)
|
||||||
|
if color.isValid():
|
||||||
|
return QVariant(color)
|
||||||
|
except:
|
||||||
|
return NONE
|
||||||
|
elif self.is_custom_column(key) and \
|
||||||
|
self.custom_columns[key]['datatype'] == 'enumeration':
|
||||||
|
cc = self.custom_columns[self.column_map[col]]['display']
|
||||||
|
colors = cc.get('enum_colors', [])
|
||||||
|
values = cc.get('enum_values', [])
|
||||||
|
txt = unicode(index.data(Qt.DisplayRole).toString())
|
||||||
|
if len(colors) > 0 and txt in values:
|
||||||
|
try:
|
||||||
|
color = QColor(colors[values.index(txt)])
|
||||||
|
if color.isValid():
|
||||||
|
return QVariant(color)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return NONE
|
||||||
elif role == Qt.DecorationRole:
|
elif role == Qt.DecorationRole:
|
||||||
if self.column_to_dc_decorator_map[col] is not None:
|
if self.column_to_dc_decorator_map[col] is not None:
|
||||||
return self.column_to_dc_decorator_map[index.column()](index.row())
|
return self.column_to_dc_decorator_map[index.column()](index.row())
|
||||||
|
@ -5,7 +5,7 @@ import sys, logging, os, traceback, time
|
|||||||
from PyQt4.QtGui import QKeySequence, QPainter, QDialog, QSpinBox, QSlider, QIcon
|
from PyQt4.QtGui import QKeySequence, QPainter, QDialog, QSpinBox, QSlider, QIcon
|
||||||
from PyQt4.QtCore import Qt, QObject, SIGNAL, QCoreApplication, QThread
|
from PyQt4.QtCore import Qt, QObject, SIGNAL, QCoreApplication, QThread
|
||||||
|
|
||||||
from calibre import __appname__, setup_cli_handlers, islinux, isfreebsd
|
from calibre import __appname__, setup_cli_handlers, islinux, isbsd
|
||||||
from calibre.ebooks.lrf.lrfparser import LRFDocument
|
from calibre.ebooks.lrf.lrfparser import LRFDocument
|
||||||
|
|
||||||
from calibre.gui2 import ORG_NAME, APP_UID, error_dialog, \
|
from calibre.gui2 import ORG_NAME, APP_UID, error_dialog, \
|
||||||
@ -258,7 +258,7 @@ def file_renderer(stream, opts, parent=None, logger=None):
|
|||||||
level = logging.DEBUG if opts.verbose else logging.INFO
|
level = logging.DEBUG if opts.verbose else logging.INFO
|
||||||
logger = logging.getLogger('lrfviewer')
|
logger = logging.getLogger('lrfviewer')
|
||||||
setup_cli_handlers(logger, level)
|
setup_cli_handlers(logger, level)
|
||||||
if islinux or isfreebsd:
|
if islinux or isbsd:
|
||||||
try: # Set lrfviewer as the default for LRF files for this user
|
try: # Set lrfviewer as the default for LRF files for this user
|
||||||
from subprocess import call
|
from subprocess import call
|
||||||
call('xdg-mime default calibre-lrfviewer.desktop application/lrf', shell=True)
|
call('xdg-mime default calibre-lrfviewer.desktop application/lrf', shell=True)
|
||||||
@ -307,7 +307,7 @@ def main(args=sys.argv, logger=None):
|
|||||||
if hasattr(opts, 'help'):
|
if hasattr(opts, 'help'):
|
||||||
parser.print_help()
|
parser.print_help()
|
||||||
return 1
|
return 1
|
||||||
pid = os.fork() if (islinux or isfreebsd) else -1
|
pid = os.fork() if (islinux or isbsd) else -1
|
||||||
if pid <= 0:
|
if pid <= 0:
|
||||||
app = Application(args)
|
app = Application(args)
|
||||||
app.setWindowIcon(QIcon(I('viewer.png')))
|
app.setWindowIcon(QIcon(I('viewer.png')))
|
||||||
|
@ -88,11 +88,22 @@ class TitleEdit(EnLineEdit):
|
|||||||
|
|
||||||
def commit(self, db, id_):
|
def commit(self, db, id_):
|
||||||
title = self.current_val
|
title = self.current_val
|
||||||
if self.COMMIT:
|
try:
|
||||||
getattr(db, 'set_'+ self.TITLE_ATTR)(id_, title, notify=False)
|
if self.COMMIT:
|
||||||
else:
|
getattr(db, 'set_'+ self.TITLE_ATTR)(id_, title, notify=False)
|
||||||
getattr(db, 'set_'+ self.TITLE_ATTR)(id_, title, notify=False,
|
else:
|
||||||
commit=False)
|
getattr(db, 'set_'+ self.TITLE_ATTR)(id_, title, notify=False,
|
||||||
|
commit=False)
|
||||||
|
except (IOError, OSError) as err:
|
||||||
|
if getattr(err, 'errno', -1) == 13: # Permission denied
|
||||||
|
import traceback
|
||||||
|
fname = err.filename if err.filename else 'file'
|
||||||
|
error_dialog(self, _('Permission denied'),
|
||||||
|
_('Could not open %s. Is it being used by another'
|
||||||
|
' program?')%fname, det_msg=traceback.format_exc(),
|
||||||
|
show=True)
|
||||||
|
return False
|
||||||
|
raise
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@dynamic_property
|
@dynamic_property
|
||||||
@ -225,8 +236,19 @@ class AuthorsEdit(MultiCompleteComboBox):
|
|||||||
|
|
||||||
def commit(self, db, id_):
|
def commit(self, db, id_):
|
||||||
authors = self.current_val
|
authors = self.current_val
|
||||||
self.books_to_refresh |= db.set_authors(id_, authors, notify=False,
|
try:
|
||||||
|
self.books_to_refresh |= db.set_authors(id_, authors, notify=False,
|
||||||
allow_case_change=True)
|
allow_case_change=True)
|
||||||
|
except (IOError, OSError) as err:
|
||||||
|
if getattr(err, 'errno', -1) == 13: # Permission denied
|
||||||
|
import traceback
|
||||||
|
fname = err.filename if err.filename else 'file'
|
||||||
|
error_dialog(self, _('Permission denied'),
|
||||||
|
_('Could not open %s. Is it being used by another'
|
||||||
|
' program?')%fname, det_msg=traceback.format_exc(),
|
||||||
|
show=True)
|
||||||
|
return False
|
||||||
|
raise
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@dynamic_property
|
@dynamic_property
|
||||||
|
@ -6,7 +6,7 @@ __copyright__ = '2010, Kovid Goyal <kovid at kovidgoyal.net>'
|
|||||||
import re
|
import re
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
|
||||||
from PyQt4.Qt import QDialog, Qt, QListWidgetItem, QVariant
|
from PyQt4.Qt import QDialog, Qt, QListWidgetItem, QVariant, QColor
|
||||||
|
|
||||||
from calibre.gui2.preferences.create_custom_column_ui import Ui_QCreateCustomColumn
|
from calibre.gui2.preferences.create_custom_column_ui import Ui_QCreateCustomColumn
|
||||||
from calibre.gui2 import error_dialog
|
from calibre.gui2 import error_dialog
|
||||||
@ -126,11 +126,15 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn):
|
|||||||
c['display'].get('make_category', False))
|
c['display'].get('make_category', False))
|
||||||
elif ct == 'enumeration':
|
elif ct == 'enumeration':
|
||||||
self.enum_box.setText(','.join(c['display'].get('enum_values', [])))
|
self.enum_box.setText(','.join(c['display'].get('enum_values', [])))
|
||||||
|
self.enum_colors.setText(','.join(c['display'].get('enum_colors', [])))
|
||||||
self.datatype_changed()
|
self.datatype_changed()
|
||||||
if ct in ['text', 'composite', 'enumeration']:
|
if ct in ['text', 'composite', 'enumeration']:
|
||||||
self.use_decorations.setChecked(c['display'].get('use_decorations', False))
|
self.use_decorations.setChecked(c['display'].get('use_decorations', False))
|
||||||
elif ct == '*text':
|
elif ct == '*text':
|
||||||
self.is_names.setChecked(c['display'].get('is_names', False))
|
self.is_names.setChecked(c['display'].get('is_names', False))
|
||||||
|
|
||||||
|
all_colors = [unicode(s) for s in list(QColor.colorNames())]
|
||||||
|
self.enum_colors_label.setToolTip('<p>' + ', '.join(all_colors) + '</p>')
|
||||||
self.exec_()
|
self.exec_()
|
||||||
|
|
||||||
def shortcut_activated(self, url):
|
def shortcut_activated(self, url):
|
||||||
@ -170,7 +174,7 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn):
|
|||||||
for x in ('box', 'default_label', 'label', 'sort_by', 'sort_by_label',
|
for x in ('box', 'default_label', 'label', 'sort_by', 'sort_by_label',
|
||||||
'make_category'):
|
'make_category'):
|
||||||
getattr(self, 'composite_'+x).setVisible(col_type in ['composite', '*composite'])
|
getattr(self, 'composite_'+x).setVisible(col_type in ['composite', '*composite'])
|
||||||
for x in ('box', 'default_label', 'label'):
|
for x in ('box', 'default_label', 'label', 'colors', 'colors_label'):
|
||||||
getattr(self, 'enum_'+x).setVisible(col_type == 'enumeration')
|
getattr(self, 'enum_'+x).setVisible(col_type == 'enumeration')
|
||||||
self.use_decorations.setVisible(col_type in ['text', 'composite', 'enumeration'])
|
self.use_decorations.setVisible(col_type in ['text', 'composite', 'enumeration'])
|
||||||
self.is_names.setVisible(col_type == '*text')
|
self.is_names.setVisible(col_type == '*text')
|
||||||
@ -247,7 +251,20 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn):
|
|||||||
if l[i] in l[i+1:]:
|
if l[i] in l[i+1:]:
|
||||||
return self.simple_error('', _('The value "{0}" is in the '
|
return self.simple_error('', _('The value "{0}" is in the '
|
||||||
'list more than once').format(l[i]))
|
'list more than once').format(l[i]))
|
||||||
display_dict = {'enum_values': l}
|
c = unicode(self.enum_colors.text())
|
||||||
|
if c:
|
||||||
|
c = [v.strip() for v in unicode(self.enum_colors.text()).split(',')]
|
||||||
|
else:
|
||||||
|
c = []
|
||||||
|
if len(c) != 0 and len(c) != len(l):
|
||||||
|
return self.simple_error('', _('The colors box must be empty or '
|
||||||
|
'contain the same number of items as the value box'))
|
||||||
|
for tc in c:
|
||||||
|
if tc not in QColor.colorNames():
|
||||||
|
return self.simple_error('',
|
||||||
|
_('The color {0} is unknown').format(tc))
|
||||||
|
|
||||||
|
display_dict = {'enum_values': l, 'enum_colors': c}
|
||||||
elif col_type == 'text' and is_multiple:
|
elif col_type == 'text' and is_multiple:
|
||||||
display_dict = {'is_names': self.is_names.isChecked()}
|
display_dict = {'is_names': self.is_names.isChecked()}
|
||||||
|
|
||||||
|
@ -304,8 +304,8 @@ Everything else will show nothing.</string>
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="6" column="2">
|
<item row="6" column="2">
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
<layout class="QGridLayout" name="horizontalLayout_2">
|
||||||
<item>
|
<item row="0" column="0">
|
||||||
<widget class="QLineEdit" name="enum_box">
|
<widget class="QLineEdit" name="enum_box">
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||||
@ -320,13 +320,34 @@ four values, the first of them being the empty value.</string>
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item row="0" column="1">
|
||||||
<widget class="QLabel" name="enum_default_label">
|
<widget class="QLabel" name="enum_default_label">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string>The empty string is always the first value</string>
|
<string>The empty string is always the first value</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Default: (nothing)</string>
|
<string>Values</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QLineEdit" name="enum_colors">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>A list of color names to use when displaying an item. The
|
||||||
|
list must be empty or contain a color for each value.</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<widget class="QLabel" name="enum_colors_label">
|
||||||
|
<property name="text">
|
||||||
|
<string>Colors</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
@ -76,7 +76,7 @@ class UserDefinedDevice(QDialog):
|
|||||||
for i,d in enumerate(sorted(new_drives,
|
for i,d in enumerate(sorted(new_drives,
|
||||||
key=lambda x: after['drive_details'][x][0])):
|
key=lambda x: after['drive_details'][x][0])):
|
||||||
if i == 0:
|
if i == 0:
|
||||||
res += _('Windows main memory ID string') + ': ' + \
|
res += _('Windows main memory vendor string') + ': ' + \
|
||||||
after['drive_details'][d][1] + '\n'
|
after['drive_details'][d][1] + '\n'
|
||||||
res += _('Windows main memory ID string') + ': ' + \
|
res += _('Windows main memory ID string') + ': ' + \
|
||||||
after['drive_details'][d][2] + '\n'
|
after['drive_details'][d][2] + '\n'
|
||||||
|