mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Sync to trunk.
This commit is contained in:
commit
fe11525aaa
Binary file not shown.
152
resources/jquery.simulate.js
Normal file
152
resources/jquery.simulate.js
Normal file
@ -0,0 +1,152 @@
|
||||
/*
|
||||
* jquery.simulate - simulate browser mouse and keyboard events
|
||||
*
|
||||
* Copyright (c) 2009 Eduardo Lundgren (eduardolundgren@gmail.com)
|
||||
* and Richard D. Worth (rdworth@gmail.com)
|
||||
*
|
||||
* Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
|
||||
* and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
|
||||
*
|
||||
*/
|
||||
|
||||
;(function($) {
|
||||
|
||||
$.fn.extend({
|
||||
simulate: function(type, options) {
|
||||
return this.each(function() {
|
||||
var opt = $.extend({}, $.simulate.defaults, options || {});
|
||||
new $.simulate(this, type, opt);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$.simulate = function(el, type, options) {
|
||||
this.target = el;
|
||||
this.options = options;
|
||||
|
||||
if (/^drag$/.test(type)) {
|
||||
this[type].apply(this, [this.target, options]);
|
||||
} else {
|
||||
this.simulateEvent(el, type, options);
|
||||
}
|
||||
}
|
||||
|
||||
$.extend($.simulate.prototype, {
|
||||
simulateEvent: function(el, type, options) {
|
||||
var evt = this.createEvent(type, options);
|
||||
this.dispatchEvent(el, type, evt, options);
|
||||
return evt;
|
||||
},
|
||||
createEvent: function(type, options) {
|
||||
if (/^mouse(over|out|down|up|move)|(dbl)?click$/.test(type)) {
|
||||
return this.mouseEvent(type, options);
|
||||
} else if (/^key(up|down|press)$/.test(type)) {
|
||||
return this.keyboardEvent(type, options);
|
||||
}
|
||||
},
|
||||
mouseEvent: function(type, options) {
|
||||
var evt;
|
||||
var e = $.extend({
|
||||
bubbles: true, cancelable: (type != "mousemove"), view: window, detail: 0,
|
||||
screenX: 0, screenY: 0, clientX: 0, clientY: 0,
|
||||
ctrlKey: false, altKey: false, shiftKey: false, metaKey: false,
|
||||
button: 0, relatedTarget: undefined
|
||||
}, options);
|
||||
|
||||
var relatedTarget = $(e.relatedTarget)[0];
|
||||
|
||||
if ($.isFunction(document.createEvent)) {
|
||||
evt = document.createEvent("MouseEvents");
|
||||
evt.initMouseEvent(type, e.bubbles, e.cancelable, e.view, e.detail,
|
||||
e.screenX, e.screenY, e.clientX, e.clientY,
|
||||
e.ctrlKey, e.altKey, e.shiftKey, e.metaKey,
|
||||
e.button, e.relatedTarget || document.body.parentNode);
|
||||
} else if (document.createEventObject) {
|
||||
evt = document.createEventObject();
|
||||
$.extend(evt, e);
|
||||
evt.button = { 0:1, 1:4, 2:2 }[evt.button] || evt.button;
|
||||
}
|
||||
return evt;
|
||||
},
|
||||
keyboardEvent: function(type, options) {
|
||||
var evt;
|
||||
|
||||
var e = $.extend({ bubbles: true, cancelable: true, view: window,
|
||||
ctrlKey: false, altKey: false, shiftKey: false, metaKey: false,
|
||||
keyCode: 0, charCode: 0
|
||||
}, options);
|
||||
|
||||
if ($.isFunction(document.createEvent)) {
|
||||
try {
|
||||
evt = document.createEvent("KeyEvents");
|
||||
evt.initKeyEvent(type, e.bubbles, e.cancelable, e.view,
|
||||
e.ctrlKey, e.altKey, e.shiftKey, e.metaKey,
|
||||
e.keyCode, e.charCode);
|
||||
} catch(err) {
|
||||
evt = document.createEvent("Events");
|
||||
evt.initEvent(type, e.bubbles, e.cancelable);
|
||||
$.extend(evt, { view: e.view,
|
||||
ctrlKey: e.ctrlKey, altKey: e.altKey, shiftKey: e.shiftKey, metaKey: e.metaKey,
|
||||
keyCode: e.keyCode, charCode: e.charCode
|
||||
});
|
||||
}
|
||||
} else if (document.createEventObject) {
|
||||
evt = document.createEventObject();
|
||||
$.extend(evt, e);
|
||||
}
|
||||
if ($.browser.msie || $.browser.opera) {
|
||||
evt.keyCode = (e.charCode > 0) ? e.charCode : e.keyCode;
|
||||
evt.charCode = undefined;
|
||||
}
|
||||
return evt;
|
||||
},
|
||||
|
||||
dispatchEvent: function(el, type, evt) {
|
||||
if (el.dispatchEvent) {
|
||||
el.dispatchEvent(evt);
|
||||
} else if (el.fireEvent) {
|
||||
el.fireEvent('on' + type, evt);
|
||||
}
|
||||
return evt;
|
||||
},
|
||||
|
||||
drag: function(el) {
|
||||
var self = this, center = this.findCenter(this.target),
|
||||
options = this.options, x = Math.floor(center.x), y = Math.floor(center.y),
|
||||
dx = options.dx || 0, dy = options.dy || 0, target = this.target;
|
||||
var coord = { clientX: x, clientY: y };
|
||||
this.simulateEvent(target, "mousedown", coord);
|
||||
coord = { clientX: x + 1, clientY: y + 1 };
|
||||
this.simulateEvent(document, "mousemove", coord);
|
||||
coord = { clientX: x + dx, clientY: y + dy };
|
||||
this.simulateEvent(document, "mousemove", coord);
|
||||
this.simulateEvent(document, "mousemove", coord);
|
||||
this.simulateEvent(target, "mouseup", coord);
|
||||
},
|
||||
findCenter: function(el) {
|
||||
var el = $(this.target), o = el.offset();
|
||||
return {
|
||||
x: o.left + el.outerWidth() / 2,
|
||||
y: o.top + el.outerHeight() / 2
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
$.extend($.simulate, {
|
||||
defaults: {
|
||||
speed: 'sync'
|
||||
},
|
||||
VK_TAB: 9,
|
||||
VK_ENTER: 13,
|
||||
VK_ESC: 27,
|
||||
VK_PGUP: 33,
|
||||
VK_PGDN: 34,
|
||||
VK_END: 35,
|
||||
VK_HOME: 36,
|
||||
VK_LEFT: 37,
|
||||
VK_UP: 38,
|
||||
VK_RIGHT: 39,
|
||||
VK_DOWN: 40
|
||||
});
|
||||
|
||||
})(jQuery);
|
45
resources/recipes/bangkokpost.recipe
Normal file
45
resources/recipes/bangkokpost.recipe
Normal file
@ -0,0 +1,45 @@
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class BangkokPostRecipe(BasicNewsRecipe):
|
||||
__license__ = 'GPL v3'
|
||||
__author__ = 'kwetal'
|
||||
language = 'en_TH'
|
||||
version = 1
|
||||
|
||||
title = u'Bangkok Post'
|
||||
publisher = u'Post Publishing PCL'
|
||||
category = u'News'
|
||||
description = u'The world\'s window to Thailand'
|
||||
|
||||
oldest_article = 7
|
||||
max_articles_per_feed = 100
|
||||
|
||||
no_stylesheets = True
|
||||
remove_javascript = True
|
||||
use_embedded_content = False
|
||||
|
||||
# Feeds from: http://www.bangkokpost.com/rss/
|
||||
feeds = []
|
||||
feeds.append((u'Breaking News', u'http://www.bangkokpost.com/rss/data/breakingnews.xml'))
|
||||
feeds.append((u'Top Stories', u'http://www.bangkokpost.com/rss/data/topstories.xml'))
|
||||
feeds.append((u'News', u'http://www.bangkokpost.com/rss/data/news.xml'))
|
||||
feeds.append((u'Business', u'http://www.bangkokpost.com/rss/data/business.xml'))
|
||||
feeds.append((u'Opinion', u'http://www.bangkokpost.com/rss/data/opinion.xml'))
|
||||
feeds.append((u'Travel', u'http://www.bangkokpost.com/rss/data/travel.xml'))
|
||||
feeds.append((u'Leisure', u'http://www.bangkokpost.com/rss/data/leisure.xml'))
|
||||
feeds.append((u'Entertainment', u'http://www.bangkokpost.com/rss/data/entertainment.xml'))
|
||||
feeds.append((u'Auto', u'http://www.bangkokpost.com/rss/data/auto.xml'))
|
||||
feeds.append((u'Life', u'http://www.bangkokpost.com/rss/data/life.xml'))
|
||||
feeds.append((u'Tech', u'http://www.bangkokpost.com/rss/data/tect.xml'))
|
||||
|
||||
keep_only_tags = []
|
||||
keep_only_tags.append(dict(name = 'div', attrs = {'class' : 'entry'}))
|
||||
|
||||
remove_tags = []
|
||||
remove_tags.append(dict(name = 'div', attrs = {'class': 'article-features'}))
|
||||
remove_tags.append(dict(name = 'div', attrs = {'class': 'socialBookmark'}))
|
||||
remove_tags.append(dict(name = 'div', attrs = {'id': 'main-sns'}))
|
||||
# Their YouTube movies are displayed in an iframe, if you want those you will have to parse the articles by hand.
|
||||
# Setting self.recursion to 1, which might resolve this, makes calibre downloading a lot of PDF files, which will cause a very, very very, long download time
|
||||
remove_tags.append(dict(name = 'iframe'))
|
||||
|
97
resources/recipes/glennbeck.recipe
Normal file
97
resources/recipes/glennbeck.recipe
Normal file
@ -0,0 +1,97 @@
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
from calibre.ebooks.BeautifulSoup import BeautifulSoup, Tag, Comment
|
||||
|
||||
class GlennBeckRecipe(BasicNewsRecipe):
|
||||
__license__ = 'GPL v3'
|
||||
__author__ = 'kwetal'
|
||||
language = 'en'
|
||||
version = 1
|
||||
|
||||
title = u'Glenn Beck'
|
||||
publisher = u'Premiere Radio Networks'
|
||||
category = u'News, Opinion'
|
||||
description = u'The fusion of entertainment and enlightenment'
|
||||
|
||||
oldest_article = 7
|
||||
max_articles_per_feed = 100
|
||||
|
||||
no_stylesheets = True
|
||||
remove_javascript = True
|
||||
use_embedded_content = False
|
||||
|
||||
feeds = [(u'Glenn Beck', u'http://feeds.feedburner.com/GlennBeckArticles')]
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
# Their html is horribly broken; if we search for the div that has the content BeatifulSoup returns the div with only the headline and no content.
|
||||
# This is due to illegal nesting of tags. So we do it the hard way.
|
||||
|
||||
# We can find this one, and we don't want it.
|
||||
div = soup.find('div', attrs = {'id': 'extraInfo'})
|
||||
if div:
|
||||
div.extract()
|
||||
|
||||
# Don't want these either.
|
||||
iframes = soup.findAll('iframe')
|
||||
[iframe.extract() for iframe in iframes]
|
||||
|
||||
# Get empty document.
|
||||
freshSoup = self.getFreshSoup()
|
||||
|
||||
# This is the broken div; but we can find the headline.
|
||||
newsDiv = soup.find('div', attrs = {'class': 'news-detail'})
|
||||
if newsDiv:
|
||||
if newsDiv.h1:
|
||||
freshSoup.body.append(newsDiv.h1)
|
||||
|
||||
# The content is wrapped in <p></p> tags, most of the time anyway.
|
||||
counter = 0
|
||||
for p in soup.findAll('p'):
|
||||
if p.get('class') == 'smalltextwhite':
|
||||
# But we don't want this one.
|
||||
continue
|
||||
|
||||
freshSoup.body.append(p)
|
||||
counter += 1
|
||||
|
||||
# Debugging block
|
||||
#h3 = Tag(freshSoup, 'h3')
|
||||
#h3.append('First counter: ' + str(counter))
|
||||
#freshSoup.body.insert(0, h3)
|
||||
|
||||
# In some articles the content is not wrapped in <p></p> tags. In that case the counter is low.
|
||||
# 2 is the magic number that seems to work.
|
||||
if counter <= 2:
|
||||
# So they are playing hard-to-get: first throw out all comments.
|
||||
comments = soup.findAll(text = lambda text: isinstance(text, Comment))
|
||||
[comment.extract() for comment in comments]
|
||||
|
||||
# Find all unwrapped strings.
|
||||
for txt in soup.findAll(text = True):
|
||||
raw = txt.strip()
|
||||
# Debugging line
|
||||
#para.append(raw + '(parent: ' + txt.parent.name + '; length: ' + str(len(raw)) + '; start: ' + raw[0:4] + ')')
|
||||
|
||||
if (txt.parent.name == 'body' and len(raw) > 0) and not (len(raw) == 6 and raw == ' '):
|
||||
# This is our content; ignore the rest.
|
||||
para = Tag(freshSoup, 'p')
|
||||
para.append(raw)
|
||||
freshSoup.body.append(para)
|
||||
counter += 1
|
||||
|
||||
# Now if the counter is still 0 or 1 they did something completely different and we still have an empty article. In a last attempt, add the whole content div, just in case.
|
||||
if counter < 2:
|
||||
freshSoup.body.append(newsDiv)
|
||||
|
||||
# Debugging block
|
||||
#h3 = Tag(freshSoup, 'h3')
|
||||
#h3.append('Second counter: ' + str(counter))
|
||||
#freshSoup.body.insert(1, h3)
|
||||
|
||||
return freshSoup
|
||||
|
||||
def getFreshSoup(self, title = None):
|
||||
if title:
|
||||
return BeautifulSoup('<html><head><title>' + str(title) + '</title></head><body></body></html>')
|
||||
else:
|
||||
return BeautifulSoup('<html><head><title></title></head><body></body></html>')
|
||||
|
134
resources/recipes/huffingtonpost.recipe
Normal file
134
resources/recipes/huffingtonpost.recipe
Normal file
@ -0,0 +1,134 @@
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
from calibre.ebooks.BeautifulSoup import BeautifulSoup, Tag, Comment
|
||||
import re
|
||||
|
||||
class HuffingtonPostRecipe(BasicNewsRecipe):
|
||||
__license__ = 'GPL v3'
|
||||
__author__ = 'kwetal'
|
||||
language = 'en'
|
||||
version = 1
|
||||
|
||||
title = u'The Huffington Post'
|
||||
publisher = u'huffingtonpost.com'
|
||||
category = u'News, Politics'
|
||||
description = u'Political Blog'
|
||||
|
||||
oldest_article = 1.5
|
||||
max_articles_per_feed = 100
|
||||
use_embedded_content = False
|
||||
|
||||
no_stylesheets = True
|
||||
remove_javascript = True
|
||||
# Seems to work best, but YMMV
|
||||
simultaneous_downloads = 1
|
||||
|
||||
feeds = []
|
||||
feeds.append((u'Latest News', u'http://feeds.huffingtonpost.com/huffingtonpost/LatestNews'))
|
||||
# Works, but appears to be a subset of the politics-blog feed
|
||||
#feeds.append((u'Politics', u'http://www.huffingtonpost.com/feeds/verticals/politics/index.xml'))
|
||||
feeds.append((u'Politics', u'http://www.huffingtonpost.com/feeds/verticals/politics/blog.xml'))
|
||||
# Does not work
|
||||
#feeds.append((u'Politics: News', u'http://www.huffingtonpost.com/feeds/verticals/politics/news.xml'))
|
||||
# Works, but appears to be a subset of the media-blog feed
|
||||
#feeds.append((u'Media', u'http://www.huffingtonpost.com/feeds/verticals/media/index.xml'))
|
||||
feeds.append((u'Media', u'http://www.huffingtonpost.com/feeds/verticals/media/blog.xml'))
|
||||
# Does not work
|
||||
#feeds.append((u'Media: News', u'http://www.huffingtonpost.com/feeds/verticals/media/news.xml'))
|
||||
# Works, but appears to be a subset of the business-blog feed
|
||||
#feeds.append((u'Business', u'http://www.huffingtonpost.com/feeds/verticals/business/index.xml'))
|
||||
feeds.append((u'Business', u'http://www.huffingtonpost.com/feeds/verticals/business/blog.xml'))
|
||||
# Does not work
|
||||
#feeds.append((u'Business: News', u'http://www.huffingtonpost.com/feeds/verticals/business/news.xml'))
|
||||
feeds.append((u'Original Reporting', u'http://www.huffingtonpost.com/tag/huffpolitics/feed'))
|
||||
feeds.append((u'Original Posts', u'http://www.huffingtonpost.com/feeds/original_posts/index.xml'))
|
||||
|
||||
keep_only_tags = []
|
||||
# For reporters posts
|
||||
keep_only_tags.append(dict(name = 'div', attrs = {'class' : 'about_reporter_name'}))
|
||||
keep_only_tags.append(dict(name = 'div', attrs = {'class' : 'entry'}))
|
||||
# For blog posts
|
||||
keep_only_tags.append(dict(name = 'div', attrs = {'id' : 'blog_author_info'}))
|
||||
keep_only_tags.append(dict(name = 'div', attrs = {'id' : 'blog_title'}))
|
||||
|
||||
remove_tags = []
|
||||
remove_tags.append(dict(name = 'div', attrs = {'class' : 'contin_below'}))
|
||||
remove_tags.append(dict(name = 'div', attrs = {'class' : 'adver_cont_below'}))
|
||||
remove_tags.append(dict(name = 'div', attrs = {'class' : 'blogger_menu_content'}))
|
||||
remove_tags.append(dict(name = 'div', attrs = {'class' : 'chicklets'}))
|
||||
remove_tags.append(dict(name = 'div', attrs = {'class' : 'google-searcG-blogp'}))
|
||||
remove_tags.append(dict(name = 'div', attrs = {'class' : 'forma_email'}))
|
||||
remove_tags.append(dict(name = 'div', attrs = {'class' : 'new_share_module'}))
|
||||
remove_tags.append(dict(name = 'div', attrs = {'class' : 'cse-branding-right'}))
|
||||
remove_tags.append(dict(name = 'div', attrs = {'class' : 'clear'}))
|
||||
remove_tags.append(dict(name = 'div', attrs = {'style' : re.compile('clear:both;*')}))
|
||||
remove_tags.append(dict(name = 'div', attrs = {'class' : re.compile('facebookvote_reaction_blog.*')}))
|
||||
remove_tags.append(dict(name = 'div', attrs = {'class' : re.compile('read_more.*')}))
|
||||
remove_tags.append(dict(name = 'div', attrs = {'class' : re.compile('facebookvote_v2.*')}))
|
||||
remove_tags.append(dict(name = 'div', attrs = {'class' : re.compile('facebookvote_reaction.*')}))
|
||||
remove_tags.append(dict(name = 'div', attrs = {'class' : 'slideshow_poll'}))
|
||||
|
||||
remove_attributes = ['style']
|
||||
|
||||
extra_css = 'a[href] {color: blue; text-decoration: none; cursor: pointer;}'
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
# Condens the soup.
|
||||
soup = self.unComment(self.nukeHead(soup))
|
||||
|
||||
# Don't want the picture of the author
|
||||
blogAuthor = soup.find('div', attrs = {'id': 'blog_author_info'})
|
||||
if blogAuthor:
|
||||
for img in blogAuthor.findAll('img'):
|
||||
img.extract()
|
||||
|
||||
byline = soup.find('h2')
|
||||
if byline:
|
||||
h2 = Tag(soup, 'h2')
|
||||
raw = self.tag_to_string(byline)
|
||||
h2.append(raw)
|
||||
byline.replaceWith(h2)
|
||||
else:
|
||||
byline = soup.find('div', attrs = {'class': re.compile('about_*reporter_*name')})
|
||||
if byline:
|
||||
h2 = Tag(soup, 'h2')
|
||||
raw = self.tag_to_string(byline)
|
||||
h2.append(raw.strip())
|
||||
byline.replaceWith(h2)
|
||||
|
||||
headline = soup.find('h1')
|
||||
if headline:
|
||||
h1 = Tag(soup, 'h1')
|
||||
raw = self.tag_to_string(headline)
|
||||
h1.append(raw)
|
||||
headline.replaceWith(h1)
|
||||
|
||||
return soup
|
||||
|
||||
def postprocess_html(self, soup, first):
|
||||
# Get rid of those pesky <br /> tags
|
||||
html = re.sub(r'\n<br />\n', '', str(soup))
|
||||
newSoup = BeautifulSoup(html)
|
||||
|
||||
return newSoup
|
||||
|
||||
def nukeHead(self, soup):
|
||||
titleStr = ''
|
||||
newHead = Tag(soup, 'head')
|
||||
newTitle = Tag(soup, 'title')
|
||||
newHead.append(newTitle)
|
||||
head = soup.head
|
||||
if head:
|
||||
title = head.title
|
||||
if title:
|
||||
titleStr = self.tag_to_string(title)
|
||||
newTitle.append(titleStr)
|
||||
head.replaceWith(newHead)
|
||||
else:
|
||||
soup.insert(0, newHead)
|
||||
return soup
|
||||
|
||||
def unComment(self, soup):
|
||||
comments = soup.findAll(text = lambda text: isinstance(text, Comment))
|
||||
[comment.extract() for comment in comments]
|
||||
return soup
|
||||
|
42
resources/recipes/rian_eng.recipe
Normal file
42
resources/recipes/rian_eng.recipe
Normal file
@ -0,0 +1,42 @@
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2009, Darko Miletic <darko.miletic at gmail.com>'
|
||||
'''
|
||||
en.rian.ru
|
||||
'''
|
||||
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class Ria_eng(BasicNewsRecipe):
|
||||
title = 'Ria Novosti'
|
||||
__author__ = 'Darko Miletic'
|
||||
description = 'News from Russia in English'
|
||||
language = 'en'
|
||||
publisher = 'en.rian.ru'
|
||||
category = 'news, politics, Russia'
|
||||
oldest_article = 3
|
||||
max_articles_per_feed = 100
|
||||
no_stylesheets = True
|
||||
use_embedded_content = False
|
||||
encoding = 'utf-8'
|
||||
|
||||
conversion_options = {
|
||||
'comment' : description
|
||||
, 'tags' : category
|
||||
, 'publisher' : publisher
|
||||
, 'language' : language
|
||||
}
|
||||
|
||||
|
||||
keep_only_tags = [dict(name='div', attrs={'class':'article'})]
|
||||
|
||||
remove_tags = [
|
||||
dict(name=['object','link','iframe','base'])
|
||||
,dict(name='div',attrs={'class':['related','mmban','view-story']})
|
||||
,dict(name='span',attrs={'class':'copyright'})
|
||||
]
|
||||
remove_tags_after = dict(name='div',attrs={'class':'text'})
|
||||
|
||||
|
||||
feeds = [(u'Online news', u'http://en.rian.ru/export/rss2/archive/index.xml')]
|
||||
|
41
resources/recipes/rian_spa.recipe
Normal file
41
resources/recipes/rian_spa.recipe
Normal file
@ -0,0 +1,41 @@
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2009, Darko Miletic <darko.miletic at gmail.com>'
|
||||
'''
|
||||
sp.rian.ru
|
||||
'''
|
||||
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class Ria_eng(BasicNewsRecipe):
|
||||
title = 'Ria Novosti'
|
||||
__author__ = 'Darko Miletic'
|
||||
description = 'Noticias desde Russia en Castellano'
|
||||
language = 'es'
|
||||
publisher = 'sp.rian.ru'
|
||||
category = 'news, politics, Russia'
|
||||
oldest_article = 3
|
||||
max_articles_per_feed = 100
|
||||
no_stylesheets = True
|
||||
use_embedded_content = False
|
||||
encoding = 'utf-8'
|
||||
|
||||
conversion_options = {
|
||||
'comment' : description
|
||||
, 'tags' : category
|
||||
, 'publisher' : publisher
|
||||
, 'language' : language
|
||||
}
|
||||
|
||||
|
||||
keep_only_tags = [dict(name='div', attrs={'class':'articletxt'})]
|
||||
remove_tags = [dict(name=['object','link','iframe','base'])]
|
||||
remove_tags_after = dict(name='div',attrs={'class':'text'})
|
||||
|
||||
|
||||
feeds = [(u'Noticias', u'http://sp.rian.ru/export/rss2/index.xml')]
|
||||
|
||||
def print_version(self, url):
|
||||
return url.replace('.html','-print.html')
|
||||
|
||||
|
33
resources/recipes/sabah.recipe
Normal file
33
resources/recipes/sabah.recipe
Normal file
@ -0,0 +1,33 @@
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class AdvancedUserRecipe1253477125(BasicNewsRecipe):
|
||||
title = u'Sabah'
|
||||
__author__ = u'Deniz O\u011fuz'
|
||||
language = 'tr'
|
||||
oldest_article = 1
|
||||
max_articles_per_feed = 20
|
||||
cover_url = 'http://www.sabah.com.tr/c/sb/i/sabah_logo.gif'
|
||||
no_stylesheets = True
|
||||
remove_tags = [dict(name='div', attrs={'style':['width: 200px', 'border-top: #d8d8d8 1px dotted', 'width: 200px; margin-right: 5px', 'padding-top: 1px']})]
|
||||
keep_only_tags = [dict(name='div', attrs={'class':['haber line_height_def', 'haber haber_renk line_height_def']})]
|
||||
extra_css = '''
|
||||
body{font-family:Arial,Helvetica,sans-serif; font-size:small; align:left}
|
||||
h1{font-size:large;}
|
||||
.sh{font-size:large; font-weight:bold}
|
||||
.cap{font-size:xx-small; }
|
||||
.lu{font-size:xx-small; }
|
||||
.ds{font-size:xx-small; }
|
||||
.mvb{font-size:xx-small;}
|
||||
.by1{font-size:x-small; color:#666666}
|
||||
.byd{font-size:x-small;}
|
||||
'''
|
||||
feeds = [(u'Son 24 Saat', u'http://www.sabah.com.tr/rss/Son24Saat.xml'),
|
||||
(u'Ekonomi', u'http://www.sabah.com.tr/rss/Ekonomi.xml'),
|
||||
(u'G\xfcndem', u'http://www.sabah.com.tr/rss/Gundem.xml'),
|
||||
(u'Siyaset', u'http://www.sabah.com.tr/rss/Siyaset.xml'),
|
||||
(u'Yazarlar', u'http://www.sabah.com.tr/rss/Yazarlar.xml'),
|
||||
(u'D\xfcnya', u'http://www.sabah.com.tr/rss/Dunya.xml'),
|
||||
(u'Teknoloji', u'http://www.sabah.com.tr/rss/Teknoloji.xml'),
|
||||
(u'Spor', u'http://www.sabah.com.tr/rss/Spor.xml'),
|
||||
(u'G\xfcn\xfcn \u0130\xe7inden', u'http://www.sabah.com.tr/rss/gununicinden.xml'),
|
||||
(u'Emlak', u'http://www.sabah.com.tr/rss/Emlak.xml'),]
|
@ -1,4 +1,3 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2009, Darko Miletic <darko.miletic at gmail.com>'
|
||||
@ -16,26 +15,23 @@ class Soldiers(BasicNewsRecipe):
|
||||
max_articles_per_feed = 100
|
||||
no_stylesheets = True
|
||||
use_embedded_content = False
|
||||
remove_javascript = True
|
||||
simultaneous_downloads = 1
|
||||
delay = 4
|
||||
max_connections = 1
|
||||
encoding = 'utf-8'
|
||||
publisher = 'U.S. Army'
|
||||
category = 'news, politics, war, weapons'
|
||||
language = 'en'
|
||||
|
||||
language = 'en'
|
||||
INDEX = 'http://www.army.mil/soldiers/'
|
||||
|
||||
html2lrf_options = [
|
||||
'--comment', description
|
||||
, '--category', category
|
||||
, '--publisher', publisher
|
||||
]
|
||||
|
||||
html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"'
|
||||
conversion_options = {
|
||||
'comment' : description
|
||||
, 'tags' : category
|
||||
, 'publisher' : publisher
|
||||
, 'language' : language
|
||||
}
|
||||
|
||||
keep_only_tags = [dict(name='div', attrs={'id':'rightCol'})]
|
||||
keep_only_tags = [dict(name='div', attrs={'id':['storyHeader','textArea']})]
|
||||
|
||||
remove_tags = [
|
||||
dict(name='div', attrs={'id':['addThis','comment','articleFooter']})
|
||||
@ -44,10 +40,6 @@ class Soldiers(BasicNewsRecipe):
|
||||
|
||||
feeds = [(u'Frontpage', u'http://www.army.mil/rss/feeds/soldiersfrontpage.xml' )]
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
for item in soup.findAll(style=True):
|
||||
del item['style']
|
||||
return soup
|
||||
|
||||
def get_cover_url(self):
|
||||
cover_url = None
|
||||
@ -56,3 +48,4 @@ class Soldiers(BasicNewsRecipe):
|
||||
if cover_item:
|
||||
cover_url = cover_item['src']
|
||||
return cover_url
|
||||
|
||||
|
20
resources/recipes/zaman.recipe
Normal file
20
resources/recipes/zaman.recipe
Normal file
@ -0,0 +1,20 @@
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class ZamanRecipe(BasicNewsRecipe):
|
||||
title = u'Zaman'
|
||||
__author__ = u'Deniz Og\xfcz'
|
||||
language = 'tr'
|
||||
oldest_article = 1
|
||||
max_articles_per_feed = 10
|
||||
|
||||
cover_url = 'http://medya.zaman.com.tr/zamantryeni/pics/zamanonline.gif'
|
||||
feeds = [(u'Gundem', u'http://www.zaman.com.tr/gundem.rss'),
|
||||
(u'Son Dakika', u'http://www.zaman.com.tr/sondakika.rss'),
|
||||
(u'Spor', u'http://www.zaman.com.tr/spor.rss'),
|
||||
(u'Ekonomi', u'http://www.zaman.com.tr/ekonomi.rss'),
|
||||
(u'Politika', u'http://www.zaman.com.tr/politika.rss'),
|
||||
(u'D\u0131\u015f Haberler', u'http://www.zaman.com.tr/dishaberler.rss'),
|
||||
(u'Yazarlar', u'http://www.zaman.com.tr/yazarlar.rss'),]
|
||||
|
||||
def print_version(self, url):
|
||||
return url.replace('www.zaman.com.tr/haber.do?', 'www.zaman.com.tr/yazdir.do?')
|
@ -112,6 +112,13 @@ extensions = [
|
||||
sip_files = ['calibre/gui2/pictureflow/pictureflow.sip']
|
||||
),
|
||||
|
||||
Extension('progress_indicator',
|
||||
['calibre/gui2/progress_indicator/QProgressIndicator.cpp'],
|
||||
inc_dirs = ['calibre/gui2/progress_indicator'],
|
||||
headers = ['calibre/gui2/progress_indicator/QProgressIndicator.h'],
|
||||
sip_files = ['calibre/gui2/progress_indicator/QProgressIndicator.sip']
|
||||
),
|
||||
|
||||
]
|
||||
|
||||
|
||||
|
@ -55,7 +55,7 @@ if plugins is None:
|
||||
sys.path.insert(0, plugin_path)
|
||||
|
||||
for plugin in ['pictureflow', 'lzx', 'msdes', 'podofo', 'cPalmdoc',
|
||||
'fontconfig', 'pdfreflow'] + \
|
||||
'fontconfig', 'pdfreflow', 'progress_indicator'] + \
|
||||
(['winutil'] if iswindows else []) + \
|
||||
(['usbobserver'] if isosx else []):
|
||||
try:
|
||||
|
124
src/calibre/gui2/progress_indicator/QProgressIndicator.cpp
Normal file
124
src/calibre/gui2/progress_indicator/QProgressIndicator.cpp
Normal file
@ -0,0 +1,124 @@
|
||||
#include "QProgressIndicator.h"
|
||||
|
||||
#include <QPainter>
|
||||
|
||||
QProgressIndicator::QProgressIndicator(QWidget* parent, int size)
|
||||
: QWidget(parent),
|
||||
m_angle(0),
|
||||
m_timerId(-1),
|
||||
m_delay(80),
|
||||
m_displayedWhenStopped(true),
|
||||
m_displaySize(size),
|
||||
m_color(Qt::black)
|
||||
{
|
||||
setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||
setFocusPolicy(Qt::NoFocus);
|
||||
}
|
||||
|
||||
bool QProgressIndicator::isAnimated () const
|
||||
{
|
||||
return (m_timerId != -1);
|
||||
}
|
||||
|
||||
void QProgressIndicator::setDisplayedWhenStopped(bool state)
|
||||
{
|
||||
m_displayedWhenStopped = state;
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
void QProgressIndicator::setDisplaySize(int size)
|
||||
{
|
||||
m_displaySize = size;
|
||||
update();
|
||||
}
|
||||
|
||||
|
||||
bool QProgressIndicator::isDisplayedWhenStopped() const
|
||||
{
|
||||
return m_displayedWhenStopped;
|
||||
}
|
||||
|
||||
void QProgressIndicator::startAnimation()
|
||||
{
|
||||
m_angle = 0;
|
||||
|
||||
if (m_timerId == -1)
|
||||
m_timerId = startTimer(m_delay);
|
||||
}
|
||||
|
||||
void QProgressIndicator::stopAnimation()
|
||||
{
|
||||
if (m_timerId != -1)
|
||||
killTimer(m_timerId);
|
||||
|
||||
m_timerId = -1;
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
void QProgressIndicator::setAnimationDelay(int delay)
|
||||
{
|
||||
if (m_timerId != -1)
|
||||
killTimer(m_timerId);
|
||||
|
||||
m_delay = delay;
|
||||
|
||||
if (m_timerId != -1)
|
||||
m_timerId = startTimer(m_delay);
|
||||
}
|
||||
|
||||
void QProgressIndicator::setColor(const QColor & color)
|
||||
{
|
||||
m_color = color;
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
QSize QProgressIndicator::sizeHint() const
|
||||
{
|
||||
return QSize(m_displaySize, m_displaySize);
|
||||
}
|
||||
|
||||
int QProgressIndicator::heightForWidth(int w) const
|
||||
{
|
||||
return w;
|
||||
}
|
||||
|
||||
void QProgressIndicator::timerEvent(QTimerEvent * /*event*/)
|
||||
{
|
||||
m_angle = (m_angle+30)%360;
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
void QProgressIndicator::paintEvent(QPaintEvent * /*event*/)
|
||||
{
|
||||
if (!m_displayedWhenStopped && !isAnimated())
|
||||
return;
|
||||
|
||||
int width = qMin(this->width(), this->height());
|
||||
|
||||
QPainter p(this);
|
||||
p.setRenderHint(QPainter::Antialiasing);
|
||||
|
||||
int outerRadius = (width-1)*0.5;
|
||||
int innerRadius = (width-1)*0.5*0.38;
|
||||
|
||||
int capsuleHeight = outerRadius - innerRadius;
|
||||
int capsuleWidth = (width > 32 ) ? capsuleHeight *.23 : capsuleHeight *.35;
|
||||
int capsuleRadius = capsuleWidth/2;
|
||||
|
||||
for (int i=0; i<12; i++)
|
||||
{
|
||||
QColor color = m_color;
|
||||
color.setAlphaF(1.0f - (i/12.0f));
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(color);
|
||||
p.save();
|
||||
p.translate(rect().center());
|
||||
p.rotate(m_angle - i*30.0f);
|
||||
p.drawRoundedRect(-capsuleWidth*0.5, -(innerRadius+capsuleHeight), capsuleWidth, capsuleHeight, capsuleRadius, capsuleRadius);
|
||||
p.restore();
|
||||
}
|
||||
}
|
93
src/calibre/gui2/progress_indicator/QProgressIndicator.h
Normal file
93
src/calibre/gui2/progress_indicator/QProgressIndicator.h
Normal file
@ -0,0 +1,93 @@
|
||||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
#include <QColor>
|
||||
|
||||
/*!
|
||||
\class QProgressIndicator
|
||||
\brief The QProgressIndicator class lets an application display a progress indicator to show that a lengthy task is under way.
|
||||
|
||||
Progress indicators are indeterminate and do nothing more than spin to show that the application is busy.
|
||||
\sa QProgressBar
|
||||
*/
|
||||
class QProgressIndicator : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(int delay READ animationDelay WRITE setAnimationDelay)
|
||||
Q_PROPERTY(bool displayedWhenStopped READ isDisplayedWhenStopped WRITE setDisplayedWhenStopped)
|
||||
Q_PROPERTY(QColor color READ color WRITE setColor)
|
||||
Q_PROPERTY(int displaySize READ displaySize WRITE setDisplaySize)
|
||||
public:
|
||||
QProgressIndicator(QWidget* parent = 0, int size = 64);
|
||||
|
||||
/*! Returns the delay between animation steps.
|
||||
\return The number of milliseconds between animation steps. By default, the animation delay is set to 80 milliseconds.
|
||||
\sa setAnimationDelay
|
||||
*/
|
||||
int animationDelay() const { return m_delay; }
|
||||
|
||||
/*! Returns a Boolean value indicating whether the component is currently animated.
|
||||
\return Animation state.
|
||||
\sa startAnimation stopAnimation
|
||||
*/
|
||||
bool isAnimated () const;
|
||||
|
||||
/*! Returns a Boolean value indicating whether the receiver shows itself even when it is not animating.
|
||||
\return Return true if the progress indicator shows itself even when it is not animating. By default, it returns false.
|
||||
\sa setDisplayedWhenStopped
|
||||
*/
|
||||
bool isDisplayedWhenStopped() const;
|
||||
|
||||
/*! Returns the color of the component.
|
||||
\sa setColor
|
||||
*/
|
||||
const QColor & color() const { return m_color; }
|
||||
|
||||
virtual QSize sizeHint() const;
|
||||
int heightForWidth(int w) const;
|
||||
int displaySize() const { return m_displaySize; }
|
||||
public slots:
|
||||
/*! Starts the spin animation.
|
||||
\sa stopAnimation isAnimated
|
||||
*/
|
||||
void startAnimation();
|
||||
|
||||
/*! Stops the spin animation.
|
||||
\sa startAnimation isAnimated
|
||||
*/
|
||||
void stopAnimation();
|
||||
|
||||
/*! Sets the delay between animation steps.
|
||||
Setting the \a delay to a value larger than 40 slows the animation, while setting the \a delay to a smaller value speeds it up.
|
||||
\param delay The delay, in milliseconds.
|
||||
\sa animationDelay
|
||||
*/
|
||||
void setAnimationDelay(int delay);
|
||||
|
||||
/*! Sets whether the component hides itself when it is not animating.
|
||||
\param state The animation state. Set false to hide the progress indicator when it is not animating; otherwise true.
|
||||
\sa isDisplayedWhenStopped
|
||||
*/
|
||||
void setDisplayedWhenStopped(bool state);
|
||||
|
||||
/*! Sets the color of the components to the given color.
|
||||
\sa color
|
||||
*/
|
||||
void setColor(const QColor & color);
|
||||
|
||||
/*! Set the size of this widget (used by sizeHint)
|
||||
* \sa displaySize
|
||||
*/
|
||||
void setDisplaySize(int size);
|
||||
protected:
|
||||
virtual void timerEvent(QTimerEvent * event);
|
||||
virtual void paintEvent(QPaintEvent * event);
|
||||
private:
|
||||
int m_angle;
|
||||
int m_timerId;
|
||||
int m_delay;
|
||||
int m_displaySize;
|
||||
bool m_displayedWhenStopped;
|
||||
QColor m_color;
|
||||
};
|
||||
|
52
src/calibre/gui2/progress_indicator/QProgressIndicator.sip
Normal file
52
src/calibre/gui2/progress_indicator/QProgressIndicator.sip
Normal file
@ -0,0 +1,52 @@
|
||||
//Define the SIP wrapper to the QProgressIndicator widget
|
||||
//Author - Kovid Goyal <kovid@kovidgoyal.net>
|
||||
|
||||
%Module progress_indicator 1
|
||||
|
||||
%Import QtCore/QtCoremod.sip
|
||||
%Import QtGui/QtGuimod.sip
|
||||
|
||||
class QProgressIndicator : QWidget {
|
||||
|
||||
%TypeHeaderCode
|
||||
#include <QProgressIndicator.h>
|
||||
%End
|
||||
|
||||
public:
|
||||
|
||||
QProgressIndicator(QWidget *parent /TransferThis/ = 0, int size = 64);
|
||||
|
||||
int animationDelay() const;
|
||||
|
||||
bool isAnimated () const;
|
||||
|
||||
bool isDisplayedWhenStopped() const;
|
||||
|
||||
const QColor & color() const;
|
||||
|
||||
virtual QSize sizeHint() const;
|
||||
|
||||
int heightForWidth(int w) const;
|
||||
|
||||
int displaySize() const;
|
||||
|
||||
public slots:
|
||||
void startAnimation();
|
||||
|
||||
void stopAnimation();
|
||||
|
||||
void setAnimationDelay(int delay);
|
||||
|
||||
void setDisplayedWhenStopped(bool state);
|
||||
|
||||
void setColor(const QColor & color);
|
||||
|
||||
void setDisplaySize(int size);
|
||||
|
||||
protected:
|
||||
|
||||
virtual void timerEvent(QTimerEvent * event);
|
||||
|
||||
virtual void paintEvent(QPaintEvent * event);
|
||||
|
||||
};
|
17
src/calibre/gui2/progress_indicator/__init__.py
Normal file
17
src/calibre/gui2/progress_indicator/__init__.py
Normal file
@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||
from __future__ import with_statement
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
|
||||
from calibre.constants import plugins
|
||||
pi, pi_error = plugins['progress_indicator']
|
||||
|
||||
if pi_error:
|
||||
raise RuntimeError('Failed to load the Progress Indicator plugin: '+\
|
||||
pi_error)
|
||||
|
||||
ProgressIndicator = pi.QProgressIndicator
|
@ -2,12 +2,13 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
import os, re, collections
|
||||
|
||||
from PyQt4.QtGui import QStatusBar, QMovie, QLabel, QWidget, QHBoxLayout, QPixmap, \
|
||||
from PyQt4.QtGui import QStatusBar, QLabel, QWidget, QHBoxLayout, QPixmap, \
|
||||
QVBoxLayout, QSizePolicy, QToolButton, QIcon, QScrollArea, QFrame
|
||||
from PyQt4.QtCore import Qt, QSize, SIGNAL, QCoreApplication
|
||||
from calibre import fit_image, preferred_encoding, isosx
|
||||
from calibre.gui2 import qstring_to_unicode, config
|
||||
from calibre.gui2.widgets import IMAGE_EXTENSIONS
|
||||
from calibre.gui2.progress_indicator import ProgressIndicator
|
||||
from calibre.ebooks import BOOK_EXTENSIONS
|
||||
|
||||
class BookInfoDisplay(QWidget):
|
||||
@ -138,14 +139,12 @@ class BookInfoDisplay(QWidget):
|
||||
self.setVisible(True)
|
||||
|
||||
class MovieButton(QFrame):
|
||||
def __init__(self, movie, jobs_dialog):
|
||||
|
||||
def __init__(self, jobs_dialog):
|
||||
QFrame.__init__(self)
|
||||
movie.setCacheMode(QMovie.CacheAll)
|
||||
self.setLayout(QVBoxLayout())
|
||||
self.movie_widget = QLabel()
|
||||
self.movie_widget.setMovie(movie)
|
||||
self.movie = movie
|
||||
self.layout().addWidget(self.movie_widget)
|
||||
self.pi = ProgressIndicator(self)
|
||||
self.layout().addWidget(self.pi)
|
||||
self.jobs = QLabel('<b>'+_('Jobs:')+' 0')
|
||||
self.jobs.setAlignment(Qt.AlignHCenter|Qt.AlignBottom)
|
||||
self.layout().addWidget(self.jobs)
|
||||
@ -156,11 +155,8 @@ class MovieButton(QFrame):
|
||||
self.jobs_dialog = jobs_dialog
|
||||
self.setCursor(Qt.PointingHandCursor)
|
||||
self.setToolTip(_('Click to see list of active jobs.'))
|
||||
movie.start()
|
||||
movie.setPaused(True)
|
||||
self.jobs_dialog.jobs_view.restore_column_widths()
|
||||
|
||||
|
||||
def mouseReleaseEvent(self, event):
|
||||
if self.jobs_dialog.isVisible():
|
||||
self.jobs_dialog.jobs_view.write_settings()
|
||||
@ -170,6 +166,17 @@ class MovieButton(QFrame):
|
||||
self.jobs_dialog.show()
|
||||
self.jobs_dialog.jobs_view.restore_column_widths()
|
||||
|
||||
@property
|
||||
def is_running(self):
|
||||
return self.pi.isAnimated()
|
||||
|
||||
def start(self):
|
||||
self.pi.startAnimation()
|
||||
|
||||
def stop(self):
|
||||
self.pi.stopAnimation()
|
||||
|
||||
|
||||
class CoverFlowButton(QToolButton):
|
||||
|
||||
def __init__(self, parent=None):
|
||||
@ -211,7 +218,7 @@ class StatusBar(QStatusBar):
|
||||
def __init__(self, jobs_dialog, systray=None):
|
||||
QStatusBar.__init__(self)
|
||||
self.systray = systray
|
||||
self.movie_button = MovieButton(QMovie(I('jobs-animated.mng')), jobs_dialog)
|
||||
self.movie_button = MovieButton(jobs_dialog)
|
||||
self.cover_flow_button = CoverFlowButton()
|
||||
self.tag_view_button = TagViewButton()
|
||||
self.addPermanentWidget(self.cover_flow_button)
|
||||
@ -262,8 +269,7 @@ class StatusBar(QStatusBar):
|
||||
num = self.jobs()
|
||||
text = src.replace(str(num), str(nnum))
|
||||
jobs.setText(text)
|
||||
if self.movie_button.movie.state() == QMovie.Paused:
|
||||
self.movie_button.movie.setPaused(False)
|
||||
self.movie_button.start()
|
||||
|
||||
def job_done(self, nnum):
|
||||
jobs = self.movie_button.jobs
|
||||
@ -275,49 +281,8 @@ class StatusBar(QStatusBar):
|
||||
self.no_more_jobs()
|
||||
|
||||
def no_more_jobs(self):
|
||||
if self.movie_button.movie.state() == QMovie.Running:
|
||||
self.movie_button.movie.jumpToFrame(0)
|
||||
self.movie_button.movie.setPaused(True)
|
||||
if self.movie_button.is_running:
|
||||
self.movie_button.stop()
|
||||
QCoreApplication.instance().alert(self, 5000)
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Used to create the animated status icon
|
||||
from PyQt4.Qt import QPainter, QSvgRenderer, QColor
|
||||
from subprocess import check_call
|
||||
from calibre.gui2 import is_ok_to_use_qt
|
||||
is_ok_to_use_qt()
|
||||
|
||||
def create_pixmaps(path, size=16, delta=20):
|
||||
r = QSvgRenderer(path)
|
||||
if not r.isValid():
|
||||
raise Exception(path + ' not valid svg')
|
||||
pixmaps = []
|
||||
for angle in range(0, 360+delta, delta):
|
||||
pm = QPixmap(size, size)
|
||||
pm.fill(QColor(0,0,0,0))
|
||||
p = QPainter(pm)
|
||||
p.translate(size/2., size/2.)
|
||||
p.rotate(angle)
|
||||
p.translate(-size/2., -size/2.)
|
||||
r.render(p)
|
||||
p.end()
|
||||
pixmaps.append(pm)
|
||||
return pixmaps
|
||||
|
||||
def create_mng(path='', size=64, angle=5, delay=5):
|
||||
pixmaps = create_pixmaps(path, size, angle)
|
||||
filesl = []
|
||||
for i in range(len(pixmaps)):
|
||||
name = 'a%s.png'%(i,)
|
||||
filesl.append(name)
|
||||
pixmaps[i].save(name, 'PNG')
|
||||
filesc = ' '.join(filesl)
|
||||
cmd = 'convert -dispose Background -delay '+str(delay)+ ' ' + filesc + ' -loop 0 animated.gif'
|
||||
try:
|
||||
check_call(cmd, shell=True)
|
||||
finally:
|
||||
for file in filesl:
|
||||
os.remove(file)
|
||||
import sys
|
||||
create_mng(sys.argv[1])
|
||||
|
||||
|
@ -5,7 +5,7 @@ import traceback, os, sys, functools, collections, re
|
||||
from functools import partial
|
||||
from threading import Thread
|
||||
|
||||
from PyQt4.Qt import QMovie, QApplication, Qt, QIcon, QTimer, QWidget, SIGNAL, \
|
||||
from PyQt4.Qt import QApplication, Qt, QIcon, QTimer, SIGNAL, \
|
||||
QDesktopServices, QDoubleSpinBox, QLabel, QTextBrowser, \
|
||||
QPainter, QBrush, QColor, QStandardItemModel, QPalette, \
|
||||
QStandardItem, QUrl, QRegExpValidator, QRegExp, QLineEdit, \
|
||||
@ -14,6 +14,7 @@ from PyQt4.Qt import QMovie, QApplication, Qt, QIcon, QTimer, QWidget, SIGNAL, \
|
||||
from calibre.gui2.viewer.main_ui import Ui_EbookViewer
|
||||
from calibre.gui2.viewer.printing import Printing
|
||||
from calibre.gui2.viewer.bookmarkmanager import BookmarkManager
|
||||
from calibre.gui2.widgets import ProgressIndicator
|
||||
from calibre.gui2.main_window import MainWindow
|
||||
from calibre.gui2 import Application, ORG_NAME, APP_UID, choose_files, \
|
||||
info_dialog, error_dialog
|
||||
@ -62,39 +63,6 @@ class Worker(Thread):
|
||||
self.exception = err
|
||||
self.traceback = traceback.format_exc()
|
||||
|
||||
class ProgressIndicator(QWidget):
|
||||
|
||||
def __init__(self, *args):
|
||||
QWidget.__init__(self, *args)
|
||||
self.setGeometry(0, 0, 300, 500)
|
||||
self.movie = QMovie(I('jobs-animated.mng'))
|
||||
self.ml = QLabel(self)
|
||||
self.ml.setMovie(self.movie)
|
||||
self.movie.start()
|
||||
self.movie.setPaused(True)
|
||||
self.status = QLabel(self)
|
||||
self.status.setWordWrap(True)
|
||||
self.status.setAlignment(Qt.AlignHCenter|Qt.AlignTop)
|
||||
self.setVisible(False)
|
||||
|
||||
def start(self, msg=''):
|
||||
view = self.parent()
|
||||
pwidth, pheight = view.size().width(), view.size().height()
|
||||
self.resize(pwidth, min(pheight, 250))
|
||||
self.move(0, (pheight-self.size().height())/2.)
|
||||
self.ml.resize(self.ml.sizeHint())
|
||||
self.ml.move(int((self.size().width()-self.ml.size().width())/2.), 0)
|
||||
self.status.resize(self.size().width(), self.size().height()-self.ml.size().height()-10)
|
||||
self.status.move(0, self.ml.size().height()+10)
|
||||
self.status.setText('<h1>'+msg+'</h1>')
|
||||
self.setVisible(True)
|
||||
self.movie.setPaused(False)
|
||||
|
||||
def stop(self):
|
||||
if self.movie.state() == self.movie.Running:
|
||||
self.movie.setPaused(True)
|
||||
self.setVisible(False)
|
||||
|
||||
class History(collections.deque):
|
||||
|
||||
def __init__(self, action_back, action_forward):
|
||||
|
@ -7,7 +7,7 @@ import re, os, traceback
|
||||
from PyQt4.Qt import QListView, QIcon, QFont, QLabel, QListWidget, \
|
||||
QListWidgetItem, QTextCharFormat, QApplication, \
|
||||
QSyntaxHighlighter, QCursor, QColor, QWidget, \
|
||||
QPixmap, QMovie, QPalette, QTimer, QDialog, \
|
||||
QPixmap, QPalette, QTimer, QDialog, \
|
||||
QAbstractListModel, QVariant, Qt, SIGNAL, \
|
||||
QRegExp, QSettings, QSize, QModelIndex, \
|
||||
QAbstractButton, QPainter, QLineEdit, QComboBox, \
|
||||
@ -22,17 +22,14 @@ from calibre.utils.fonts import fontconfig
|
||||
from calibre.ebooks import BOOK_EXTENSIONS
|
||||
from calibre.ebooks.metadata.meta import metadata_from_filename
|
||||
from calibre.utils.config import prefs
|
||||
from calibre.gui2.progress_indicator import ProgressIndicator as _ProgressIndicator
|
||||
|
||||
class ProgressIndicator(QWidget):
|
||||
|
||||
def __init__(self, *args):
|
||||
QWidget.__init__(self, *args)
|
||||
self.setGeometry(0, 0, 300, 350)
|
||||
self.movie = QMovie(I('jobs-animated.mng'))
|
||||
self.ml = QLabel(self)
|
||||
self.ml.setMovie(self.movie)
|
||||
self.movie.start()
|
||||
self.movie.setPaused(True)
|
||||
self.pi = _ProgressIndicator(self)
|
||||
self.status = QLabel(self)
|
||||
self.status.setWordWrap(True)
|
||||
self.status.setAlignment(Qt.AlignHCenter|Qt.AlignTop)
|
||||
@ -43,18 +40,17 @@ class ProgressIndicator(QWidget):
|
||||
pwidth, pheight = view.size().width(), view.size().height()
|
||||
self.resize(pwidth, min(pheight, 250))
|
||||
self.move(0, (pheight-self.size().height())/2.)
|
||||
self.ml.resize(self.ml.sizeHint())
|
||||
self.ml.move(int((self.size().width()-self.ml.size().width())/2.), 0)
|
||||
self.status.resize(self.size().width(), self.size().height()-self.ml.size().height()-10)
|
||||
self.status.move(0, self.ml.size().height()+10)
|
||||
self.pi.resize(self.pi.sizeHint())
|
||||
self.pi.move(int((self.size().width()-self.pi.size().width())/2.), 0)
|
||||
self.status.resize(self.size().width(), self.size().height()-self.pi.size().height()-10)
|
||||
self.status.move(0, self.pi.size().height()+10)
|
||||
self.status.setText('<h1>'+msg+'</h1>')
|
||||
self.setVisible(True)
|
||||
self.movie.setPaused(False)
|
||||
self.pi.startAnimation()
|
||||
|
||||
def stop(self):
|
||||
if self.movie.state() == self.movie.Running:
|
||||
self.movie.setPaused(True)
|
||||
self.setVisible(False)
|
||||
self.pi.stopAnimation()
|
||||
self.setVisible(False)
|
||||
|
||||
class FilenamePattern(QWidget, Ui_Form):
|
||||
|
||||
|
@ -433,7 +433,7 @@ settings (if any) or the defaults.
|
||||
|
||||
.. note::
|
||||
|
||||
You can see the actual settings used during any conversion by clicking the rotating hourglass
|
||||
You can see the actual settings used during any conversion by clicking the rotating icon in the lower right corner
|
||||
and then double clicking the individual conversion job. This will bring up a conversion log
|
||||
that will contain the actual settings used, near the top.
|
||||
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 3.6 KiB |
@ -9,9 +9,9 @@ So you've just started |app|. What do you do now? Well, before |app| can do anyt
|
||||
|
||||
.. image:: images/added_books.png
|
||||
|
||||
Once you've admired the list of books you just added to your heart's content, you'll probably want to actually read one. In order to do that you'll have to convert the book to a format your reader understands. When first running |app|, the Welcome Wizard starts and it will have setup calibre for your reader device. Conversion is a breeze, just select the book you want to convert, and click the "Convert E-book" button. Ignore all the options for now and just click "OK". The little hourglass in the bottom right corner will start spinning. Once it's finished spinning, your converted book is ready. Click the "View" button to read the book.
|
||||
Once you've admired the list of books you just added to your heart's content, you'll probably want to actually read one. In order to do that you'll have to convert the book to a format your reader understands. When first running |app|, the Welcome Wizard starts and it will have setup calibre for your reader device. Conversion is a breeze, just select the book you want to convert, and click the "Convert E-book" button. Ignore all the options for now and just click "OK". The little icon in the bottom right corner will start spinning. Once it's finished spinning, your converted book is ready. Click the "View" button to read the book.
|
||||
|
||||
Now if you want to read the book on your reader, just connect it to the computer, wait till calibre detects it (10-20secs) and then click the "Send to device" button. Once the hourglass stops spinning again, disconnect your reader and read away! If you didn't convert the book in the previous step, |app| will auto convert it to the format your reader device understands.
|
||||
Now if you want to read the book on your reader, just connect it to the computer, wait till calibre detects it (10-20secs) and then click the "Send to device" button. Once the icon stops spinning again, disconnect your reader and read away! If you didn't convert the book in the previous step, |app| will auto convert it to the format your reader device understands.
|
||||
|
||||
To get started with more advanced usage, you should read about the :ref:`Graphical User Interface <gui>`. For even more power and versatility, learn the :ref:`Command Line Interface <cli>`.
|
||||
|
||||
|
@ -99,6 +99,7 @@ _extra_lang_codes = {
|
||||
'en_AU' : _('English (AU)'),
|
||||
'en_CA' : _('English (CA)'),
|
||||
'en_IN' : _('English (IND)'),
|
||||
'en_TH' : _('English (TH)'),
|
||||
'nl' : _('Dutch (NL)'),
|
||||
'nl_BE' : _('Dutch (BE)'),
|
||||
'und' : _('Unknown')
|
||||
|
Loading…
x
Reference in New Issue
Block a user