Merge from trunk

This commit is contained in:
Charles Haley 2010-11-28 08:05:18 +00:00
commit f50f3793db
17 changed files with 126 additions and 30 deletions

View File

@ -4,6 +4,14 @@
# for important features/bug fixes. # for important features/bug fixes.
# Also, each release can have new and improved recipes. # Also, each release can have new and improved recipes.
- version: 0.7.31
date: 2010-11-27
bug fixes:
- title: "Fix various regressions in the calibre windows build caused by the switch to python 2.7. If you are on windows and upgraded to 0.7.30, it is highly recommended that you upgrade to 0.7.31. If you are not on windows, you can ignore 0.7.31"
tickets: [7685, 7694, 7691]
- version: 0.7.30 - version: 0.7.30
date: 2010-11-26 date: 2010-11-26

View File

@ -32,15 +32,15 @@ class NewYorker(BasicNewsRecipe):
, 'publisher' : publisher , 'publisher' : publisher
, 'language' : language , 'language' : language
} }
keep_only_tags = [ keep_only_tags = [
dict(name='div', attrs={'class':'headers'}) dict(name='div', attrs={'class':'headers'})
,dict(name='div', attrs={'id':['articleheads','items-container','articleRail','articletext','photocredits']}) ,dict(name='div', attrs={'id':['articleheads','items-container','articleRail','articletext','photocredits']})
] ]
remove_tags = [ remove_tags = [
dict(name=['meta','iframe','base','link','embed','object']) dict(name=['meta','iframe','base','link','embed','object'])
,dict(attrs={'class':['utils','articleRailLinks','icons'] }) ,dict(attrs={'class':['utils','articleRailLinks','icons'] })
,dict(attrs={'id':['show-header','show-footer'] }) ,dict(attrs={'id':['show-header','show-footer'] })
] ]
remove_attributes = ['lang'] remove_attributes = ['lang']
feeds = [(u'The New Yorker', u'http://feeds.newyorker.com/services/rss/feeds/everything.xml')] feeds = [(u'The New Yorker', u'http://feeds.newyorker.com/services/rss/feeds/everything.xml')]
@ -58,4 +58,4 @@ class NewYorker(BasicNewsRecipe):
if cover_item: if cover_item:
cover_url = 'http://www.newyorker.com' + cover_item['src'].strip() cover_url = 'http://www.newyorker.com' + cover_item['src'].strip()
return cover_url return cover_url

View File

@ -13,6 +13,8 @@ class RevistaMuyInteresante(BasicNewsRecipe):
no_stylesheets = True no_stylesheets = True
remove_javascript = True remove_javascript = True
conversion_options = {'linearize_tables': True}
extra_css = ' .txt_articulo{ font-family: sans-serif; font-size: medium; text-align: justify } .contentheading{font-family: serif; font-size: large; font-weight: bold; color: #000000; text-align: center}' extra_css = ' .txt_articulo{ font-family: sans-serif; font-size: medium; text-align: justify } .contentheading{font-family: serif; font-size: large; font-weight: bold; color: #000000; text-align: center}'
@ -39,11 +41,12 @@ class RevistaMuyInteresante(BasicNewsRecipe):
keep_only_tags = [dict(name='div', attrs={'class':['article']}),dict(name='td', attrs={'class':['txt_articulo']})] keep_only_tags = [dict(name='div', attrs={'class':['article']}),dict(name='td', attrs={'class':['txt_articulo']})]
remove_tags = [ remove_tags = [
dict(name=['object','link','script','ul']) dict(name=['object','link','script','ul','iframe','ins'])
,dict(name='div', attrs={'id':['comment']}) ,dict(name='div', attrs={'id':['comment']})
,dict(name='td', attrs={'class':['buttonheading']}) ,dict(name='td', attrs={'class':['buttonheading']})
,dict(name='div', attrs={'class':['tags_articles']}) ,dict(name='div', attrs={'class':['tags_articles','bajo_title']})
,dict(name='table', attrs={'class':['pagenav']}) ,dict(name='table', attrs={'class':['pagenav']})
,dict(name='form', attrs={'class':['voteform']})
] ]
remove_tags_after = dict(name='div', attrs={'class':'tags_articles'}) remove_tags_after = dict(name='div', attrs={'class':'tags_articles'})
@ -115,3 +118,5 @@ class RevistaMuyInteresante(BasicNewsRecipe):
if link_item: if link_item:
cover_url = "http://www.muyinteresante.es"+link_item['src'] cover_url = "http://www.muyinteresante.es"+link_item['src']
return cover_url return cover_url

View File

@ -19,7 +19,7 @@ class RusiaHoy(BasicNewsRecipe):
use_embedded_content = False use_embedded_content = False
language = 'es' language = 'es'
remove_empty_feeds = True remove_empty_feeds = True
extra_css = """ extra_css = """
body{font-family: Arial,sans-serif } body{font-family: Arial,sans-serif }
.article_article_title{font-size: xx-large; font-weight: bold} .article_article_title{font-size: xx-large; font-weight: bold}
.article_date{color: black; font-size: small} .article_date{color: black; font-size: small}
@ -44,4 +44,4 @@ class RusiaHoy(BasicNewsRecipe):
for item in soup.findAll(style=True): for item in soup.findAll(style=True):
del item['style'] del item['style']
return soup return soup

View File

@ -6,8 +6,8 @@ www.getwokingham.co.uk
from calibre.web.feeds.recipes import BasicNewsRecipe from calibre.web.feeds.recipes import BasicNewsRecipe
class TheWorkinghamTimes(BasicNewsRecipe): class TheWokinghamTimes(BasicNewsRecipe):
title = 'The Workingham Times' title = 'The Wokingham Times'
__author__ = 'Darko Miletic' __author__ = 'Darko Miletic'
description = 'News from UK' description = 'News from UK'
oldest_article = 2 oldest_article = 2

View File

@ -108,8 +108,7 @@ class Win32Freeze(Command, WixMixIn):
for f in x[-1]: for f in x[-1]:
if f.lower().endswith('.dll'): if f.lower().endswith('.dll'):
f = self.j(x[0], f) f = self.j(x[0], f)
if 'py2exe' not in f: shutil.copy2(f, self.dll_dir)
shutil.copy2(f, self.dll_dir)
shutil.copy2( shutil.copy2(
r'C:\Python%(v)s\Lib\site-packages\pywin32_system32\pywintypes%(v)s.dll' r'C:\Python%(v)s\Lib\site-packages\pywin32_system32\pywintypes%(v)s.dll'
% dict(v=self.py_ver), self.dll_dir) % dict(v=self.py_ver), self.dll_dir)
@ -118,7 +117,7 @@ class Win32Freeze(Command, WixMixIn):
ans = [] ans = []
for x in items: for x in items:
ext = os.path.splitext(x)[1] ext = os.path.splitext(x)[1]
if (not ext and (x in ('demos', 'tests') or 'py2exe' in x)) or \ if (not ext and (x in ('demos', 'tests'))) or \
(ext in ('.dll', '.chm', '.htm', '.txt')): (ext in ('.dll', '.chm', '.htm', '.txt')):
ans.append(x) ans.append(x)
return ans return ans

View File

@ -21,6 +21,8 @@ This is where all dependencies will be installed.
Add C:\Python27\Scripts and C:\Python27 to PATH Add C:\Python27\Scripts and C:\Python27 to PATH
Edit mimetypes.py in C:\Python27\Lib and change line 250 UnicodeEncodeError to ValueError and set _winreg = None to prevent reading of mimetypes from the windows registry
Install setuptools from http://pypi.python.org/pypi/setuptools Install setuptools from http://pypi.python.org/pypi/setuptools
If there are no windows binaries already compiled for the version of python you are using then download the source and run the following command in the folder where the source has been unpacked:: If there are no windows binaries already compiled for the version of python you are using then download the source and run the following command in the folder where the source has been unpacked::
@ -32,6 +34,8 @@ Run the following command to install python dependencies::
Install BeautifulSoup 3.0.x manually into site-packages (3.1.x parses broken HTML very poorly) Install BeautifulSoup 3.0.x manually into site-packages (3.1.x parses broken HTML very poorly)
Install pywin32 and edit win32com\__init__.py setting _frozen = True and
__gen_path__ to a temp dir (otherwise it tries to set it to a dir in the install tree which leads to permission errors)
SQLite SQLite
--------- ---------
@ -66,7 +70,11 @@ Compiling instructions::
Python Imaging Library Python Imaging Library
------------------------ ------------------------
Install as normal using provided installer. Install as normal using installer at http://www.lfd.uci.edu/~gohlke/pythonlibs/
Test it on the target system with
calibre-debug -c "import _imaging, _imagingmath, _imagingft, _imagingcms"
Libunrar Libunrar
---------- ----------

View File

@ -48,6 +48,13 @@ mimetypes.add_type('application/x-cbz', '.cbz')
mimetypes.add_type('application/x-cbr', '.cbr') mimetypes.add_type('application/x-cbr', '.cbr')
mimetypes.add_type('application/x-koboreader-ebook', '.kobo') mimetypes.add_type('application/x-koboreader-ebook', '.kobo')
mimetypes.add_type('image/wmf', '.wmf') mimetypes.add_type('image/wmf', '.wmf')
mimetypes.add_type('image/jpeg', '.jpg')
mimetypes.add_type('image/jpeg', '.jpeg')
mimetypes.add_type('image/png', '.png')
mimetypes.add_type('image/gif', '.gif')
mimetypes.add_type('image/bmp', '.bmp')
mimetypes.add_type('image/svg+xml', '.svg')
guess_type = mimetypes.guess_type guess_type = mimetypes.guess_type
import cssutils import cssutils
cssutils.log.setLevel(logging.WARN) cssutils.log.setLevel(logging.WARN)
@ -362,6 +369,8 @@ def walk(dir):
def strftime(fmt, t=None): def strftime(fmt, t=None):
''' A version of strftime that returns unicode strings and tries to handle dates ''' A version of strftime that returns unicode strings and tries to handle dates
before 1900 ''' before 1900 '''
if not fmt:
return u''
if t is None: if t is None:
t = time.localtime() t = time.localtime()
if hasattr(t, 'timetuple'): if hasattr(t, 'timetuple'):
@ -378,7 +387,8 @@ def strftime(fmt, t=None):
if isinstance(fmt, unicode): if isinstance(fmt, unicode):
fmt = fmt.encode('mbcs') fmt = fmt.encode('mbcs')
ans = plugins['winutil'][0].strftime(fmt, t) ans = plugins['winutil'][0].strftime(fmt, t)
ans = time.strftime(fmt, t).decode(preferred_encoding, 'replace') else:
ans = time.strftime(fmt, t).decode(preferred_encoding, 'replace')
if early_year: if early_year:
ans = ans.replace('_early year hack##', str(orig_year)) ans = ans.replace('_early year hack##', str(orig_year))
return ans return ans

View File

@ -2,7 +2,7 @@ __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net' __copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
__appname__ = 'calibre' __appname__ = 'calibre'
__version__ = '0.7.30' __version__ = '0.7.31'
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>" __author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
import re import re

View File

@ -370,6 +370,15 @@ class InterfaceActionBase(Plugin): # {{{
can_be_disabled = False can_be_disabled = False
actual_plugin = None actual_plugin = None
def load_actual_plugin(self, gui):
'''
This method must return the actual interface action plugin object.
'''
mod, cls = self.actual_plugin.split(':')
return getattr(__import__(mod, fromlist=['1'], level=0), cls)(gui,
self.site_customization)
# }}} # }}}
class PreferencesPlugin(Plugin): # {{{ class PreferencesPlugin(Plugin): # {{{

View File

@ -292,7 +292,8 @@ class RTFInput(InputFormatPlugin):
# Replace newlines inserted by the 'empty_paragraphs' option in rtf2xml with html blank lines # Replace newlines inserted by the 'empty_paragraphs' option in rtf2xml with html blank lines
if not getattr(self.options, 'remove_paragraph_spacing', False): if not getattr(self.options, 'remove_paragraph_spacing', False):
res = re.sub('\s*<body>', '<body>', res) res = re.sub('\s*<body>', '<body>', res)
res = re.sub('(?<=\n)\n{2}', u'<p>\u00a0</p>\n', res) res = re.sub('(?<=\n)\n{2}',
u'<p>\u00a0</p>\n'.encode('utf-8'), res)
if self.options.preprocess_html: if self.options.preprocess_html:
preprocessor = PreProcessor(self.options, log=getattr(self, 'log', None)) preprocessor = PreProcessor(self.options, log=getattr(self, 'log', None))
res = preprocessor(res) res = preprocessor(res)

View File

@ -190,6 +190,10 @@ class BookInfo(QWebView):
self.page().setLinkDelegationPolicy(self.page().DelegateAllLinks) self.page().setLinkDelegationPolicy(self.page().DelegateAllLinks)
self.linkClicked.connect(self.link_activated) self.linkClicked.connect(self.link_activated)
self._link_clicked = False self._link_clicked = False
self.setAttribute(Qt.WA_OpaquePaintEvent, False)
palette = self.palette()
palette.setBrush(QPalette.Base, Qt.transparent)
self.page().setPalette(palette)
def link_activated(self, link): def link_activated(self, link):
self._link_clicked = True self._link_clicked = True
@ -210,16 +214,23 @@ class BookInfo(QWebView):
def _show_data(self, rows, comments): def _show_data(self, rows, comments):
def color_to_string(col):
ans = '#000000'
if col.isValid():
col = col.toRgb()
if col.isValid():
ans = unicode(col.name())
return ans
f = QFontInfo(QApplication.font(self.parent())).pixelSize() f = QFontInfo(QApplication.font(self.parent())).pixelSize()
p = unicode(QApplication.palette().color(QPalette.Normal, c = color_to_string(QApplication.palette().color(QPalette.Normal,
QPalette.Window).name()) QPalette.WindowText))
c = unicode(QApplication.palette().color(QPalette.Normal,
QPalette.WindowText).name())
templ = u'''\ templ = u'''\
<html> <html>
<head> <head>
<style type="text/css"> <style type="text/css">
body, td {background-color: %s; font-size: %dpx; color: %s } body, td {background-color: transparent; font-size: %dpx; color: %s }
a { text-decoration: none; color: blue } a { text-decoration: none; color: blue }
</style> </style>
</head> </head>
@ -227,7 +238,7 @@ class BookInfo(QWebView):
%%s %%s
</body> </body>
<html> <html>
'''%(p, f, c) '''%(f, c)
if self.vertical: if self.vertical:
if comments: if comments:
rows += u'<tr><td colspan="2">%s</td></tr>'%comments rows += u'<tr><td colspan="2">%s</td></tr>'%comments

View File

@ -102,9 +102,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
self.device_connected = None self.device_connected = None
acmap = OrderedDict() acmap = OrderedDict()
for action in interface_actions(): for action in interface_actions():
mod, cls = action.actual_plugin.split(':') ac = action.load_actual_plugin(self)
ac = getattr(__import__(mod, fromlist=['1'], level=0), cls)(self,
action.site_customization)
if ac.name in acmap: if ac.name in acmap:
if ac.priority >= acmap[ac.name].priority: if ac.priority >= acmap[ac.name].priority:
acmap[ac.name] = ac acmap[ac.name] = ac

View File

@ -150,7 +150,7 @@ class DBThread(Thread):
detect_types=sqlite.PARSE_DECLTYPES|sqlite.PARSE_COLNAMES) detect_types=sqlite.PARSE_DECLTYPES|sqlite.PARSE_COLNAMES)
self.conn.execute('pragma cache_size=5000') self.conn.execute('pragma cache_size=5000')
encoding = self.conn.execute('pragma encoding').fetchone()[0] encoding = self.conn.execute('pragma encoding').fetchone()[0]
c_ext_loaded = False #load_c_extensions(self.conn) c_ext_loaded = load_c_extensions(self.conn)
self.conn.row_factory = sqlite.Row if self.row_factory else lambda cursor, row : list(row) self.conn.row_factory = sqlite.Row if self.row_factory else lambda cursor, row : list(row)
self.conn.create_aggregate('concat', 1, Concatenate) self.conn.create_aggregate('concat', 1, Concatenate)
if not c_ext_loaded: if not c_ext_loaded:

View File

@ -98,6 +98,44 @@ Every time you use calibre to convert a book, the plugin's :meth:`run` method wi
converted book will have its publisher set to "Hello World". For more information about converted book will have its publisher set to "Hello World". For more information about
|app|'s plugin system, read on... |app|'s plugin system, read on...
A Hello World GUI plugin
---------------------------
Here's a simple Hello World plugin for the |app| GUI. It will cause a box to popup with the message "Hellooo World!" when you press Ctrl+Shift+H
.. code-block:: python
from calibre.customize import InterfaceActionBase
class HelloWorldBase(InterfaceActionBase):
name = 'Hello World GUI'
author = 'The little green man'
def load_actual_plugin(self, gui):
from calibre.gui2.actions import InterfaceAction
class HelloWorld(InterfaceAction):
name = 'Hello World GUI'
action_spec = ('Hello World!', 'add_book.png', None,
_('Ctrl+Shift+H'))
def genesis(self):
self.qaction.triggered.connect(self.hello_world)
def hello_world(self, *args):
from calibre.gui2 import info_dialog
info_dialog(self.gui, 'Hello World!', 'Hellooo World!',
show=True)
return HelloWorld(gui, self.site_customization)
You can also have it show up in the toolbars/context menu by going to Preferences->Toolbars and adding this plugin to the locations you want it to be in.
While this plugin is utterly useless, note that all calibre GUI actions like adding/saving/removing/viewing/etc. are implemented as plugins, so there is no limit to what you can achieve. The key thing to remember is that the plugin has access to the full |app| GUI via ``self.gui``.
The Plugin base class The Plugin base class
------------------------ ------------------------

View File

@ -161,11 +161,20 @@ The base class for such devices is :class:`calibre.devices.usbms.driver.USBMS`.
User Interface Actions User Interface Actions
-------------------------- --------------------------
If you are adding your own plugin in a zip file, you should subclass both InterfaceActionBase and InterfaceAction. The :meth:`load_actual_plugin` method of you InterfaceActionBase subclass must return an instantiated object of your InterfaceBase subclass.
.. autoclass:: calibre.gui2.actions.InterfaceAction .. autoclass:: calibre.gui2.actions.InterfaceAction
:show-inheritance: :show-inheritance:
:members: :members:
:member-order: bysource :member-order: bysource
.. autoclass:: calibre.customize.InterfaceActionBase
:show-inheritance:
:members:
:member-order: bysource
Preferences Plugins Preferences Plugins
-------------------------- --------------------------

View File

@ -4,9 +4,9 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: calibre 0.7.30\n" "Project-Id-Version: calibre 0.7.31\n"
"POT-Creation-Date: 2010-11-26 10:43+MST\n" "POT-Creation-Date: 2010-11-27 11:31+MST\n"
"PO-Revision-Date: 2010-11-26 10:43+MST\n" "PO-Revision-Date: 2010-11-27 11:31+MST\n"
"Last-Translator: Automatically generated\n" "Last-Translator: Automatically generated\n"
"Language-Team: LANGUAGE\n" "Language-Team: LANGUAGE\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"