mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Merge from trunk
This commit is contained in:
commit
1bfa5d0873
42
recipes/aachener_nachrichten.recipe
Normal file
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')]
|
@ -3,10 +3,7 @@ class AdvancedUserRecipe1303841067(BasicNewsRecipe):
|
|||||||
|
|
||||||
title = u'Faz.net'
|
title = u'Faz.net'
|
||||||
__author__ = 'schuster'
|
__author__ = 'schuster'
|
||||||
remove_tags = [dict(attrs={'class':['right', 'ArrowLinkRight', 'ModulVerlagsInfo', 'left', 'Head']}),
|
oldest_article = 1
|
||||||
dict(id=['BreadCrumbs', 'tstag', 'FazFooterPrint']),
|
|
||||||
dict(name=['script', 'noscript', 'style'])]
|
|
||||||
oldest_article = 2
|
|
||||||
description = 'Frankfurter Allgemeine Zeitung'
|
description = 'Frankfurter Allgemeine Zeitung'
|
||||||
max_articles_per_feed = 100
|
max_articles_per_feed = 100
|
||||||
no_stylesheets = True
|
no_stylesheets = True
|
||||||
@ -15,9 +12,9 @@ class AdvancedUserRecipe1303841067(BasicNewsRecipe):
|
|||||||
remove_javascript = True
|
remove_javascript = True
|
||||||
cover_url = 'http://www.faz.net/f30/Images/Logos/logo.gif'
|
cover_url = 'http://www.faz.net/f30/Images/Logos/logo.gif'
|
||||||
|
|
||||||
def print_version(self, url):
|
remove_tags = [dict(attrs={'class':['LinkBoxModulSmall', 'ModulLesermeinungenFooter', 'ModulArtikelServices', 'SocialMediaUnten', 'ArrowLinkRight', 'ModulVerlagsInfo', 'AdData', 'FazFooter', 'Date']}),
|
||||||
return url.replace('.html', '~Afor~Eprint.html')
|
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'),
|
feeds = [(u'Politik', u'http://www.faz.net/s/RubA24ECD630CAE40E483841DB7D16F4211/Tpl~Epartner~SRss_.xml'),
|
||||||
|
35
recipes/frankfurter_rundschau.recipe
Normal file
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')
|
||||||
|
|
55
recipes/rheinische_post.recipe
Normal file
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'),
|
||||||
|
|
||||||
|
]
|
@ -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"
|
||||||
}
|
}
|
@ -594,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
|
||||||
@ -693,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,
|
||||||
@ -1272,6 +1271,16 @@ class StoreKoboStore(StoreBase):
|
|||||||
headquarters = 'CA'
|
headquarters = 'CA'
|
||||||
formats = ['EPUB']
|
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 = u'Public domain and creative commons works from many sources.'
|
description = u'Public domain and creative commons works from many sources.'
|
||||||
@ -1306,7 +1315,7 @@ class StoreOpenLibraryStore(StoreBase):
|
|||||||
actual_plugin = 'calibre.gui2.store.open_library_plugin:OpenLibraryStore'
|
actual_plugin = 'calibre.gui2.store.open_library_plugin:OpenLibraryStore'
|
||||||
|
|
||||||
drm_free_only = True
|
drm_free_only = True
|
||||||
headquarters = ['US']
|
headquarters = 'US'
|
||||||
formats = ['DAISY', 'DJVU', 'EPUB', 'MOBI', 'PDF', 'TXT']
|
formats = ['DAISY', 'DJVU', 'EPUB', 'MOBI', 'PDF', 'TXT']
|
||||||
|
|
||||||
class StoreOReillyStore(StoreBase):
|
class StoreOReillyStore(StoreBase):
|
||||||
@ -1336,6 +1345,16 @@ class StoreSmashwordsStore(StoreBase):
|
|||||||
headquarters = 'US'
|
headquarters = 'US'
|
||||||
formats = ['EPUB', 'HTML', 'LRF', 'MOBI', 'PDB', 'RTF', 'TXT']
|
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'
|
||||||
author = 'Charles Haley'
|
author = 'Charles Haley'
|
||||||
@ -1366,12 +1385,12 @@ class StoreWizardsTowerBooksStore(StoreBase):
|
|||||||
|
|
||||||
class StoreWoblinkStore(StoreBase):
|
class StoreWoblinkStore(StoreBase):
|
||||||
name = 'Woblink'
|
name = 'Woblink'
|
||||||
author = 'Tomasz Długosz'
|
author = u'Tomasz Długosz'
|
||||||
description = u'Czytanie zdarza się wszędzie!'
|
description = u'Czytanie zdarza się wszędzie!'
|
||||||
actual_plugin = 'calibre.gui2.store.woblink_plugin:WoblinkStore'
|
actual_plugin = 'calibre.gui2.store.woblink_plugin:WoblinkStore'
|
||||||
|
|
||||||
drm_free_only = False
|
drm_free_only = False
|
||||||
location = 'PL'
|
headquarters = 'PL'
|
||||||
formats = ['EPUB']
|
formats = ['EPUB']
|
||||||
|
|
||||||
plugins += [
|
plugins += [
|
||||||
@ -1393,6 +1412,7 @@ plugins += [
|
|||||||
StoreGoogleBooksStore,
|
StoreGoogleBooksStore,
|
||||||
StoreGutenbergStore,
|
StoreGutenbergStore,
|
||||||
StoreKoboStore,
|
StoreKoboStore,
|
||||||
|
StoreLegimiStore,
|
||||||
StoreManyBooksStore,
|
StoreManyBooksStore,
|
||||||
StoreMobileReadStore,
|
StoreMobileReadStore,
|
||||||
StoreNextoStore,
|
StoreNextoStore,
|
||||||
@ -1400,6 +1420,7 @@ plugins += [
|
|||||||
StoreOReillyStore,
|
StoreOReillyStore,
|
||||||
StorePragmaticBookshelfStore,
|
StorePragmaticBookshelfStore,
|
||||||
StoreSmashwordsStore,
|
StoreSmashwordsStore,
|
||||||
|
StoreVirtualoStore,
|
||||||
StoreWaterstonesUKStore,
|
StoreWaterstonesUKStore,
|
||||||
StoreWeightlessBooksStore,
|
StoreWeightlessBooksStore,
|
||||||
StoreWizardsTowerBooksStore,
|
StoreWizardsTowerBooksStore,
|
||||||
|
@ -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]},
|
||||||
|
@ -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'
|
||||||
|
|
||||||
|
|
||||||
|
@ -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')
|
||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
|
@ -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,9 +439,15 @@ 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=None):
|
||||||
|
if domain is None:
|
||||||
domain = self.domain
|
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',
|
||||||
'unfiltered' : '1',
|
'unfiltered' : '1',
|
||||||
@ -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')
|
||||||
@ -571,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:
|
||||||
|
@ -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 != '':
|
||||||
|
if t == "isbn" or t == "subject":
|
||||||
url = url + "?apikey=" + self.DOUBAN_API_KEY
|
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:
|
||||||
|
@ -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):
|
||||||
@ -107,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)
|
||||||
|
@ -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">
|
||||||
|
<item>
|
||||||
|
<widget class="SearchBox2" name="search"/>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QToolButton" name="go_button">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>&Search:</string>
|
<string>Go</string>
|
||||||
</property>
|
|
||||||
<property name="buddy">
|
|
||||||
<cstring>search</cstring>
|
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="0" column="1">
|
<item>
|
||||||
<widget class="SearchBox2" name="search"/>
|
<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>
|
||||||
<item row="0" column="2" rowspan="3">
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="1" rowspan="2">
|
||||||
<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>
|
||||||
|
@ -9,7 +9,7 @@ from PyQt4.Qt import (QLineEdit, QDialog, QGridLayout, QLabel,
|
|||||||
QDialogButtonBox, QColor, QComboBox, QIcon)
|
QDialogButtonBox, QColor, QComboBox, QIcon)
|
||||||
|
|
||||||
from calibre.gui2.dialogs.template_dialog import TemplateDialog
|
from calibre.gui2.dialogs.template_dialog import TemplateDialog
|
||||||
from calibre.gui2.complete import MultiCompleteComboBox
|
from calibre.gui2.complete import MultiCompleteLineEdit
|
||||||
from calibre.gui2 import error_dialog
|
from calibre.gui2 import error_dialog
|
||||||
|
|
||||||
class TemplateLineEditor(QLineEdit):
|
class TemplateLineEditor(QLineEdit):
|
||||||
@ -63,14 +63,16 @@ class TagWizard(QDialog):
|
|||||||
self.tags = tags
|
self.tags = tags
|
||||||
l = QGridLayout()
|
l = QGridLayout()
|
||||||
self.setLayout(l)
|
self.setLayout(l)
|
||||||
l.addWidget(QLabel(_('Tag Value')), 0, 0, 1, 1)
|
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)
|
l.addWidget(QLabel(_('Color')), 0, 1, 1, 1)
|
||||||
self.tagboxes = []
|
self.tagboxes = []
|
||||||
self.colorboxes = []
|
self.colorboxes = []
|
||||||
self.colors = [unicode(s) for s in list(QColor.colorNames())]
|
self.colors = [unicode(s) for s in list(QColor.colorNames())]
|
||||||
self.colors.insert(0, '')
|
self.colors.insert(0, '')
|
||||||
for i in range(0, 10):
|
for i in range(0, 10):
|
||||||
tb = MultiCompleteComboBox(self)
|
tb = MultiCompleteLineEdit(self)
|
||||||
tb.set_separator(', ')
|
tb.set_separator(', ')
|
||||||
tb.update_items_cache(self.tags)
|
tb.update_items_cache(self.tags)
|
||||||
self.tagboxes.append(tb)
|
self.tagboxes.append(tb)
|
||||||
@ -104,7 +106,8 @@ class TagWizard(QDialog):
|
|||||||
" t = field('tags');\n first_non_empty(\n")
|
" t = field('tags');\n first_non_empty(\n")
|
||||||
lines = []
|
lines = []
|
||||||
for tb, cb in zip(self.tagboxes, self.colorboxes):
|
for tb, cb in zip(self.tagboxes, self.colorboxes):
|
||||||
tags = [t.strip() for t in unicode(tb.currentText()).split(',') if t.strip()]
|
tags = [t.strip() for t in unicode(tb.text()).split(',') if t.strip()]
|
||||||
|
tags = '$|^'.join(tags)
|
||||||
c = unicode(cb.currentText()).strip()
|
c = unicode(cb.currentText()).strip()
|
||||||
if not tags or not c:
|
if not tags or not c:
|
||||||
continue
|
continue
|
||||||
@ -113,14 +116,13 @@ class TagWizard(QDialog):
|
|||||||
_('The color {0} is not valid').format(c),
|
_('The color {0} is not valid').format(c),
|
||||||
show=True, show_copy_button=False)
|
show=True, show_copy_button=False)
|
||||||
return False
|
return False
|
||||||
for t in tags:
|
lines.append(" in_list(t, ',', '^{0}$', '{1}', '')".format(tags, c))
|
||||||
lines.append(" in_list(t, ',', '^{0}$', '{1}', '')".format(t, c))
|
|
||||||
res += ',\n'.join(lines)
|
res += ',\n'.join(lines)
|
||||||
res += ')\n'
|
res += ')\n'
|
||||||
self.template = res
|
self.template = res
|
||||||
res = ''
|
res = ''
|
||||||
for tb, cb in zip(self.tagboxes, self.colorboxes):
|
for tb, cb in zip(self.tagboxes, self.colorboxes):
|
||||||
t = unicode(tb.currentText()).strip()
|
t = unicode(tb.text()).strip()
|
||||||
if t.endswith(','):
|
if t.endswith(','):
|
||||||
t = t[:-1]
|
t = t[:-1]
|
||||||
c = unicode(cb.currentText()).strip()
|
c = unicode(cb.currentText()).strip()
|
||||||
|
@ -167,8 +167,8 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
|||||||
'<a href="http://calibre-ebook.com/user_manual/template_lang.html">'
|
'<a href="http://calibre-ebook.com/user_manual/template_lang.html">'
|
||||||
'tutorial</a> on using templates.') +
|
'tutorial</a> on using templates.') +
|
||||||
'</p><p>' +
|
'</p><p>' +
|
||||||
_('If you want to color a field based on tags, then right-click '
|
_('If you want to color a field based on tags, then click the '
|
||||||
'in an empty template line and choose tags wizard. '
|
'button next to an empty line to open the tags wizard. '
|
||||||
'It will build a template for you. You can later edit that '
|
'It will build a template for you. You can later edit that '
|
||||||
'template with the same wizard. If you edit it by hand, the '
|
'template with the same wizard. If you edit it by hand, the '
|
||||||
'wizard might not work or might restore old values.') +
|
'wizard might not work or might restore old values.') +
|
||||||
|
@ -442,6 +442,9 @@ then the tags will be displayed each on their own line.</string>
|
|||||||
<iconset resource="../../../../resources/images.qrc">
|
<iconset resource="../../../../resources/images.qrc">
|
||||||
<normaloff>:/images/wizard.png</normaloff>:/images/wizard.png</iconset>
|
<normaloff>:/images/wizard.png</normaloff>:/images/wizard.png</iconset>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Open the tags wizard.</string>
|
||||||
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="3" column="0">
|
<item row="3" column="0">
|
||||||
@ -456,6 +459,9 @@ then the tags will be displayed each on their own line.</string>
|
|||||||
<iconset resource="../../../../resources/images.qrc">
|
<iconset resource="../../../../resources/images.qrc">
|
||||||
<normaloff>:/images/wizard.png</normaloff>:/images/wizard.png</iconset>
|
<normaloff>:/images/wizard.png</normaloff>:/images/wizard.png</iconset>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Open the tags wizard.</string>
|
||||||
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="4" column="0">
|
<item row="4" column="0">
|
||||||
@ -470,6 +476,9 @@ then the tags will be displayed each on their own line.</string>
|
|||||||
<iconset resource="../../../../resources/images.qrc">
|
<iconset resource="../../../../resources/images.qrc">
|
||||||
<normaloff>:/images/wizard.png</normaloff>:/images/wizard.png</iconset>
|
<normaloff>:/images/wizard.png</normaloff>:/images/wizard.png</iconset>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Open the tags wizard.</string>
|
||||||
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="5" column="0">
|
<item row="5" column="0">
|
||||||
@ -484,6 +493,9 @@ then the tags will be displayed each on their own line.</string>
|
|||||||
<iconset resource="../../../../resources/images.qrc">
|
<iconset resource="../../../../resources/images.qrc">
|
||||||
<normaloff>:/images/wizard.png</normaloff>:/images/wizard.png</iconset>
|
<normaloff>:/images/wizard.png</normaloff>:/images/wizard.png</iconset>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Open the tags wizard.</string>
|
||||||
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="6" column="0">
|
<item row="6" column="0">
|
||||||
@ -498,6 +510,9 @@ then the tags will be displayed each on their own line.</string>
|
|||||||
<iconset resource="../../../../resources/images.qrc">
|
<iconset resource="../../../../resources/images.qrc">
|
||||||
<normaloff>:/images/wizard.png</normaloff>:/images/wizard.png</iconset>
|
<normaloff>:/images/wizard.png</normaloff>:/images/wizard.png</iconset>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Open the tags wizard.</string>
|
||||||
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="20" column="0">
|
<item row="20" column="0">
|
||||||
|
0
src/calibre/gui2/store/config/chooser/__init__.py
Normal file
0
src/calibre/gui2/store/config/chooser/__init__.py
Normal file
131
src/calibre/gui2/store/config/chooser/adv_search_builder.py
Normal file
131
src/calibre/gui2/store/config/chooser/adv_search_builder.py
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from __future__ import (unicode_literals, division, absolute_import, print_function)
|
||||||
|
|
||||||
|
__license__ = 'GPL 3'
|
||||||
|
__copyright__ = '2011, John Schember <john@nachtimwald.com>'
|
||||||
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from PyQt4.Qt import (QDialog, QDialogButtonBox)
|
||||||
|
|
||||||
|
from calibre.gui2.store.config.chooser.adv_search_builder_ui import Ui_Dialog
|
||||||
|
from calibre.library.caches import CONTAINS_MATCH, EQUALS_MATCH
|
||||||
|
|
||||||
|
class AdvSearchBuilderDialog(QDialog, Ui_Dialog):
|
||||||
|
|
||||||
|
def __init__(self, parent):
|
||||||
|
QDialog.__init__(self, parent)
|
||||||
|
self.setupUi(self)
|
||||||
|
|
||||||
|
self.buttonBox.accepted.connect(self.advanced_search_button_pushed)
|
||||||
|
self.tab_2_button_box.accepted.connect(self.accept)
|
||||||
|
self.tab_2_button_box.rejected.connect(self.reject)
|
||||||
|
self.clear_button.clicked.connect(self.clear_button_pushed)
|
||||||
|
self.adv_search_used = False
|
||||||
|
self.mc = ''
|
||||||
|
|
||||||
|
self.tabWidget.setCurrentIndex(0)
|
||||||
|
self.tabWidget.currentChanged[int].connect(self.tab_changed)
|
||||||
|
self.tab_changed(0)
|
||||||
|
|
||||||
|
def tab_changed(self, idx):
|
||||||
|
if idx == 1:
|
||||||
|
self.tab_2_button_box.button(QDialogButtonBox.Ok).setDefault(True)
|
||||||
|
else:
|
||||||
|
self.buttonBox.button(QDialogButtonBox.Ok).setDefault(True)
|
||||||
|
|
||||||
|
def advanced_search_button_pushed(self):
|
||||||
|
self.adv_search_used = True
|
||||||
|
self.accept()
|
||||||
|
|
||||||
|
def clear_button_pushed(self):
|
||||||
|
self.name_box.setText('')
|
||||||
|
self.description_box.setText('')
|
||||||
|
self.headquarters_box.setText('')
|
||||||
|
self.format_box.setText('')
|
||||||
|
self.enabled_combo.setIndex(0)
|
||||||
|
self.drm_combo.setIndex(0)
|
||||||
|
|
||||||
|
def tokens(self, raw):
|
||||||
|
phrases = re.findall(r'\s*".*?"\s*', raw)
|
||||||
|
for f in phrases:
|
||||||
|
raw = raw.replace(f, ' ')
|
||||||
|
phrases = [t.strip('" ') for t in phrases]
|
||||||
|
return ['"' + self.mc + t + '"' for t in phrases + [r.strip() for r in raw.split()]]
|
||||||
|
|
||||||
|
def search_string(self):
|
||||||
|
if self.adv_search_used:
|
||||||
|
return self.adv_search_string()
|
||||||
|
else:
|
||||||
|
return self.box_search_string()
|
||||||
|
|
||||||
|
def adv_search_string(self):
|
||||||
|
mk = self.matchkind.currentIndex()
|
||||||
|
if mk == CONTAINS_MATCH:
|
||||||
|
self.mc = ''
|
||||||
|
elif mk == EQUALS_MATCH:
|
||||||
|
self.mc = '='
|
||||||
|
else:
|
||||||
|
self.mc = '~'
|
||||||
|
all, any, phrase, none = map(lambda x: unicode(x.text()),
|
||||||
|
(self.all, self.any, self.phrase, self.none))
|
||||||
|
all, any, none = map(self.tokens, (all, any, none))
|
||||||
|
phrase = phrase.strip()
|
||||||
|
all = ' and '.join(all)
|
||||||
|
any = ' or '.join(any)
|
||||||
|
none = ' and not '.join(none)
|
||||||
|
ans = ''
|
||||||
|
if phrase:
|
||||||
|
ans += '"%s"'%phrase
|
||||||
|
if all:
|
||||||
|
ans += (' and ' if ans else '') + all
|
||||||
|
if none:
|
||||||
|
ans += (' and not ' if ans else 'not ') + none
|
||||||
|
if any:
|
||||||
|
ans += (' or ' if ans else '') + any
|
||||||
|
return ans
|
||||||
|
|
||||||
|
def token(self):
|
||||||
|
txt = unicode(self.text.text()).strip()
|
||||||
|
if txt:
|
||||||
|
if self.negate.isChecked():
|
||||||
|
txt = '!'+txt
|
||||||
|
tok = self.FIELDS[unicode(self.field.currentText())]+txt
|
||||||
|
if re.search(r'\s', tok):
|
||||||
|
tok = '"%s"'%tok
|
||||||
|
return tok
|
||||||
|
|
||||||
|
def box_search_string(self):
|
||||||
|
mk = self.matchkind.currentIndex()
|
||||||
|
if mk == CONTAINS_MATCH:
|
||||||
|
self.mc = ''
|
||||||
|
elif mk == EQUALS_MATCH:
|
||||||
|
self.mc = '='
|
||||||
|
else:
|
||||||
|
self.mc = '~'
|
||||||
|
|
||||||
|
ans = []
|
||||||
|
self.box_last_values = {}
|
||||||
|
name = unicode(self.name_box.text()).strip()
|
||||||
|
if name:
|
||||||
|
ans.append('name:"' + self.mc + name + '"')
|
||||||
|
description = unicode(self.description_box.text()).strip()
|
||||||
|
if description:
|
||||||
|
ans.append('description:"' + self.mc + description + '"')
|
||||||
|
headquarters = unicode(self.headquarters_box.text()).strip()
|
||||||
|
if headquarters:
|
||||||
|
ans.append('headquarters:"' + self.mc + headquarters + '"')
|
||||||
|
format = unicode(self.format_box.text()).strip()
|
||||||
|
if format:
|
||||||
|
ans.append('format:"' + self.mc + format + '"')
|
||||||
|
enabled = unicode(self.enabled_combo.currentText()).strip()
|
||||||
|
if enabled:
|
||||||
|
ans.append('enabled:' + enabled)
|
||||||
|
drm = unicode(self.drm_combo.currentText()).strip()
|
||||||
|
if drm:
|
||||||
|
ans.append('drm:' + drm)
|
||||||
|
if ans:
|
||||||
|
return ' and '.join(ans)
|
||||||
|
return ''
|
416
src/calibre/gui2/store/config/chooser/adv_search_builder.ui
Normal file
416
src/calibre/gui2/store/config/chooser/adv_search_builder.ui
Normal file
@ -0,0 +1,416 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>Dialog</class>
|
||||||
|
<widget class="QDialog" name="Dialog">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>752</width>
|
||||||
|
<height>472</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Advanced Search</string>
|
||||||
|
</property>
|
||||||
|
<property name="windowIcon">
|
||||||
|
<iconset>
|
||||||
|
<normaloff>:/images/search.png</normaloff>:/images/search.png</iconset>
|
||||||
|
</property>
|
||||||
|
<layout class="QGridLayout" name="gridLayout_2">
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QLabel" name="label_5">
|
||||||
|
<property name="text">
|
||||||
|
<string>&What kind of match to use:</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy">
|
||||||
|
<cstring>matchkind</cstring>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="1">
|
||||||
|
<widget class="QComboBox" name="matchkind">
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Contains: the word or phrase matches anywhere in the metadata field</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Equals: the word or phrase must match the entire metadata field</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Regular expression: the expression must match anywhere in the metadata field</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="0" colspan="2">
|
||||||
|
<widget class="QTabWidget" name="tabWidget">
|
||||||
|
<property name="currentIndex">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<widget class="QWidget" name="tab">
|
||||||
|
<attribute name="title">
|
||||||
|
<string>A&dvanced Search</string>
|
||||||
|
</attribute>
|
||||||
|
<layout class="QGridLayout" name="gridLayout_3">
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QGroupBox" name="groupBox">
|
||||||
|
<property name="title">
|
||||||
|
<string>Find entries that have...</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label">
|
||||||
|
<property name="text">
|
||||||
|
<string>&All these words:</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy">
|
||||||
|
<cstring>all</cstring>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLineEdit" name="all"/>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_2">
|
||||||
|
<property name="text">
|
||||||
|
<string>This exact &phrase:</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy">
|
||||||
|
<cstring>all</cstring>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLineEdit" name="phrase"/>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_3">
|
||||||
|
<property name="text">
|
||||||
|
<string>&One or more of these words:</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy">
|
||||||
|
<cstring>all</cstring>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLineEdit" name="any"/>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QGroupBox" name="groupBox_2">
|
||||||
|
<property name="title">
|
||||||
|
<string>But dont show entries that have...</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_4">
|
||||||
|
<property name="text">
|
||||||
|
<string>Any of these &unwanted words:</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy">
|
||||||
|
<cstring>all</cstring>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLineEdit" name="none"/>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_6">
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>16777215</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>See the <a href="http://calibre-ebook.com/user_manual/gui.html#the-search-interface">User Manual</a> for more help</string>
|
||||||
|
</property>
|
||||||
|
<property name="openExternalLinks">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="0">
|
||||||
|
<spacer name="verticalSpacer_2">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>40</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="0">
|
||||||
|
<widget class="QDialogButtonBox" name="buttonBox">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="standardButtons">
|
||||||
|
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<widget class="QWidget" name="tab_2">
|
||||||
|
<attribute name="title">
|
||||||
|
<string>Nam&e/Description ...</string>
|
||||||
|
</attribute>
|
||||||
|
<layout class="QGridLayout" name="gridLayout">
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QLabel" name="label_7">
|
||||||
|
<property name="text">
|
||||||
|
<string>&Name:</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy">
|
||||||
|
<cstring>name_box</cstring>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<widget class="EnLineEdit" name="name_box">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Enter the title.</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="0">
|
||||||
|
<widget class="QLabel" name="label_8">
|
||||||
|
<property name="text">
|
||||||
|
<string>&Description:</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy">
|
||||||
|
<cstring>description_box</cstring>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="0">
|
||||||
|
<widget class="QLabel" name="price_label">
|
||||||
|
<property name="text">
|
||||||
|
<string>&Headquarters:</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy">
|
||||||
|
<cstring>headquarters_box</cstring>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="8" column="0" colspan="2">
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_6">
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="clear_button">
|
||||||
|
<property name="text">
|
||||||
|
<string>&Clear</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QDialogButtonBox" name="tab_2_button_box">
|
||||||
|
<property name="standardButtons">
|
||||||
|
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item row="7" column="1">
|
||||||
|
<spacer name="verticalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>40</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="0" colspan="2">
|
||||||
|
<widget class="QLabel" name="label_11">
|
||||||
|
<property name="text">
|
||||||
|
<string>Search only in specific fields:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="1">
|
||||||
|
<widget class="EnLineEdit" name="description_box"/>
|
||||||
|
</item>
|
||||||
|
<item row="4" column="1">
|
||||||
|
<widget class="QLineEdit" name="format_box"/>
|
||||||
|
</item>
|
||||||
|
<item row="4" column="0">
|
||||||
|
<widget class="QLabel" name="label_10">
|
||||||
|
<property name="text">
|
||||||
|
<string>&Format:</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy">
|
||||||
|
<cstring>format_box</cstring>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="1">
|
||||||
|
<widget class="EnLineEdit" name="headquarters_box"/>
|
||||||
|
</item>
|
||||||
|
<item row="5" column="0">
|
||||||
|
<widget class="QLabel" name="label_9">
|
||||||
|
<property name="text">
|
||||||
|
<string>Enabled:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="6" column="0">
|
||||||
|
<widget class="QLabel" name="label_12">
|
||||||
|
<property name="text">
|
||||||
|
<string>DRM:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="5" column="1">
|
||||||
|
<widget class="QComboBox" name="enabled_combo">
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>true</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>false</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="6" column="1">
|
||||||
|
<widget class="QComboBox" name="drm_combo">
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>true</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>false</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<spacer name="verticalSpacer_3">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>40</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<customwidgets>
|
||||||
|
<customwidget>
|
||||||
|
<class>EnLineEdit</class>
|
||||||
|
<extends>QLineEdit</extends>
|
||||||
|
<header>widgets.h</header>
|
||||||
|
</customwidget>
|
||||||
|
</customwidgets>
|
||||||
|
<tabstops>
|
||||||
|
<tabstop>all</tabstop>
|
||||||
|
<tabstop>phrase</tabstop>
|
||||||
|
<tabstop>any</tabstop>
|
||||||
|
<tabstop>none</tabstop>
|
||||||
|
<tabstop>buttonBox</tabstop>
|
||||||
|
<tabstop>name_box</tabstop>
|
||||||
|
<tabstop>description_box</tabstop>
|
||||||
|
<tabstop>headquarters_box</tabstop>
|
||||||
|
<tabstop>format_box</tabstop>
|
||||||
|
<tabstop>clear_button</tabstop>
|
||||||
|
<tabstop>tab_2_button_box</tabstop>
|
||||||
|
<tabstop>tabWidget</tabstop>
|
||||||
|
<tabstop>matchkind</tabstop>
|
||||||
|
</tabstops>
|
||||||
|
<resources>
|
||||||
|
<include location="../../../../resources/images.qrc"/>
|
||||||
|
</resources>
|
||||||
|
<connections>
|
||||||
|
<connection>
|
||||||
|
<sender>buttonBox</sender>
|
||||||
|
<signal>accepted()</signal>
|
||||||
|
<receiver>Dialog</receiver>
|
||||||
|
<slot>accept()</slot>
|
||||||
|
<hints>
|
||||||
|
<hint type="sourcelabel">
|
||||||
|
<x>248</x>
|
||||||
|
<y>254</y>
|
||||||
|
</hint>
|
||||||
|
<hint type="destinationlabel">
|
||||||
|
<x>157</x>
|
||||||
|
<y>274</y>
|
||||||
|
</hint>
|
||||||
|
</hints>
|
||||||
|
</connection>
|
||||||
|
<connection>
|
||||||
|
<sender>buttonBox</sender>
|
||||||
|
<signal>rejected()</signal>
|
||||||
|
<receiver>Dialog</receiver>
|
||||||
|
<slot>reject()</slot>
|
||||||
|
<hints>
|
||||||
|
<hint type="sourcelabel">
|
||||||
|
<x>316</x>
|
||||||
|
<y>260</y>
|
||||||
|
</hint>
|
||||||
|
<hint type="destinationlabel">
|
||||||
|
<x>286</x>
|
||||||
|
<y>274</y>
|
||||||
|
</hint>
|
||||||
|
</hints>
|
||||||
|
</connection>
|
||||||
|
</connections>
|
||||||
|
</ui>
|
28
src/calibre/gui2/store/config/chooser/chooser_dialog.py
Normal file
28
src/calibre/gui2/store/config/chooser/chooser_dialog.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from __future__ import (unicode_literals, division, absolute_import, print_function)
|
||||||
|
|
||||||
|
__license__ = 'GPL 3'
|
||||||
|
__copyright__ = '2011, John Schember <john@nachtimwald.com>'
|
||||||
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
from PyQt4.Qt import (QDialog, QDialogButtonBox, QVBoxLayout)
|
||||||
|
|
||||||
|
from calibre.gui2.store.config.chooser.chooser_widget import StoreChooserWidget
|
||||||
|
|
||||||
|
class StoreChooserDialog(QDialog):
|
||||||
|
|
||||||
|
def __init__(self, parent):
|
||||||
|
QDialog.__init__(self, parent)
|
||||||
|
|
||||||
|
self.setWindowTitle(_('Choose stores'))
|
||||||
|
|
||||||
|
button_box = QDialogButtonBox(QDialogButtonBox.Close)
|
||||||
|
button_box.accepted.connect(self.accept)
|
||||||
|
button_box.rejected.connect(self.reject)
|
||||||
|
v = QVBoxLayout(self)
|
||||||
|
self.config_widget = StoreChooserWidget()
|
||||||
|
v.addWidget(self.config_widget)
|
||||||
|
v.addWidget(button_box)
|
||||||
|
|
||||||
|
self.resize(800, 600)
|
35
src/calibre/gui2/store/config/chooser/chooser_widget.py
Normal file
35
src/calibre/gui2/store/config/chooser/chooser_widget.py
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from __future__ import (unicode_literals, division, absolute_import, print_function)
|
||||||
|
|
||||||
|
__license__ = 'GPL 3'
|
||||||
|
__copyright__ = '2011, John Schember <john@nachtimwald.com>'
|
||||||
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
from PyQt4.Qt import (QWidget, QIcon, QDialog)
|
||||||
|
|
||||||
|
from calibre.gui2.store.config.chooser.adv_search_builder import AdvSearchBuilderDialog
|
||||||
|
from calibre.gui2.store.config.chooser.chooser_widget_ui import Ui_Form
|
||||||
|
|
||||||
|
class StoreChooserWidget(QWidget, Ui_Form):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
QWidget.__init__(self)
|
||||||
|
self.setupUi(self)
|
||||||
|
|
||||||
|
self.adv_search_builder.setIcon(QIcon(I('search.png')))
|
||||||
|
|
||||||
|
self.search.clicked.connect(self.do_search)
|
||||||
|
self.adv_search_builder.clicked.connect(self.build_adv_search)
|
||||||
|
self.results_view.activated.connect(self.toggle_plugin)
|
||||||
|
|
||||||
|
def do_search(self):
|
||||||
|
self.results_view.model().search(unicode(self.query.text()))
|
||||||
|
|
||||||
|
def toggle_plugin(self, index):
|
||||||
|
self.results_view.model().toggle_plugin(index)
|
||||||
|
|
||||||
|
def build_adv_search(self):
|
||||||
|
adv = AdvSearchBuilderDialog(self)
|
||||||
|
if adv.exec_() == QDialog.Accepted:
|
||||||
|
self.query.setText(adv.search_string())
|
87
src/calibre/gui2/store/config/chooser/chooser_widget.ui
Normal file
87
src/calibre/gui2/store/config/chooser/chooser_widget.ui
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>Form</class>
|
||||||
|
<widget class="QWidget" name="Form">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>610</width>
|
||||||
|
<height>553</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Form</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label">
|
||||||
|
<property name="text">
|
||||||
|
<string>Query:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QToolButton" name="adv_search_builder">
|
||||||
|
<property name="text">
|
||||||
|
<string>...</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLineEdit" name="query"/>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="search">
|
||||||
|
<property name="text">
|
||||||
|
<string>Search</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="ResultsView" name="results_view">
|
||||||
|
<property name="alternatingRowColors">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="selectionMode">
|
||||||
|
<enum>QAbstractItemView::SingleSelection</enum>
|
||||||
|
</property>
|
||||||
|
<property name="selectionBehavior">
|
||||||
|
<enum>QAbstractItemView::SelectRows</enum>
|
||||||
|
</property>
|
||||||
|
<property name="rootIsDecorated">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="uniformRowHeights">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="itemsExpandable">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="sortingEnabled">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="expandsOnDoubleClick">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<attribute name="headerStretchLastSection">
|
||||||
|
<bool>false</bool>
|
||||||
|
</attribute>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<customwidgets>
|
||||||
|
<customwidget>
|
||||||
|
<class>ResultsView</class>
|
||||||
|
<extends>QTreeView</extends>
|
||||||
|
<header>results_view.h</header>
|
||||||
|
</customwidget>
|
||||||
|
</customwidgets>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
257
src/calibre/gui2/store/config/chooser/models.py
Normal file
257
src/calibre/gui2/store/config/chooser/models.py
Normal file
@ -0,0 +1,257 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from __future__ import (unicode_literals, division, absolute_import, print_function)
|
||||||
|
|
||||||
|
__license__ = 'GPL 3'
|
||||||
|
__copyright__ = '2011, John Schember <john@nachtimwald.com>'
|
||||||
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
from PyQt4.Qt import (Qt, QAbstractItemModel, QIcon, QVariant, QModelIndex)
|
||||||
|
|
||||||
|
from calibre.gui2 import NONE
|
||||||
|
from calibre.customize.ui import is_disabled, disable_plugin, enable_plugin
|
||||||
|
from calibre.library.caches import _match, CONTAINS_MATCH, EQUALS_MATCH, \
|
||||||
|
REGEXP_MATCH
|
||||||
|
from calibre.utils.icu import sort_key
|
||||||
|
from calibre.utils.search_query_parser import SearchQueryParser
|
||||||
|
|
||||||
|
|
||||||
|
class Matches(QAbstractItemModel):
|
||||||
|
|
||||||
|
HEADERS = [_('Enabled'), _('Name'), _('No DRM'), _('Headquarters'), _('Formats')]
|
||||||
|
HTML_COLS = [1]
|
||||||
|
|
||||||
|
def __init__(self, plugins):
|
||||||
|
QAbstractItemModel.__init__(self)
|
||||||
|
|
||||||
|
self.NO_DRM_ICON = QIcon(I('ok.png'))
|
||||||
|
|
||||||
|
self.all_matches = plugins
|
||||||
|
self.matches = plugins
|
||||||
|
self.filter = ''
|
||||||
|
self.search_filter = SearchFilter(self.all_matches)
|
||||||
|
|
||||||
|
self.sort_col = 1
|
||||||
|
self.sort_order = Qt.AscendingOrder
|
||||||
|
|
||||||
|
def get_plugin(self, index):
|
||||||
|
row = index.row()
|
||||||
|
if row < len(self.matches):
|
||||||
|
return self.matches[row]
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def search(self, filter):
|
||||||
|
self.filter = filter.strip()
|
||||||
|
if not self.filter:
|
||||||
|
self.matches = self.all_matches
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
self.matches = list(self.search_filter.parse(self.filter))
|
||||||
|
except:
|
||||||
|
self.matches = self.all_matches
|
||||||
|
self.layoutChanged.emit()
|
||||||
|
self.sort(self.sort_col, self.sort_order)
|
||||||
|
|
||||||
|
def toggle_plugin(self, index):
|
||||||
|
new_index = self.createIndex(index.row(), 0)
|
||||||
|
data = QVariant(is_disabled(self.get_plugin(index)))
|
||||||
|
self.setData(new_index, data, Qt.CheckStateRole)
|
||||||
|
|
||||||
|
def index(self, row, column, parent=QModelIndex()):
|
||||||
|
return self.createIndex(row, column)
|
||||||
|
|
||||||
|
def parent(self, index):
|
||||||
|
if not index.isValid() or index.internalId() == 0:
|
||||||
|
return QModelIndex()
|
||||||
|
return self.createIndex(0, 0)
|
||||||
|
|
||||||
|
def rowCount(self, *args):
|
||||||
|
return len(self.matches)
|
||||||
|
|
||||||
|
def columnCount(self, *args):
|
||||||
|
return len(self.HEADERS)
|
||||||
|
|
||||||
|
def headerData(self, section, orientation, role):
|
||||||
|
if role != Qt.DisplayRole:
|
||||||
|
return NONE
|
||||||
|
text = ''
|
||||||
|
if orientation == Qt.Horizontal:
|
||||||
|
if section < len(self.HEADERS):
|
||||||
|
text = self.HEADERS[section]
|
||||||
|
return QVariant(text)
|
||||||
|
else:
|
||||||
|
return QVariant(section+1)
|
||||||
|
|
||||||
|
def data(self, index, role):
|
||||||
|
row, col = index.row(), index.column()
|
||||||
|
result = self.matches[row]
|
||||||
|
if role in (Qt.DisplayRole, Qt.EditRole):
|
||||||
|
if col == 1:
|
||||||
|
return QVariant('<b>%s</b><br><i>%s</i>' % (result.name, result.description))
|
||||||
|
elif col == 3:
|
||||||
|
return QVariant(result.headquarters)
|
||||||
|
elif col == 4:
|
||||||
|
return QVariant(', '.join(result.formats).upper())
|
||||||
|
elif role == Qt.DecorationRole:
|
||||||
|
if col == 2:
|
||||||
|
if result.drm_free_only:
|
||||||
|
return QVariant(self.NO_DRM_ICON)
|
||||||
|
elif role == Qt.CheckStateRole:
|
||||||
|
if col == 0:
|
||||||
|
if is_disabled(result):
|
||||||
|
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):
|
||||||
|
if not index.isValid():
|
||||||
|
return False
|
||||||
|
row, col = index.row(), index.column()
|
||||||
|
if col == 0:
|
||||||
|
if data.toBool():
|
||||||
|
enable_plugin(self.get_plugin(index))
|
||||||
|
else:
|
||||||
|
disable_plugin(self.get_plugin(index))
|
||||||
|
self.dataChanged.emit(self.index(index.row(), 0), self.index(index.row(), self.columnCount() - 1))
|
||||||
|
return True
|
||||||
|
|
||||||
|
def flags(self, index):
|
||||||
|
if index.column() == 0:
|
||||||
|
return QAbstractItemModel.flags(self, index) | Qt.ItemIsUserCheckable
|
||||||
|
return QAbstractItemModel.flags(self, index)
|
||||||
|
|
||||||
|
def data_as_text(self, match, col):
|
||||||
|
text = ''
|
||||||
|
if col == 0:
|
||||||
|
text = 'b' if is_disabled(match) else 'a'
|
||||||
|
elif col == 1:
|
||||||
|
text = match.name
|
||||||
|
elif col == 2:
|
||||||
|
text = 'a' if getattr(match, 'drm_free_only', True) else 'b'
|
||||||
|
elif col == 3:
|
||||||
|
text = getattr(match, 'headquarters', '')
|
||||||
|
return text
|
||||||
|
|
||||||
|
def sort(self, col, order, reset=True):
|
||||||
|
self.sort_col = col
|
||||||
|
self.sort_order = order
|
||||||
|
if not self.matches:
|
||||||
|
return
|
||||||
|
descending = order == Qt.DescendingOrder
|
||||||
|
self.matches.sort(None,
|
||||||
|
lambda x: sort_key(unicode(self.data_as_text(x, col))),
|
||||||
|
descending)
|
||||||
|
if reset:
|
||||||
|
self.reset()
|
||||||
|
|
||||||
|
|
||||||
|
class SearchFilter(SearchQueryParser):
|
||||||
|
|
||||||
|
USABLE_LOCATIONS = [
|
||||||
|
'all',
|
||||||
|
'description',
|
||||||
|
'drm',
|
||||||
|
'enabled',
|
||||||
|
'format',
|
||||||
|
'formats',
|
||||||
|
'headquarters',
|
||||||
|
'name',
|
||||||
|
]
|
||||||
|
|
||||||
|
def __init__(self, all_plugins=[]):
|
||||||
|
SearchQueryParser.__init__(self, locations=self.USABLE_LOCATIONS)
|
||||||
|
self.srs = set(all_plugins)
|
||||||
|
|
||||||
|
def universal_set(self):
|
||||||
|
return self.srs
|
||||||
|
|
||||||
|
def get_matches(self, location, query):
|
||||||
|
location = location.lower().strip()
|
||||||
|
if location == 'formats':
|
||||||
|
location = 'format'
|
||||||
|
|
||||||
|
matchkind = CONTAINS_MATCH
|
||||||
|
if len(query) > 1:
|
||||||
|
if query.startswith('\\'):
|
||||||
|
query = query[1:]
|
||||||
|
elif query.startswith('='):
|
||||||
|
matchkind = EQUALS_MATCH
|
||||||
|
query = query[1:]
|
||||||
|
elif query.startswith('~'):
|
||||||
|
matchkind = REGEXP_MATCH
|
||||||
|
query = query[1:]
|
||||||
|
if matchkind != REGEXP_MATCH: ### leave case in regexps because it can be significant e.g. \S \W \D
|
||||||
|
query = query.lower()
|
||||||
|
|
||||||
|
if location not in self.USABLE_LOCATIONS:
|
||||||
|
return set([])
|
||||||
|
matches = set([])
|
||||||
|
all_locs = set(self.USABLE_LOCATIONS) - set(['all'])
|
||||||
|
locations = all_locs if location == 'all' else [location]
|
||||||
|
q = {
|
||||||
|
'description': lambda x: x.description.lower(),
|
||||||
|
'drm': lambda x: not x.drm_free_only,
|
||||||
|
'enabled': lambda x: not is_disabled(x),
|
||||||
|
'format': lambda x: ','.join(x.formats).lower(),
|
||||||
|
'headquarters': lambda x: x.headquarters.lower(),
|
||||||
|
'name': lambda x : x.name.lower(),
|
||||||
|
}
|
||||||
|
q['formats'] = q['format']
|
||||||
|
for sr in self.srs:
|
||||||
|
for locvalue in locations:
|
||||||
|
accessor = q[locvalue]
|
||||||
|
if query == 'true':
|
||||||
|
if locvalue in ('drm', 'enabled'):
|
||||||
|
if accessor(sr) == True:
|
||||||
|
matches.add(sr)
|
||||||
|
elif accessor(sr) is not None:
|
||||||
|
matches.add(sr)
|
||||||
|
continue
|
||||||
|
if query == 'false':
|
||||||
|
if locvalue in ('drm', 'enabled'):
|
||||||
|
if accessor(sr) == False:
|
||||||
|
matches.add(sr)
|
||||||
|
elif accessor(sr) is None:
|
||||||
|
matches.add(sr)
|
||||||
|
continue
|
||||||
|
# this is bool, so can't match below
|
||||||
|
if locvalue in ('drm', 'enabled'):
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
### Can't separate authors because comma is used for name sep and author sep
|
||||||
|
### Exact match might not get what you want. For that reason, turn author
|
||||||
|
### exactmatch searches into contains searches.
|
||||||
|
if locvalue == 'name' and matchkind == EQUALS_MATCH:
|
||||||
|
m = CONTAINS_MATCH
|
||||||
|
else:
|
||||||
|
m = matchkind
|
||||||
|
|
||||||
|
if locvalue == 'format':
|
||||||
|
vals = accessor(sr).split(',')
|
||||||
|
else:
|
||||||
|
vals = [accessor(sr)]
|
||||||
|
if _match(query, vals, m):
|
||||||
|
matches.add(sr)
|
||||||
|
break
|
||||||
|
except ValueError: # Unicode errors
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
return matches
|
34
src/calibre/gui2/store/config/chooser/results_view.py
Normal file
34
src/calibre/gui2/store/config/chooser/results_view.py
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from __future__ import (unicode_literals, division, absolute_import, print_function)
|
||||||
|
|
||||||
|
__license__ = 'GPL 3'
|
||||||
|
__copyright__ = '2011, John Schember <john@nachtimwald.com>'
|
||||||
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
from PyQt4.Qt import (Qt, QTreeView, QSize)
|
||||||
|
|
||||||
|
from calibre.customize.ui import store_plugins
|
||||||
|
from calibre.gui2.metadata.single_download import RichTextDelegate
|
||||||
|
from calibre.gui2.store.config.chooser.models import Matches
|
||||||
|
|
||||||
|
class ResultsView(QTreeView):
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
QTreeView.__init__(self,*args)
|
||||||
|
|
||||||
|
self._model = Matches([p for p in store_plugins()])
|
||||||
|
self.setModel(self._model)
|
||||||
|
|
||||||
|
self.setIconSize(QSize(24, 24))
|
||||||
|
|
||||||
|
self.rt_delegate = RichTextDelegate(self)
|
||||||
|
|
||||||
|
for i in self._model.HTML_COLS:
|
||||||
|
self.setItemDelegateForColumn(i, self.rt_delegate)
|
||||||
|
|
||||||
|
for i in xrange(self._model.columnCount()):
|
||||||
|
self.resizeColumnToContents(i)
|
||||||
|
|
||||||
|
self.model().sort(1, Qt.AscendingOrder)
|
||||||
|
self.header().setSortIndicator(self.model().sort_col, self.model().sort_order)
|
0
src/calibre/gui2/store/config/search/__init__.py
Normal file
0
src/calibre/gui2/store/config/search/__init__.py
Normal file
@ -9,7 +9,7 @@ __docformat__ = 'restructuredtext en'
|
|||||||
from PyQt4.Qt import QWidget
|
from PyQt4.Qt import QWidget
|
||||||
|
|
||||||
from calibre.gui2 import JSONConfig
|
from calibre.gui2 import JSONConfig
|
||||||
from calibre.gui2.store.config.search_widget_ui import Ui_Form
|
from calibre.gui2.store.config.search.search_widget_ui import Ui_Form
|
||||||
|
|
||||||
class StoreConfigWidget(QWidget, Ui_Form):
|
class StoreConfigWidget(QWidget, Ui_Form):
|
||||||
|
|
@ -93,13 +93,13 @@
|
|||||||
<item>
|
<item>
|
||||||
<widget class="QGroupBox" name="groupBox_3">
|
<widget class="QGroupBox" name="groupBox_3">
|
||||||
<property name="title">
|
<property name="title">
|
||||||
<string>Performance</string>
|
<string>Threads</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QFormLayout" name="formLayout_3">
|
<layout class="QFormLayout" name="formLayout_3">
|
||||||
<item row="0" column="0">
|
<item row="0" column="0">
|
||||||
<widget class="QLabel" name="label_4">
|
<widget class="QLabel" name="label_4">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Number of simultaneous searches</string>
|
<string>Number of search threads to use</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@ -113,7 +113,7 @@
|
|||||||
<item row="1" column="0">
|
<item row="1" column="0">
|
||||||
<widget class="QLabel" name="label_5">
|
<widget class="QLabel" name="label_5">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Number of simultaneous cache updates</string>
|
<string>Number of cache update threads to use</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@ -127,7 +127,7 @@
|
|||||||
<item row="2" column="0">
|
<item row="2" column="0">
|
||||||
<widget class="QLabel" name="label_6">
|
<widget class="QLabel" name="label_6">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Number of simultaneous cover downloads</string>
|
<string>Number of conver download threads to use</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@ -141,7 +141,7 @@
|
|||||||
<item row="3" column="0">
|
<item row="3" column="0">
|
||||||
<widget class="QLabel" name="label_7">
|
<widget class="QLabel" name="label_7">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Number of simultaneous details downloads</string>
|
<string>Number of details threads to use</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
@ -11,7 +11,7 @@ Config widget access functions for configuring the store action.
|
|||||||
'''
|
'''
|
||||||
|
|
||||||
def config_widget():
|
def config_widget():
|
||||||
from calibre.gui2.store.config.search_widget import StoreConfigWidget
|
from calibre.gui2.store.config.search.search_widget import StoreConfigWidget
|
||||||
return StoreConfigWidget()
|
return StoreConfigWidget()
|
||||||
|
|
||||||
def save_settings(config_widget):
|
def save_settings(config_widget):
|
||||||
|
75
src/calibre/gui2/store/legimi_plugin.py
Normal file
75
src/calibre/gui2/store/legimi_plugin.py
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
# -*- 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 LegimiStore(BasicStoreConfig, StorePlugin):
|
||||||
|
|
||||||
|
def open(self, parent=None, detail_item=None, external=False):
|
||||||
|
|
||||||
|
url = 'http://www.legimi.com/pl/ebooks/?price=any'
|
||||||
|
detail_url = None
|
||||||
|
|
||||||
|
if detail_item:
|
||||||
|
detail_url = detail_item
|
||||||
|
|
||||||
|
if external or self.config.get('open_external', False):
|
||||||
|
open_url(QUrl(url_slash_cleaner(detail_url if detail_url 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://www.legimi.com/pl/ebooks/?price=any&lang=pl&search=' + urllib.quote_plus(query.encode('utf-8')) + '&sort=relevance'
|
||||||
|
|
||||||
|
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[@class="list"]/ul/li'):
|
||||||
|
if counter <= 0:
|
||||||
|
break
|
||||||
|
|
||||||
|
id = ''.join(data.xpath('.//div[@class="item_cover_container"]/a[1]/@href'))
|
||||||
|
if not id:
|
||||||
|
continue
|
||||||
|
|
||||||
|
cover_url = ''.join(data.xpath('.//div[@class="item_cover_container"]/a/img/@src'))
|
||||||
|
title = ''.join(data.xpath('.//div[@class="item_entries"]/h2/a/text()'))
|
||||||
|
author = ''.join(data.xpath('.//div[@class="item_entries"]/span[1]/a/text()'))
|
||||||
|
price = ''.join(data.xpath('.//div[@class="item_entries"]/span[3]/text()'))
|
||||||
|
price = re.sub(r'[^0-9,]*','',price) + ' zł'
|
||||||
|
|
||||||
|
counter -= 1
|
||||||
|
|
||||||
|
s = SearchResult()
|
||||||
|
s.cover_url = 'http://www.legimi.com/' + cover_url
|
||||||
|
s.title = title.strip()
|
||||||
|
s.author = author.strip()
|
||||||
|
s.price = price
|
||||||
|
s.detail_item = 'http://www.legimi.com/' + id.strip()
|
||||||
|
s.drm = SearchResult.DRM_LOCKED
|
||||||
|
s.formats = 'EPUB'
|
||||||
|
|
||||||
|
yield s
|
@ -47,6 +47,7 @@ class BooksModel(QAbstractItemModel):
|
|||||||
self.books = list(self.search_filter.parse(self.filter))
|
self.books = list(self.search_filter.parse(self.filter))
|
||||||
except:
|
except:
|
||||||
self.books = self.all_books
|
self.books = self.all_books
|
||||||
|
self.layoutChanged.emit()
|
||||||
self.sort(self.sort_col, self.sort_order)
|
self.sort(self.sort_col, self.sort_order)
|
||||||
self.total_changed.emit(self.rowCount())
|
self.total_changed.emit(self.rowCount())
|
||||||
|
|
||||||
|
@ -116,7 +116,7 @@ class AdvSearchBuilderDialog(QDialog, Ui_Dialog):
|
|||||||
if price:
|
if price:
|
||||||
ans.append('price:"' + self.mc + price + '"')
|
ans.append('price:"' + self.mc + price + '"')
|
||||||
format = unicode(self.format_box.text()).strip()
|
format = unicode(self.format_box.text()).strip()
|
||||||
if author:
|
if format:
|
||||||
ans.append('format:"' + self.mc + format + '"')
|
ans.append('format:"' + self.mc + format + '"')
|
||||||
if ans:
|
if ans:
|
||||||
return ' and '.join(ans)
|
return ' and '.join(ans)
|
||||||
|
@ -10,11 +10,12 @@ import re
|
|||||||
from random import shuffle
|
from random import shuffle
|
||||||
|
|
||||||
from PyQt4.Qt import (Qt, QDialog, QDialogButtonBox, QTimer, QCheckBox,
|
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 import JSONConfig, info_dialog
|
||||||
from calibre.gui2.progress_indicator import ProgressIndicator
|
from calibre.gui2.progress_indicator import ProgressIndicator
|
||||||
from calibre.gui2.store.config.search_widget import StoreConfigWidget
|
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.adv_search_builder import AdvSearchBuilderDialog
|
||||||
from calibre.gui2.store.search.download_thread import SearchThreadPool, \
|
from calibre.gui2.store.search.download_thread import SearchThreadPool, \
|
||||||
CacheUpdateThreadPool
|
CacheUpdateThreadPool
|
||||||
@ -22,7 +23,7 @@ from calibre.gui2.store.search.search_ui import Ui_Dialog
|
|||||||
|
|
||||||
class SearchDialog(QDialog, 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)
|
QDialog.__init__(self, parent)
|
||||||
self.setupUi(self)
|
self.setupUi(self)
|
||||||
|
|
||||||
@ -34,8 +35,7 @@ class SearchDialog(QDialog, Ui_Dialog):
|
|||||||
# the variables it sets up are used later.
|
# the variables it sets up are used later.
|
||||||
self.load_settings()
|
self.load_settings()
|
||||||
|
|
||||||
# We keep a cache of store plugins and reference them by name.
|
self.gui = gui
|
||||||
self.store_plugins = istores
|
|
||||||
|
|
||||||
# Setup our worker threads.
|
# Setup our worker threads.
|
||||||
self.search_pool = SearchThreadPool(self.search_thread_count)
|
self.search_pool = SearchThreadPool(self.search_thread_count)
|
||||||
@ -49,22 +49,11 @@ class SearchDialog(QDialog, Ui_Dialog):
|
|||||||
self.hang_check = 0
|
self.hang_check = 0
|
||||||
|
|
||||||
# Update store caches silently.
|
# 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)
|
self.cache_pool.add_task(p, self.timeout)
|
||||||
|
|
||||||
# Add check boxes for each store so the user
|
self.store_checks = {}
|
||||||
# can disable searching specific stores on a
|
self.setup_store_checks()
|
||||||
# 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)
|
|
||||||
|
|
||||||
# Set the search query
|
# Set the search query
|
||||||
self.search_edit.setText(query)
|
self.search_edit.setText(query)
|
||||||
@ -92,6 +81,27 @@ class SearchDialog(QDialog, Ui_Dialog):
|
|||||||
|
|
||||||
self.restore_state()
|
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):
|
def build_adv_search(self):
|
||||||
adv = AdvSearchBuilderDialog(self)
|
adv = AdvSearchBuilderDialog(self)
|
||||||
if adv.exec_() == QDialog.Accepted:
|
if adv.exec_() == QDialog.Accepted:
|
||||||
@ -126,11 +136,12 @@ class SearchDialog(QDialog, Ui_Dialog):
|
|||||||
# futher filtering.
|
# futher filtering.
|
||||||
self.results_view.model().set_query(query)
|
self.results_view.model().set_query(query)
|
||||||
|
|
||||||
# Plugins are in alphebetic order. Randomize the
|
# Plugins are in random order that does not change.
|
||||||
# order of plugin names. This way plugins closer
|
# 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
|
# to a don't have an unfair advantage over
|
||||||
# plugins further from a.
|
# plugins further from a.
|
||||||
store_names = self.store_plugins.keys()
|
store_names = self.store_checks.keys()
|
||||||
if not store_names:
|
if not store_names:
|
||||||
return
|
return
|
||||||
# Remove all of our internal filtering logic from the query.
|
# Remove all of our internal filtering logic from the query.
|
||||||
@ -138,8 +149,8 @@ class SearchDialog(QDialog, Ui_Dialog):
|
|||||||
shuffle(store_names)
|
shuffle(store_names)
|
||||||
# Add plugins that the user has checked to the search pool's work queue.
|
# Add plugins that the user has checked to the search pool's work queue.
|
||||||
for n in store_names:
|
for n in store_names:
|
||||||
if getattr(self, 'store_check_' + n).isChecked():
|
if self.store_checks[n].isChecked():
|
||||||
self.search_pool.add_task(query, n, self.store_plugins[n], self.max_results, self.timeout)
|
self.search_pool.add_task(query, n, self.gui.istores[n], self.max_results, self.timeout)
|
||||||
self.hang_check = 0
|
self.hang_check = 0
|
||||||
self.checker.start(100)
|
self.checker.start(100)
|
||||||
self.pi.startAnimation()
|
self.pi.startAnimation()
|
||||||
@ -179,8 +190,8 @@ class SearchDialog(QDialog, Ui_Dialog):
|
|||||||
self.config['open_external'] = self.open_external.isChecked()
|
self.config['open_external'] = self.open_external.isChecked()
|
||||||
|
|
||||||
store_check = {}
|
store_check = {}
|
||||||
for n in self.store_plugins:
|
for k, v in self.store_checks.items():
|
||||||
store_check[n] = getattr(self, 'store_check_' + n).isChecked()
|
store_check[k] = v.isChecked()
|
||||||
self.config['store_checked'] = store_check
|
self.config['store_checked'] = store_check
|
||||||
|
|
||||||
def restore_state(self):
|
def restore_state(self):
|
||||||
@ -206,8 +217,8 @@ class SearchDialog(QDialog, Ui_Dialog):
|
|||||||
store_check = self.config.get('store_checked', None)
|
store_check = self.config.get('store_checked', None)
|
||||||
if store_check:
|
if store_check:
|
||||||
for n in store_check:
|
for n in store_check:
|
||||||
if hasattr(self, 'store_check_' + n):
|
if n in self.store_checks:
|
||||||
getattr(self, 'store_check_' + n).setChecked(store_check[n])
|
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_col = self.config.get('sort_col', 2)
|
||||||
self.results_view.model().sort_order = self.config.get('sort_order', Qt.AscendingOrder)
|
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()
|
self.config['open_external'] = self.open_external.isChecked()
|
||||||
|
|
||||||
d = QDialog(self)
|
d = QDialog(self)
|
||||||
button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
button_box = QDialogButtonBox(QDialogButtonBox.Close)
|
||||||
v = QVBoxLayout(d)
|
v = QVBoxLayout(d)
|
||||||
button_box.accepted.connect(d.accept)
|
button_box.accepted.connect(d.accept)
|
||||||
button_box.rejected.connect(d.reject)
|
button_box.rejected.connect(d.reject)
|
||||||
d.setWindowTitle(_('Customize Get Books'))
|
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)
|
v.addWidget(button_box)
|
||||||
|
|
||||||
d.exec_()
|
chooser_config_widget = StoreChooserWidget()
|
||||||
|
search_config_widget = StoreConfigWidget(self.config)
|
||||||
|
|
||||||
if d.result() == QDialog.Accepted:
|
tab_widget.addTab(chooser_config_widget, _('Choose stores'))
|
||||||
config_widget.save_settings()
|
tab_widget.addTab(search_config_widget, _('Configure search'))
|
||||||
|
|
||||||
|
d.exec_()
|
||||||
|
search_config_widget.save_settings()
|
||||||
self.config_changed()
|
self.config_changed()
|
||||||
|
self.gui.load_store_plugins()
|
||||||
|
self.setup_store_checks()
|
||||||
|
|
||||||
def config_changed(self):
|
def config_changed(self):
|
||||||
self.load_settings()
|
self.load_settings()
|
||||||
@ -283,7 +301,7 @@ class SearchDialog(QDialog, Ui_Dialog):
|
|||||||
|
|
||||||
def open_store(self, index):
|
def open_store(self, index):
|
||||||
result = self.results_view.model().get_result(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):
|
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():
|
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():
|
if not self.pi.isAnimated():
|
||||||
self.pi.startAnimation()
|
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):
|
def stores_select_all(self):
|
||||||
for check in self.get_store_checks():
|
for check in self.store_checks.values():
|
||||||
check.setChecked(True)
|
check.setChecked(True)
|
||||||
|
|
||||||
def stores_select_invert(self):
|
def stores_select_invert(self):
|
||||||
for check in self.get_store_checks():
|
for check in self.store_checks.values():
|
||||||
check.setChecked(not check.isChecked())
|
check.setChecked(not check.isChecked())
|
||||||
|
|
||||||
def stores_select_none(self):
|
def stores_select_none(self):
|
||||||
for check in self.get_store_checks():
|
for check in self.store_checks.values():
|
||||||
check.setChecked(False)
|
check.setChecked(False)
|
||||||
|
|
||||||
def dialog_closed(self, result):
|
def dialog_closed(self, result):
|
||||||
|
@ -82,8 +82,8 @@
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>102</width>
|
<width>125</width>
|
||||||
<height>129</height>
|
<height>127</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
@ -159,6 +159,9 @@
|
|||||||
<property name="expandsOnDoubleClick">
|
<property name="expandsOnDoubleClick">
|
||||||
<bool>false</bool>
|
<bool>false</bool>
|
||||||
</property>
|
</property>
|
||||||
|
<attribute name="headerStretchLastSection">
|
||||||
|
<bool>false</bool>
|
||||||
|
</attribute>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
|
72
src/calibre/gui2/store/virtualo_plugin.py
Normal file
72
src/calibre/gui2/store/virtualo_plugin.py
Normal 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 + ' zł'
|
||||||
|
s.detail_item = 'http://virtualo.pl' + id.strip()
|
||||||
|
s.formats = formats.upper().strip()
|
||||||
|
s.drm = SearchResult.DRM_UNKNOWN
|
||||||
|
|
||||||
|
yield s
|
@ -46,6 +46,64 @@ class TestEmail(QDialog, TE_Dialog):
|
|||||||
finally:
|
finally:
|
||||||
self.test_button.setEnabled(True)
|
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):
|
class SendEmail(QWidget, Ui_Form):
|
||||||
|
|
||||||
@ -129,7 +187,8 @@ class SendEmail(QWidget, Ui_Form):
|
|||||||
'port': 587,
|
'port': 587,
|
||||||
'username': '@gmail.com',
|
'username': '@gmail.com',
|
||||||
'url': 'www.gmail.com',
|
'url': 'www.gmail.com',
|
||||||
'extra': ''
|
'extra': '',
|
||||||
|
'at_in_username': True,
|
||||||
},
|
},
|
||||||
'hotmail': {
|
'hotmail': {
|
||||||
'name': 'Hotmail',
|
'name': 'Hotmail',
|
||||||
@ -143,53 +202,10 @@ class SendEmail(QWidget, Ui_Form):
|
|||||||
' will let calibre send email. In this case, I'
|
' will let calibre send email. In this case, I'
|
||||||
' strongly suggest you setup a free gmail account'
|
' strongly suggest you setup a free gmail account'
|
||||||
' instead.'),
|
' instead.'),
|
||||||
|
'at_in_username': True,
|
||||||
}
|
}
|
||||||
}[service]
|
}[service]
|
||||||
d = QDialog(self)
|
d = RelaySetup(service, 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)
|
|
||||||
if d.exec_() != d.Accepted:
|
if d.exec_() != d.Accepted:
|
||||||
return
|
return
|
||||||
self.relay_username.setText(d.username.text())
|
self.relay_username.setText(d.username.text())
|
||||||
|
@ -221,7 +221,12 @@ class LibraryServer(ContentServer, MobileServer, XMLServer, OPDSServer, Cache,
|
|||||||
if not ip or ip.startswith('127.'):
|
if not ip or ip.startswith('127.'):
|
||||||
raise
|
raise
|
||||||
cherrypy.log('Trying to bind to single interface: '+ip)
|
cherrypy.log('Trying to bind to single interface: '+ip)
|
||||||
|
# Change the host we listen on
|
||||||
cherrypy.config.update({'server.socket_host' : ip})
|
cherrypy.config.update({'server.socket_host' : ip})
|
||||||
|
# This ensures that the change is actually applied
|
||||||
|
cherrypy.server.socket_host = ip
|
||||||
|
cherrypy.server.httpserver = cherrypy.server.instance = None
|
||||||
|
|
||||||
cherrypy.engine.start()
|
cherrypy.engine.start()
|
||||||
|
|
||||||
self.is_running = True
|
self.is_running = True
|
||||||
@ -231,6 +236,8 @@ class LibraryServer(ContentServer, MobileServer, XMLServer, OPDSServer, Cache,
|
|||||||
cherrypy.engine.block()
|
cherrypy.engine.block()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.exception = e
|
self.exception = e
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
finally:
|
finally:
|
||||||
self.is_running = False
|
self.is_running = False
|
||||||
try:
|
try:
|
||||||
|
@ -356,7 +356,7 @@ class PostInstall:
|
|||||||
mimetypes = set([])
|
mimetypes = set([])
|
||||||
for x in all_input_formats():
|
for x in all_input_formats():
|
||||||
mt = guess_type('dummy.'+x)[0]
|
mt = guess_type('dummy.'+x)[0]
|
||||||
if mt and 'chemical' not in mt:
|
if mt and 'chemical' not in mt and 'ctc-posml' not in mt:
|
||||||
mimetypes.add(mt)
|
mimetypes.add(mt)
|
||||||
|
|
||||||
def write_mimetypes(f):
|
def write_mimetypes(f):
|
||||||
@ -376,11 +376,10 @@ class PostInstall:
|
|||||||
des = ('calibre-gui.desktop', 'calibre-lrfviewer.desktop',
|
des = ('calibre-gui.desktop', 'calibre-lrfviewer.desktop',
|
||||||
'calibre-ebook-viewer.desktop')
|
'calibre-ebook-viewer.desktop')
|
||||||
for x in des:
|
for x in des:
|
||||||
cmd = ['xdg-desktop-menu', 'install', './'+x]
|
cmd = ['xdg-desktop-menu', 'install', '--noupdate', './'+x]
|
||||||
if x != des[-1]:
|
|
||||||
cmd.insert(2, '--noupdate')
|
|
||||||
check_call(' '.join(cmd), shell=True)
|
check_call(' '.join(cmd), shell=True)
|
||||||
self.menu_resources.append(x)
|
self.menu_resources.append(x)
|
||||||
|
check_call(['xdg-desktop-menu', 'forceupdate'])
|
||||||
f = open('calibre-mimetypes', 'wb')
|
f = open('calibre-mimetypes', 'wb')
|
||||||
f.write(MIME)
|
f.write(MIME)
|
||||||
f.close()
|
f.close()
|
||||||
|
@ -138,7 +138,7 @@ Follow these steps to find the problem:
|
|||||||
My device is non-standard or unusual. What can I do to connect to it?
|
My device is non-standard or unusual. What can I do to connect to it?
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
In addition to the :guilabel:`Connect to Folder` function found under the Connect/Share button, |app| provides a ``User Defined`` device plugin that can be used to connect to any USB device that presents that shows up as a disk drive in your operating system. See the device plugin ``Preferences -> Plugins -> Device Plugins -> User Defined`` and ``Preferences -> Miscellaneous -> Get information to setup the user defined device`` for more information.
|
In addition to the :guilabel:`Connect to Folder` function found under the Connect/Share button, |app| provides a ``User Defined`` device plugin that can be used to connect to any USB device that shows up as a disk drive in your operating system. Note: on windows, the device must have a drive letter for calibre to use it. See the device plugin ``Preferences -> Plugins -> Device Plugins -> User Defined`` and ``Preferences -> Miscellaneous -> Get information to setup the user defined device`` for more information.
|
||||||
|
|
||||||
How does |app| manage collections on my SONY reader?
|
How does |app| manage collections on my SONY reader?
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
@ -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
|
/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::
|
You can email downloaded news with the command::
|
||||||
|
|
||||||
|
@ -229,13 +229,14 @@ For various values of series_index, the program returns:
|
|||||||
|
|
||||||
The following functions are available in addition to those described in single-function mode. Remember from the example above that the single-function mode functions require an additional first parameter specifying the field to operate on. With the exception of the ``id`` parameter of assign, all parameters can be statements (sequences of expressions):
|
The following functions are available in addition to those described in single-function mode. Remember from the example above that the single-function mode functions require an additional first parameter specifying the field to operate on. With the exception of the ``id`` parameter of assign, all parameters can be statements (sequences of expressions):
|
||||||
|
|
||||||
|
* ``and(value, value, ...)`` -- returns the string "1" if all values are not empty, otherwise returns the empty string. This function works well with test or first_non_empty. You can have as many values as you want.
|
||||||
* ``add(x, y)`` -- returns x + y. Throws an exception if either x or y are not numbers.
|
* ``add(x, y)`` -- returns x + y. Throws an exception if either x or y are not numbers.
|
||||||
* ``assign(id, val)`` -- assigns val to id, then returns val. id must be an identifier, not an expression
|
* ``assign(id, val)`` -- assigns val to id, then returns val. id must be an identifier, not an expression
|
||||||
* ``booksize()`` -- returns the value of the |app| 'size' field. Returns '' if there are no formats.
|
* ``booksize()`` -- returns the value of the |app| 'size' field. Returns '' if there are no formats.
|
||||||
* ``cmp(x, y, lt, eq, gt)`` -- compares x and y after converting both to numbers. Returns ``lt`` if x < y. Returns ``eq`` if x == y. Otherwise returns ``gt``.
|
* ``cmp(x, y, lt, eq, gt)`` -- compares x and y after converting both to numbers. Returns ``lt`` if x < y. Returns ``eq`` if x == y. Otherwise returns ``gt``.
|
||||||
* ``divide(x, y)`` -- returns x / y. Throws an exception if either x or y are not numbers.
|
* ``divide(x, y)`` -- returns x / y. Throws an exception if either x or y are not numbers.
|
||||||
* ``field(name)`` -- returns the metadata field named by ``name``.
|
* ``field(name)`` -- returns the metadata field named by ``name``.
|
||||||
* ``first_non_empty(value, value, ...) -- returns the first value that is not empty. If all values are empty, then the empty value is returned. You can have as many values as you want.
|
* ``first_non_empty(value, value, ...)`` -- returns the first value that is not empty. If all values are empty, then the empty value is returned. You can have as many values as you want.
|
||||||
* ``format_date(x, date_format)`` -- format_date(val, format_string) -- format the value, which must be a date field, using the format_string, returning a string. The formatting codes are::
|
* ``format_date(x, date_format)`` -- format_date(val, format_string) -- format the value, which must be a date field, using the format_string, returning a string. The formatting codes are::
|
||||||
|
|
||||||
d : the day as number without a leading zero (1 to 31)
|
d : the day as number without a leading zero (1 to 31)
|
||||||
@ -251,7 +252,9 @@ The following functions are available in addition to those described in single-f
|
|||||||
iso : the date with time and timezone. Must be the only format present.
|
iso : the date with time and timezone. Must be the only format present.
|
||||||
|
|
||||||
* ``eval(string)`` -- evaluates the string as a program, passing the local variables (those ``assign`` ed to). This permits using the template processor to construct complex results from local variables.
|
* ``eval(string)`` -- evaluates the string as a program, passing the local variables (those ``assign`` ed to). This permits using the template processor to construct complex results from local variables.
|
||||||
|
* ``not(value)`` -- returns the string "1" if the value is empty, otherwise returns the empty string. This function works well with test or first_non_empty. You can have as many values as you want.
|
||||||
* ``multiply(x, y)`` -- returns x * y. Throws an exception if either x or y are not numbers.
|
* ``multiply(x, y)`` -- returns x * y. Throws an exception if either x or y are not numbers.
|
||||||
|
* ``or(value, value, ...)`` -- returns the string "1" if any value is not empty, otherwise returns the empty string. This function works well with test or first_non_empty. You can have as many values as you want.
|
||||||
* ``print(a, b, ...)`` -- prints the arguments to standard output. Unless you start calibre from the command line (``calibre-debug -g``), the output will go to a black hole.
|
* ``print(a, b, ...)`` -- prints the arguments to standard output. Unless you start calibre from the command line (``calibre-debug -g``), the output will go to a black hole.
|
||||||
* ``raw_field(name)`` -- returns the metadata field named by name without applying any formatting.
|
* ``raw_field(name)`` -- returns the metadata field named by name without applying any formatting.
|
||||||
* ``strcat(a, b, ...)`` -- can take any number of arguments. Returns a string formed by concatenating all the arguments.
|
* ``strcat(a, b, ...)`` -- can take any number of arguments. Returns a string formed by concatenating all the arguments.
|
||||||
@ -260,6 +263,21 @@ The following functions are available in addition to those described in single-f
|
|||||||
* ``subtract(x, y)`` -- returns x - y. Throws an exception if either x or y are not numbers.
|
* ``subtract(x, y)`` -- returns x - y. Throws an exception if either x or y are not numbers.
|
||||||
* ``template(x)`` -- evaluates x as a template. The evaluation is done in its own context, meaning that variables are not shared between the caller and the template evaluation. Because the `{` and `}` characters are special, you must use `[[` for the `{` character and `]]` for the '}' character; they are converted automatically. For example, ``template('[[title_sort]]') will evaluate the template ``{title_sort}`` and return its value.
|
* ``template(x)`` -- evaluates x as a template. The evaluation is done in its own context, meaning that variables are not shared between the caller and the template evaluation. Because the `{` and `}` characters are special, you must use `[[` for the `{` character and `]]` for the '}' character; they are converted automatically. For example, ``template('[[title_sort]]') will evaluate the template ``{title_sort}`` and return its value.
|
||||||
|
|
||||||
|
Function classification summary:
|
||||||
|
|
||||||
|
* Get values from metadata: ``field``. ``raw_field``. In some situations, ``lookup`` can be used in place of ``field``.
|
||||||
|
* Arithmetic: ``add``, ``subtract``, ``multiply``, ``divide``
|
||||||
|
* Boolean: ``and``, ``or``, ``not``. The function ``if_empty`` is similar to ``and`` called with one argument.
|
||||||
|
* If-then-else: ``contains``, ``test``
|
||||||
|
* Iterating over values: ``first_non_empty``, ``lookup``, ``switch``
|
||||||
|
* List lookup: ``in_list``, ``list_item``, ``select``,
|
||||||
|
* List manipulation: ``count``, ``sublist``, ``subitems``
|
||||||
|
* Recursion: ``eval``, ``template``
|
||||||
|
* Relational: ``cmp`` , ``strcmp`` for strings
|
||||||
|
* String case changes: ``lowercase``, ``uppercase``, ``titlecase``, ``capitalize``
|
||||||
|
* String manipulation: ``re``, ``shorten``, ``substr``
|
||||||
|
* Other: ``assign``, ``booksize``, ``print``, ``format_date``,
|
||||||
|
|
||||||
.. _general_mode:
|
.. _general_mode:
|
||||||
|
|
||||||
Using general program mode
|
Using general program mode
|
||||||
|
@ -594,7 +594,56 @@ class BuiltinFirstNonEmpty(BuiltinFormatterFunction):
|
|||||||
i += 1
|
i += 1
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
|
class BuiltinAnd(BuiltinFormatterFunction):
|
||||||
|
name = 'and'
|
||||||
|
arg_count = -1
|
||||||
|
doc = _('and(value, value, ...) -- '
|
||||||
|
'returns the string "1" if all values are not empty, otherwise '
|
||||||
|
'returns the empty string. This function works well with test or '
|
||||||
|
'first_non_empty. You can have as many values as you want.')
|
||||||
|
|
||||||
|
def evaluate(self, formatter, kwargs, mi, locals, *args):
|
||||||
|
i = 0
|
||||||
|
while i < len(args):
|
||||||
|
if not args[i]:
|
||||||
|
return ''
|
||||||
|
i += 1
|
||||||
|
return '1'
|
||||||
|
|
||||||
|
class BuiltinOr(BuiltinFormatterFunction):
|
||||||
|
name = 'or'
|
||||||
|
arg_count = -1
|
||||||
|
doc = _('or(value, value, ...) -- '
|
||||||
|
'returns the string "1" if any value is not empty, otherwise '
|
||||||
|
'returns the empty string. This function works well with test or '
|
||||||
|
'first_non_empty. You can have as many values as you want.')
|
||||||
|
|
||||||
|
def evaluate(self, formatter, kwargs, mi, locals, *args):
|
||||||
|
i = 0
|
||||||
|
while i < len(args):
|
||||||
|
if args[i]:
|
||||||
|
return '1'
|
||||||
|
i += 1
|
||||||
|
return ''
|
||||||
|
|
||||||
|
class BuiltinNot(BuiltinFormatterFunction):
|
||||||
|
name = 'not'
|
||||||
|
arg_count = 1
|
||||||
|
doc = _('not(value) -- '
|
||||||
|
'returns the string "1" if the value is empty, otherwise '
|
||||||
|
'returns the empty string. This function works well with test or '
|
||||||
|
'first_non_empty. You can have as many values as you want.')
|
||||||
|
|
||||||
|
def evaluate(self, formatter, kwargs, mi, locals, *args):
|
||||||
|
i = 0
|
||||||
|
while i < len(args):
|
||||||
|
if args[i]:
|
||||||
|
return '1'
|
||||||
|
i += 1
|
||||||
|
return ''
|
||||||
|
|
||||||
builtin_add = BuiltinAdd()
|
builtin_add = BuiltinAdd()
|
||||||
|
builtin_and = BuiltinAnd()
|
||||||
builtin_assign = BuiltinAssign()
|
builtin_assign = BuiltinAssign()
|
||||||
builtin_booksize = BuiltinBooksize()
|
builtin_booksize = BuiltinBooksize()
|
||||||
builtin_capitalize = BuiltinCapitalize()
|
builtin_capitalize = BuiltinCapitalize()
|
||||||
@ -612,6 +661,8 @@ builtin_list_item = BuiltinListitem()
|
|||||||
builtin_lookup = BuiltinLookup()
|
builtin_lookup = BuiltinLookup()
|
||||||
builtin_lowercase = BuiltinLowercase()
|
builtin_lowercase = BuiltinLowercase()
|
||||||
builtin_multiply = BuiltinMultiply()
|
builtin_multiply = BuiltinMultiply()
|
||||||
|
builtin_not = BuiltinNot()
|
||||||
|
builtin_or = BuiltinOr()
|
||||||
builtin_print = BuiltinPrint()
|
builtin_print = BuiltinPrint()
|
||||||
builtin_raw_field = BuiltinRaw_field()
|
builtin_raw_field = BuiltinRaw_field()
|
||||||
builtin_re = BuiltinRe()
|
builtin_re = BuiltinRe()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user