Merge from trunk

This commit is contained in:
Charles Haley 2011-05-27 13:29:06 +01:00
commit 5fdf11466f
16 changed files with 438 additions and 162 deletions

View 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')]

View File

@ -3,10 +3,7 @@ class AdvancedUserRecipe1303841067(BasicNewsRecipe):
title = u'Faz.net'
__author__ = 'schuster'
remove_tags = [dict(attrs={'class':['right', 'ArrowLinkRight', 'ModulVerlagsInfo', 'left', 'Head']}),
dict(id=['BreadCrumbs', 'tstag', 'FazFooterPrint']),
dict(name=['script', 'noscript', 'style'])]
oldest_article = 2
oldest_article = 1
description = 'Frankfurter Allgemeine Zeitung'
max_articles_per_feed = 100
no_stylesheets = True
@ -15,9 +12,9 @@ class AdvancedUserRecipe1303841067(BasicNewsRecipe):
remove_javascript = True
cover_url = 'http://www.faz.net/f30/Images/Logos/logo.gif'
def print_version(self, url):
return url.replace('.html', '~Afor~Eprint.html')
remove_tags = [dict(attrs={'class':['LinkBoxModulSmall', 'ModulLesermeinungenFooter', 'ModulArtikelServices', 'SocialMediaUnten', 'ArrowLinkRight', 'ModulVerlagsInfo', 'AdData', 'FazFooter', 'Date']}),
dict(id=['FAZNavHeader', 'FAZNavMain', 'RightColumn', 'FazFooter', 'BreadCrumbs', 'FAZNavSubMain', 'FAZImgEvent']),
dict(name=['jksrdt'])]
feeds = [(u'Politik', u'http://www.faz.net/s/RubA24ECD630CAE40E483841DB7D16F4211/Tpl~Epartner~SRss_.xml'),

View 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')

View 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'),
]

View File

@ -1,19 +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",
"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",
"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",
"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",
"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",
"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",
"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",
"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",
"field": "def evaluate(self, formatter, kwargs, mi, locals, name):\n return formatter.get_value(name, [], kwargs)\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",
"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",
"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",
"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",
@ -23,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",
"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",
"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",
"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",
"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",
"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",
"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",
"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",
"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"
}

View File

@ -594,7 +594,7 @@ from calibre.devices.iliad.driver import ILIAD
from calibre.devices.irexdr.driver import IREXDR1000, IREXDR800
from calibre.devices.jetbook.driver import JETBOOK, MIBUK, JETBOOK_MINI
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.user_defined.driver import USER_DEFINED
from calibre.devices.android.driver import ANDROID, S60
@ -693,8 +693,7 @@ plugins += [
KINDLE,
KINDLE2,
KINDLE_DX,
NOOK,
NOOK_COLOR,
NOOK, NOOK_COLOR, NOOK_TSR,
PRS505,
ANDROID,
S60,
@ -1346,6 +1345,16 @@ class StoreSmashwordsStore(StoreBase):
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):
name = 'Waterstones UK'
author = 'Charles Haley'
@ -1376,7 +1385,7 @@ class StoreWizardsTowerBooksStore(StoreBase):
class StoreWoblinkStore(StoreBase):
name = 'Woblink'
author = 'Tomasz Długosz'
author = u'Tomasz Długosz'
description = u'Czytanie zdarza się wszędzie!'
actual_plugin = 'calibre.gui2.store.woblink_plugin:WoblinkStore'
@ -1411,6 +1420,7 @@ plugins += [
StoreOReillyStore,
StorePragmaticBookshelfStore,
StoreSmashwordsStore,
StoreVirtualoStore,
StoreWaterstonesUKStore,
StoreWeightlessBooksStore,
StoreWizardsTowerBooksStore,

View File

@ -107,3 +107,13 @@ class NOOK_COLOR(NOOK):
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'

View File

@ -297,9 +297,11 @@ class MobiMLizer(object):
if id_:
# Keep anchors so people can use display:none
# to generate hidden TOCs
tail = elem.tail
elem.clear()
elem.text = None
elem.set('id', id_)
elem.tail = tail
else:
return
tag = barename(elem.tag)
@ -309,7 +311,8 @@ class MobiMLizer(object):
istates.append(istate)
left = 0
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 tag != 'br'
if isblock:

View File

@ -44,7 +44,7 @@ class StoreAction(InterfaceAction):
def search(self, query=''):
self.show_disclaimer()
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_()
def _get_selected_row(self):

View File

@ -207,8 +207,9 @@ class SchedulerDialog(QDialog, Ui_Dialog):
self.recipe_model.searched.connect(self.search.search_done,
type=Qt.QueuedConnection)
self.recipe_model.searched.connect(self.search_done)
self.search.setFocus(Qt.OtherFocusReason)
self.recipes.setFocus(Qt.OtherFocusReason)
self.commit_on_change = True
self.previous_urn = None
self.recipes.setModel(self.recipe_model)
self.detail_box.setVisible(False)
@ -228,6 +229,9 @@ class SchedulerDialog(QDialog, Ui_Dialog):
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):
self.password.setEchoMode(self.password.Normal
if state == Qt.Checked else self.password.Password)
@ -265,14 +269,9 @@ class SchedulerDialog(QDialog, Ui_Dialog):
self.last_downloaded.setVisible(enabled)
def current_changed(self, current, previous):
if self.commit_on_change:
if previous.isValid():
if not self.commit(urn=getattr(previous.internalPointer(),
'urn', None)):
self.commit_on_change = False
self.recipes.setCurrentIndex(previous)
else:
self.commit_on_change = True
if self.previous_urn is not None:
self.commit(urn=self.previous_urn)
self.previous_urn = None
urn = self.current_urn
if urn is not None:
@ -332,6 +331,7 @@ class SchedulerDialog(QDialog, Ui_Dialog):
return True
def initialize_detail_box(self, urn):
self.previous_urn = urn
self.detail_box.setVisible(True)
self.download_button.setVisible(True)
self.detail_box.setCurrentIndex(0)

View File

@ -17,21 +17,30 @@
<iconset resource="../../../../resources/images.qrc">
<normaloff>:/images/scheduler.png</normaloff>:/images/scheduler.png</iconset>
</property>
<layout class="QGridLayout" name="gridLayout" columnstretch="0,1,2">
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="label_8">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="SearchBox2" name="search"/>
</item>
<item>
<widget class="QToolButton" name="go_button">
<property name="text">
<string>&amp;Search:</string>
</property>
<property name="buddy">
<cstring>search</cstring>
<string>Go</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="SearchBox2" name="search"/>
<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>
<item row="0" column="2" rowspan="3">
</layout>
</item>
<item row="0" column="1" rowspan="2">
<widget class="QScrollArea" name="scrollArea">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
@ -44,7 +53,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>486</width>
<width>524</width>
<height>504</height>
</rect>
</property>
@ -320,7 +329,7 @@
</widget>
</widget>
</item>
<item row="1" column="0" colspan="2">
<item row="1" column="0">
<widget class="QTreeView" name="recipes">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Expanding">
@ -345,7 +354,17 @@
</property>
</widget>
</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">
<item>
<widget class="QLabel" name="label_7">
@ -376,17 +395,7 @@
</item>
</layout>
</item>
<item row="4" column="2">
<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">
<item row="3" column="0">
<widget class="QPushButton" name="download_all_button">
<property name="toolTip">
<string>Download all scheduled news sources at once</string>
@ -394,15 +403,19 @@
<property name="text">
<string>Download &amp;all scheduled</string>
</property>
<property name="icon">
<iconset resource="../../../../resources/images.qrc">
<normaloff>:/images/news.png</normaloff>:/images/news.png</iconset>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QLabel" name="count_label">
<property name="text">
<string/>
<item row="3" column="1">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
<property name="standardButtons">
<set>QDialogButtonBox::Save</set>
</property>
</widget>
</item>

View File

@ -103,7 +103,22 @@ class Matches(QAbstractItemModel):
return Qt.Unchecked
return Qt.Checked
elif role == Qt.ToolTipRole:
if col == 0:
if is_disabled(result):
return QVariant(_('<p>This store is currently diabled and cannot be used in other parts of calibre.</p>'))
else:
return QVariant(_('<p>This store is currently enabled and can be used in other parts of calibre.</p>'))
elif col == 1:
return QVariant('<p>%s</p>' % result.description)
elif col == 2:
if result.drm_free_only:
return QVariant(_('<p>This store only distributes ebooks with DRM.</p>'))
else:
return QVariant(_('<p>This store distributes ebooks with DRM. It may have some titles without DRM, but you will need to check on a per title basis.</p>'))
elif col == 3:
return QVariant(_('<p>This store is headquartered in %s. This is a good indication of what market the store caters to. However, this does not necessarily mean that the store is limited to that market only.</p>') % result.headquarters)
elif col == 4:
return QVariant(_('<p>This store distributes ebooks in the following formats: %s</p>') % ', '.join(result.formats))
return NONE
def setData(self, index, data, role):
@ -130,7 +145,7 @@ class Matches(QAbstractItemModel):
elif col == 1:
text = match.name
elif col == 2:
text = 'b' if getattr(match, 'drm', True) else 'a'
text = 'a' if getattr(match, 'drm_free_only', True) else 'b'
elif col == 3:
text = getattr(match, 'headquarters', '')
return text
@ -240,5 +255,3 @@ class SearchFilter(SearchQueryParser):
import traceback
traceback.print_exc()
return matches

View File

@ -10,10 +10,11 @@ import re
from random import shuffle
from PyQt4.Qt import (Qt, QDialog, QDialogButtonBox, QTimer, QCheckBox,
QVBoxLayout, QIcon, QWidget)
QVBoxLayout, QIcon, QWidget, QTabWidget)
from calibre.gui2 import JSONConfig, info_dialog
from calibre.gui2.progress_indicator import ProgressIndicator
from calibre.gui2.store.config.chooser.chooser_widget import StoreChooserWidget
from calibre.gui2.store.config.search.search_widget import StoreConfigWidget
from calibre.gui2.store.search.adv_search_builder import AdvSearchBuilderDialog
from calibre.gui2.store.search.download_thread import SearchThreadPool, \
@ -22,7 +23,7 @@ from calibre.gui2.store.search.search_ui import Ui_Dialog
class SearchDialog(QDialog, Ui_Dialog):
def __init__(self, istores, parent=None, query=''):
def __init__(self, gui, parent=None, query=''):
QDialog.__init__(self, parent)
self.setupUi(self)
@ -34,8 +35,7 @@ class SearchDialog(QDialog, Ui_Dialog):
# the variables it sets up are used later.
self.load_settings()
# We keep a cache of store plugins and reference them by name.
self.store_plugins = istores
self.gui = gui
# Setup our worker threads.
self.search_pool = SearchThreadPool(self.search_thread_count)
@ -49,22 +49,11 @@ class SearchDialog(QDialog, Ui_Dialog):
self.hang_check = 0
# Update store caches silently.
for p in self.store_plugins.values():
for p in self.gui.istores.values():
self.cache_pool.add_task(p, self.timeout)
# Add check boxes for each store so the user
# can disable searching specific stores on a
# per search basis.
stores_check_widget = QWidget()
store_list_layout = QVBoxLayout()
stores_check_widget.setLayout(store_list_layout)
for x in sorted(self.store_plugins.keys(), key=lambda x: x.lower()):
cbox = QCheckBox(x)
cbox.setChecked(False)
store_list_layout.addWidget(cbox)
setattr(self, 'store_check_' + x, cbox)
store_list_layout.addStretch()
self.store_list.setWidget(stores_check_widget)
self.store_checks = {}
self.setup_store_checks()
# Set the search query
self.search_edit.setText(query)
@ -92,6 +81,27 @@ class SearchDialog(QDialog, Ui_Dialog):
self.restore_state()
def setup_store_checks(self):
# Add check boxes for each store so the user
# can disable searching specific stores on a
# per search basis.
existing = {}
for n in self.store_checks:
existing[n] = self.store_checks[n].isChecked()
self.store_checks = {}
stores_check_widget = QWidget()
store_list_layout = QVBoxLayout()
stores_check_widget.setLayout(store_list_layout)
for x in sorted(self.gui.istores.keys(), key=lambda x: x.lower()):
cbox = QCheckBox(x)
cbox.setChecked(existing.get(x, False))
store_list_layout.addWidget(cbox)
self.store_checks[x] = cbox
store_list_layout.addStretch()
self.store_list.setWidget(stores_check_widget)
def build_adv_search(self):
adv = AdvSearchBuilderDialog(self)
if adv.exec_() == QDialog.Accepted:
@ -126,11 +136,12 @@ class SearchDialog(QDialog, Ui_Dialog):
# futher filtering.
self.results_view.model().set_query(query)
# Plugins are in alphebetic order. Randomize the
# order of plugin names. This way plugins closer
# Plugins are in random order that does not change.
# Randomize the ord of the plugin names every time
# there is a search. This way plugins closer
# to a don't have an unfair advantage over
# plugins further from a.
store_names = self.store_plugins.keys()
store_names = self.store_checks.keys()
if not store_names:
return
# Remove all of our internal filtering logic from the query.
@ -138,8 +149,8 @@ class SearchDialog(QDialog, Ui_Dialog):
shuffle(store_names)
# Add plugins that the user has checked to the search pool's work queue.
for n in store_names:
if getattr(self, 'store_check_' + n).isChecked():
self.search_pool.add_task(query, n, self.store_plugins[n], self.max_results, self.timeout)
if self.store_checks[n].isChecked():
self.search_pool.add_task(query, n, self.gui.istores[n], self.max_results, self.timeout)
self.hang_check = 0
self.checker.start(100)
self.pi.startAnimation()
@ -179,8 +190,8 @@ class SearchDialog(QDialog, Ui_Dialog):
self.config['open_external'] = self.open_external.isChecked()
store_check = {}
for n in self.store_plugins:
store_check[n] = getattr(self, 'store_check_' + n).isChecked()
for k, v in self.store_checks.items():
store_check[k] = v.isChecked()
self.config['store_checked'] = store_check
def restore_state(self):
@ -206,8 +217,8 @@ class SearchDialog(QDialog, Ui_Dialog):
store_check = self.config.get('store_checked', None)
if store_check:
for n in store_check:
if hasattr(self, 'store_check_' + n):
getattr(self, 'store_check_' + n).setChecked(store_check[n])
if n in self.store_checks:
self.store_checks[n].setChecked(store_check[n])
self.results_view.model().sort_col = self.config.get('sort_col', 2)
self.results_view.model().sort_order = self.config.get('sort_order', Qt.AscendingOrder)
@ -234,20 +245,27 @@ class SearchDialog(QDialog, Ui_Dialog):
self.config['open_external'] = self.open_external.isChecked()
d = QDialog(self)
button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
button_box = QDialogButtonBox(QDialogButtonBox.Close)
v = QVBoxLayout(d)
button_box.accepted.connect(d.accept)
button_box.rejected.connect(d.reject)
d.setWindowTitle(_('Customize get books search'))
config_widget = StoreConfigWidget(self.config)
v.addWidget(config_widget)
tab_widget = QTabWidget(d)
v.addWidget(tab_widget)
v.addWidget(button_box)
d.exec_()
chooser_config_widget = StoreChooserWidget()
search_config_widget = StoreConfigWidget(self.config)
if d.result() == QDialog.Accepted:
config_widget.save_settings()
tab_widget.addTab(chooser_config_widget, _('Choose stores'))
tab_widget.addTab(search_config_widget, _('Configure search'))
d.exec_()
search_config_widget.save_settings()
self.config_changed()
self.gui.load_store_plugins()
self.setup_store_checks()
def config_changed(self):
self.load_settings()
@ -283,7 +301,7 @@ class SearchDialog(QDialog, Ui_Dialog):
def open_store(self, index):
result = self.results_view.model().get_result(index)
self.store_plugins[result.store_name].open(self, result.detail_item, self.open_external.isChecked())
self.gui.istores[result.store_name].open(self, result.detail_item, self.open_external.isChecked())
def check_progress(self):
if not self.search_pool.threads_running() and not self.results_view.model().cover_pool.threads_running() and not self.results_view.model().details_pool.threads_running():
@ -292,27 +310,16 @@ class SearchDialog(QDialog, Ui_Dialog):
if not self.pi.isAnimated():
self.pi.startAnimation()
def get_store_checks(self):
'''
Returns a list of QCheckBox's for each store.
'''
checks = []
for x in self.store_plugins:
check = getattr(self, 'store_check_' + x, None)
if check:
checks.append(check)
return checks
def stores_select_all(self):
for check in self.get_store_checks():
for check in self.store_checks.values():
check.setChecked(True)
def stores_select_invert(self):
for check in self.get_store_checks():
for check in self.store_checks.values():
check.setChecked(not check.isChecked())
def stores_select_none(self):
for check in self.get_store_checks():
for check in self.store_checks.values():
check.setChecked(False)
def dialog_closed(self, result):

View File

@ -0,0 +1,72 @@
# -*- coding: utf-8 -*-
from __future__ import (unicode_literals, division, absolute_import, print_function)
__license__ = 'GPL 3'
__copyright__ = '2011, Tomasz Długosz <tomek3d@gmail.com>'
__docformat__ = 'restructuredtext en'
import re
import urllib
from contextlib import closing
from lxml import html
from PyQt4.Qt import QUrl
from calibre import browser, url_slash_cleaner
from calibre.gui2 import open_url
from calibre.gui2.store import StorePlugin
from calibre.gui2.store.basic_config import BasicStoreConfig
from calibre.gui2.store.search_result import SearchResult
from calibre.gui2.store.web_store_dialog import WebStoreDialog
class VirtualoStore(BasicStoreConfig, StorePlugin):
def open(self, parent=None, detail_item=None, external=False):
url = 'http://virtualo.pl/ebook/c2/'
detail_url = None
if external or self.config.get('open_external', False):
open_url(QUrl(url_slash_cleaner(detail_item if detail_item else url)))
else:
d = WebStoreDialog(self.gui, url, parent, detail_url)
d.setWindowTitle(self.name)
d.set_tags(self.config.get('tags', ''))
d.exec_()
def search(self, query, max_results=10, timeout=60):
url = 'http://virtualo.pl/c2/?q=' + urllib.quote(query.encode('utf-8'))
br = browser()
counter = max_results
with closing(br.open(url, timeout=timeout)) as f:
doc = html.fromstring(f.read())
for data in doc.xpath('//div[@id="product_list"]/div/div[@class="column"]'):
if counter <= 0:
break
id = ''.join(data.xpath('.//table/tr[2]/td[1]/a/@href'))
if not id:
continue
price = ''.join(data.xpath('.//span[@class="price"]/text() | .//span[@class="price abbr"]/text()'))
cover_url = ''.join(data.xpath('.//table/tr[2]/td[1]/a/img/@src'))
title = ''.join(data.xpath('.//div[@class="title"]/a/text()'))
author = ', '.join(data.xpath('.//div[@class="authors"]/a/text()'))
formats = ', '.join(data.xpath('.//span[@class="format"]/a/text()'))
formats = re.sub(r'(, )?ONLINE(, )?', '', formats)
counter -= 1
s = SearchResult()
s.cover_url = cover_url
s.title = title.strip() + ' ' + formats
s.author = author.strip()
s.price = price + ''
s.detail_item = 'http://virtualo.pl' + id.strip()
s.formats = formats.upper().strip()
s.drm = SearchResult.DRM_UNKNOWN
yield s

View File

@ -46,6 +46,64 @@ class TestEmail(QDialog, TE_Dialog):
finally:
self.test_button.setEnabled(True)
class RelaySetup(QDialog):
def __init__(self, service, parent):
QDialog.__init__(self, parent)
self.l = l = QGridLayout()
self.setLayout(l)
self.bb = bb = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel)
bb.accepted.connect(self.accept)
bb.rejected.connect(self.reject)
self.tl = QLabel(('<p>'+_('Setup sending email using') +
' <b>{name}</b><p>' +
_('If you don\'t have an account, you can sign up for a free {name} email '
'account at <a href="http://{url}">http://{url}</a>. {extra}')).format(
**service))
l.addWidget(self.tl, 0, 0, 3, 0)
self.tl.setWordWrap(True)
self.tl.setOpenExternalLinks(True)
for name, label in (
['from_', _('Your %s &email address:')],
['username', _('Your %s &username:')],
['password', _('Your %s &password:')],
):
la = QLabel(label%service['name'])
le = QLineEdit(self)
setattr(self, name, le)
setattr(self, name+'_label', la)
r = l.rowCount()
l.addWidget(la, r, 0)
l.addWidget(le, r, 1)
la.setBuddy(le)
if name == 'password':
self.ptoggle = QCheckBox(_('&Show password'), self)
l.addWidget(self.ptoggle, r, 2)
self.ptoggle.stateChanged.connect(
lambda s: self.password.setEchoMode(self.password.Normal if s
== Qt.Checked else self.password.Password))
self.username.setText(service['username'])
self.password.setEchoMode(self.password.Password)
self.bl = QLabel('<p>' + _(
'If you plan to use email to send books to your Kindle, remember to'
' add the your %s email address to the allowed email addresses in your '
'Amazon.com Kindle management page.')%service['name'])
self.bl.setWordWrap(True)
l.addWidget(self.bl, l.rowCount(), 0, 3, 0)
l.addWidget(bb, l.rowCount(), 0, 3, 0)
self.setWindowTitle(_('Setup') + ' ' + service['name'])
self.resize(self.sizeHint())
self.service = service
def accept(self):
un = unicode(self.username.text())
if self.service.get('at_in_username', False) and '@' not in un:
return error_dialog(self, _('Incorrect username'),
_('%s needs the full email address as your username') %
self.service['name'], show=True)
QDialog.accept(self)
class SendEmail(QWidget, Ui_Form):
@ -129,7 +187,8 @@ class SendEmail(QWidget, Ui_Form):
'port': 587,
'username': '@gmail.com',
'url': 'www.gmail.com',
'extra': ''
'extra': '',
'at_in_username': True,
},
'hotmail': {
'name': 'Hotmail',
@ -143,53 +202,10 @@ class SendEmail(QWidget, Ui_Form):
' will let calibre send email. In this case, I'
' strongly suggest you setup a free gmail account'
' instead.'),
'at_in_username': True,
}
}[service]
d = QDialog(self)
l = QGridLayout()
d.setLayout(l)
bb = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel)
bb.accepted.connect(d.accept)
bb.rejected.connect(d.reject)
d.tl = QLabel(('<p>'+_('Setup sending email using') +
' <b>{name}</b><p>' +
_('If you don\'t have an account, you can sign up for a free {name} email '
'account at <a href="http://{url}">http://{url}</a>. {extra}')).format(
**service))
l.addWidget(d.tl, 0, 0, 3, 0)
d.tl.setWordWrap(True)
d.tl.setOpenExternalLinks(True)
for name, label in (
['from_', _('Your %s &email address:')],
['username', _('Your %s &username:')],
['password', _('Your %s &password:')],
):
la = QLabel(label%service['name'])
le = QLineEdit(d)
setattr(d, name, le)
setattr(d, name+'_label', la)
r = l.rowCount()
l.addWidget(la, r, 0)
l.addWidget(le, r, 1)
la.setBuddy(le)
if name == 'password':
d.ptoggle = QCheckBox(_('&Show password'), d)
l.addWidget(d.ptoggle, r, 2)
d.ptoggle.stateChanged.connect(
lambda s: d.password.setEchoMode(d.password.Normal if s
== Qt.Checked else d.password.Password))
d.username.setText(service['username'])
d.password.setEchoMode(d.password.Password)
d.bl = QLabel('<p>' + _(
'If you plan to use email to send books to your Kindle, remember to'
' add the your %s email address to the allowed email addresses in your '
'Amazon.com Kindle management page.')%service['name'])
d.bl.setWordWrap(True)
l.addWidget(d.bl, l.rowCount(), 0, 3, 0)
l.addWidget(bb, l.rowCount(), 0, 3, 0)
d.setWindowTitle(_('Setup') + ' ' + service['name'])
d.resize(d.sizeHint())
bb.setVisible(True)
d = RelaySetup(service, self)
if d.exec_() != d.Accepted:
return
self.relay_username.setText(d.username.text())

View File

@ -587,7 +587,7 @@ You can download news and convert it into an ebook with the command::
/opt/calibre/ebook-convert "Title of news source.recipe" outputfile.epub
If you want to generate MOBI, use outputfile.mobi instead.
If you want to generate MOBI, use outputfile.mobi instead and use ``--output-profile kindle``.
You can email downloaded news with the command::