Merge from trunk

This commit is contained in:
Sengian 2011-01-06 09:11:30 +01:00
commit 7b09e65c5d
37 changed files with 928 additions and 165 deletions

View File

@ -69,9 +69,12 @@ categories_use_field_for_author_name = 'author'
# avg_rating: the averate rating of all the books referencing this item # avg_rating: the averate rating of all the books referencing this item
# sort: the sort value. For authors, this is the author_sort for that author # sort: the sort value. For authors, this is the author_sort for that author
# category: the category (e.g., authors, series) that the item is in. # category: the category (e.g., authors, series) that the item is in.
categories_collapsed_name_template = '{first.sort:shorten(4,'',0)} - {last.sort:shorten(4,'',0)}' # Note that the "r'" in front of the { is necessary if there are backslashes
categories_collapsed_rating_template = '{first.avg_rating:4.2f:ifempty(0)} - {last.avg_rating:4.2f:ifempty(0)}' # (\ characters) in the template. It doesn't hurt anything to leave it there
categories_collapsed_popularity_template = '{first.count:d} - {last.count:d}' # even if there aren't any backslashes.
categories_collapsed_name_template = r'{first.sort:shorten(4,'',0)} - {last.sort:shorten(4,'',0)}'
categories_collapsed_rating_template = r'{first.avg_rating:4.2f:ifempty(0)} - {last.avg_rating:4.2f:ifempty(0)}'
categories_collapsed_popularity_template = r'{first.count:d} - {last.count:d}'
# Set whether boolean custom columns are two- or three-valued. # Set whether boolean custom columns are two- or three-valued.

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@ -0,0 +1,86 @@
__license__ = 'GPL v3'
__copyright__ = '2011, Darko Miletic <darko.miletic at gmail.com>'
'''
www.arabianbusiness.com
'''
from calibre.web.feeds.news import BasicNewsRecipe
class Arabian_Business(BasicNewsRecipe):
title = 'Arabian Business'
__author__ = 'Darko Miletic'
description = 'Comprehensive Guide to Middle East Business & Gulf Industry News including,Banking & Finance,Construction,Energy,Media & Marketing,Real Estate,Transportation,Travel,Technology,Politics,Healthcare,Lifestyle,Jobs & UAE guide.Top Gulf & Dubai Business News.'
publisher = 'Arabian Business Publishing Ltd.'
category = 'ArabianBusiness.com,Arab Business News,Middle East Business News,Middle East Business,Arab Media News,Industry Events,Middle East Industry News,Arab Business Industry,Dubai Business News,Financial News,UAE Business News,Middle East Press Releases,Gulf News,Arab News,GCC Business News,Banking Finance,Media Marketing,Construction,Oil Gas,Retail,Transportation,Travel Hospitality,Photos,Videos,Life Style,Fashion,United Arab Emirates,UAE,Dubai,Sharjah,Abu Dhabi,Qatar,KSA,Saudi Arabia,Bahrain,Kuwait,Oman,Europe,South Asia,America,Asia,news'
oldest_article = 2
max_articles_per_feed = 200
no_stylesheets = True
encoding = 'utf8'
use_embedded_content = False
language = 'en'
remove_empty_feeds = True
publication_type = 'newsportal'
masthead_url = 'http://www.arabianbusiness.com/skins/ab.main/gfx/arabianbusiness_logo_sm.gif'
extra_css = """
body{font-family: Georgia,serif }
img{margin-bottom: 0.4em; margin-top: 0.4em; display:block}
.byline,.dateline{font-size: small; display: inline; font-weight: bold}
ul{list-style: none outside none;}
"""
conversion_options = {
'comment' : description
, 'tags' : category
, 'publisher' : publisher
, 'language' : language
}
remove_tags_before=dict(attrs={'id':'article-title'})
remove_tags = [
dict(name=['meta','link','base','iframe','embed','object'])
,dict(attrs={'class':'printfooter'})
]
remove_attributes=['lang']
feeds = [
(u'Africa' , u'http://www.arabianbusiness.com/world/Africa/?service=rss' )
,(u'Americas' , u'http://www.arabianbusiness.com/world/americas/?service=rss' )
,(u'Asia Pacific' , u'http://www.arabianbusiness.com/world/asia-pacific/?service=rss' )
,(u'Europe' , u'http://www.arabianbusiness.com/world/europe/?service=rss' )
,(u'Middle East' , u'http://www.arabianbusiness.com/world/middle-east/?service=rss' )
,(u'South Asia' , u'http://www.arabianbusiness.com/world/south-asia/?service=rss' )
,(u'Banking & Finance', u'http://www.arabianbusiness.com/industries/banking-finance/?service=rss' )
,(u'Construction' , u'http://www.arabianbusiness.com/industries/construction/?service=rss' )
,(u'Education' , u'http://www.arabianbusiness.com/industries/education/?service=rss' )
,(u'Energy' , u'http://www.arabianbusiness.com/industries/energy/?service=rss' )
,(u'Healthcare' , u'http://www.arabianbusiness.com/industries/healthcare/?service=rss' )
,(u'Media' , u'http://www.arabianbusiness.com/industries/media/?service=rss' )
,(u'Real Estate' , u'http://www.arabianbusiness.com/industries/real-estate/?service=rss' )
,(u'Retail' , u'http://www.arabianbusiness.com/industries/retail/?service=rss' )
,(u'Technology' , u'http://www.arabianbusiness.com/industries/technology/?service=rss' )
,(u'Transport' , u'http://www.arabianbusiness.com/industries/transport/?service=rss' )
,(u'Travel' , u'http://www.arabianbusiness.com/industries/travel-hospitality/?service=rss')
,(u'Equities' , u'http://www.arabianbusiness.com/markets/equities/?service=rss' )
,(u'Commodities' , u'http://www.arabianbusiness.com/markets/commodities/?service=rss' )
,(u'Currencies' , u'http://www.arabianbusiness.com/markets/currencies/?service=rss' )
,(u'Market Data' , u'http://www.arabianbusiness.com/markets/market-data/?service=rss' )
,(u'Comment' , u'http://www.arabianbusiness.com/opinion/comment/?service=rss' )
,(u'Think Tank' , u'http://www.arabianbusiness.com/opinion/think-tank/?service=rss' )
,(u'Arts' , u'http://www.arabianbusiness.com/lifestyle/arts/?service=rss' )
,(u'Cars' , u'http://www.arabianbusiness.com/lifestyle/cars/?service=rss' )
,(u'Food' , u'http://www.arabianbusiness.com/lifestyle/food/?service=rss' )
,(u'Sport' , u'http://www.arabianbusiness.com/lifestyle/sport/?service=rss' )
]
def print_version(self, url):
return url + '?service=printer&page='
def preprocess_html(self, soup):
for item in soup.findAll(style=True):
del item['style']
for alink in soup.findAll('a'):
if alink.string is not None:
tstr = alink.string
alink.replaceWith(tstr)
return soup

View File

@ -0,0 +1,70 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__author__ = 'Gerardo Diez'
__copyright__ = 'Gerardo Diez<gerardo.diez.garcia@gmail.com>'
description = 'Main daily newspaper from Spain - v1.00 (05, Enero 2011)'
__docformat__ = 'restructuredtext en'
'''
deia.com
'''
from calibre.web.feeds.recipes import BasicNewsRecipe
class Deia(BasicNewsRecipe):
title ='Deia'
__author__ ='Gerardo Diez'
publisher ='Editorial Iparraguirre, S.A'
category ='news, politics, finances, world, spain, euskadi'
publication_type ='newspaper'
oldest_article =1
max_articles_per_feed =100
simultaneous_downloads =10
cover_url ='http://2.bp.blogspot.com/_RjrWzC6tI14/TM6jrPLaBZI/AAAAAAAAFaI/ayffwxidFEY/s1600/2009-10-13-logo-deia.jpg'
timefmt ='[%a, %d %b, %Y]'
encoding ='utf8'
language ='es_ES'
remove_javascript =True
remove_tags_after =dict(id='Texto')
remove_tags_before =dict(id='Texto')
remove_tags =[dict(name='div', attrs={'class':['Herramientas ', 'Multimedia']})]
no_stylesheets =True
extra_css ='h1 {margin-bottom: .15em;font-size: 2.7em; font-family: Georgia, "Times New Roman", Times, serif;} .Antetitulo {margin: 1em 0;text-transform: uppercase;color: #999;} .PieFoto {margin: .1em 0;padding: .5em .5em .5em .5em;background: #F0F0F0;} .PieFoto p {margin-bottom: 0;font-family: Georgia,"Times New Roman",Times,serif;font-weight: bold; font-style: italic; color: #666;}'
keep_only_tags =[dict(name='div', attrs={'class':['Texto ', 'NoticiaFicha ']})]
feeds = [
(u'Bizkaia' ,u'http://www.deia.com/index.php/services/rss?seccion=bizkaia'),
(u'Bilbao' ,u'http://www.deia.com/index.php/services/rss?seccion=bilbao'),
(u'Hemendik eta Handik' ,u'http://www.deia.com/index.php/services/rss?seccion=hemendik-eta-handik'),
(u'Margen Derecha' ,u'http://www.deia.com/index.php/services/rss?seccion=margen-derecha'),
(u'Encartaciones y Margen Izquierda' ,u'http://www.deia.com/index.php/services/rss?seccion=margen-izquierda-encartaciones'),
(u'Costa' ,u'http://www.deia.com/index.php/services/rss?seccion=costa'),
(u'Duranguesado' ,u'http://www.deia.com/index.php/services/rss?seccion=duranguesado'),
(u'Llodio-Nervión' ,u'http://www.deia.com/index.php/services/rss?seccion=llodio-nervion'),
(u'Arratia-Nervión' ,u'http://www.deia.com/index.php/services/rss?seccion=arratia-nervion'),
(u'Uribe-Txorierri' ,u'http://www.deia.com/index.php/services/rss?seccion=uribe-txorierri'),
(u'Ecos de sociedad' ,u'http://www.deia.com/index.php/services/rss?seccion=ecos-de-sociedad'),
(u'Sucesos' ,u'http://www.deia.com/index.php/services/rss?seccion=sucesos'),
(u'Política' ,u'http://www.deia.com/index.php/services/rss?seccion=politica'),
(u'Euskadi' ,u'http://www.deia.com/index.php/services/rss?seccion=politica/euskadi'),
(u'España' ,u'http://www.deia.com/index.php/services/rss?seccion=politica/espana'),
(u'Sociedad',u'http://www.deia.com/index.php/services/rss?seccion=sociedad'),
(u'Euskadi' ,u'http://www.deia.com/index.php/services/rss?seccion=socidad/euskadi'),
(u'Sociedad.España' ,u'http://www.deia.com/index.php/services/rss?seccion=sociedad/espana'),
(u'Ocio y Cultura' ,u'http://www.deia.com/index.php/services/rss?seccion=ocio-y-cultura'),
#(u'Cultura' ,u'http://www.deia.com/index.php/services/rss?seccion=cultura'),
#(u'Ocio' ,u'http://www.deia.com/index.php/services/rss?seccion=ocio'),
(u'On' ,u'http://www.deia.com/index.php/services/rss?seccion=on'),
(u'Agenda' ,u'http://www.deia.com/index.php/services/rss?seccion=agenda'),
(u'Comunicación' ,u'http://www.deia.com/index.php/services/rss?seccion=comunicacion'),
(u'Viajes' ,u'http://www.deia.com/index.php/services/rss?seccion=viajes'),
(u'¡Mundo!' ,u'http://www.deia.com/index.php/services/rss?seccion=que-mundo'),
(u'Humor' ,u'http://www.deia.com/index.php/services/rss?seccion=humor'),
(u'Opinión' ,u'http://www.deia.com/index.php/services/rss?seccion=opinion'),
(u'Editorial' ,u'http://www.deia.com/index.php/services/rss?seccion=editorial'),
(u'Tribuna abierta' ,u'http://www.deia.com/index.php/services/rss?seccion=tribuna-abierta'),
(u'Colaboración' ,u'http://www.deia.com/index.php/services/rss?seccion=colaboracion'),
(u'Columnistas' ,u'http://www.deia.com/index.php/services/rss?seccion=columnistas'),
(u'Deportes' ,u'http://www.deia.com/index.php/services/rss?seccion=deportes'),
(u'Athletic' ,u'http://www.deia.com/index.php/services/rss?seccion=athletic'),
(u'Economía' ,'http://www.deia.com/index.php/services/rss?seccion=economia'),
(u'Mundo' ,u'http://www.deia.com/index.php/services/rss?seccion=mundo')]

View File

@ -0,0 +1,23 @@
from calibre.web.feeds.news import BasicNewsRecipe
class AdvancedUserRecipe1293122276(BasicNewsRecipe):
title = u'Smarter Planet | Tumblr for eReaders'
__author__ = 'Jack Mason'
author = 'IBM Global Business Services'
publisher = 'IBM'
category = 'news, technology, IT, internet of things, analytics'
oldest_article = 7
max_articles_per_feed = 30
no_stylesheets = True
use_embedded_content = False
masthead_url = 'http://30.media.tumblr.com/tumblr_l70dow9UmU1qzs4rbo1_r3_250.jpg'
remove_tags_before = dict(id='item')
remove_tags_after = dict(id='item')
remove_tags = [dict(attrs={'class':['sidebar', 'about', 'footer', 'description,' 'disqus', 'nav', 'notes', 'disqus_thread']}),
dict(id=['sidebar', 'footer', 'disqus', 'nav', 'notes', 'likes_container', 'description', 'disqus_thread', 'about']),
dict(name=['script', 'noscript', 'style'])]
feeds = [(u'Smarter Planet Tumblr', u'http://smarterplanet.tumblr.com/mobile/rss')]

View File

@ -0,0 +1,115 @@
__license__ = 'GPL v3'
__copyright__ = '2010, Darko Miletic <darko.miletic at gmail.com>'
'''
www.thesundaytimes.co.uk
'''
import urllib
from calibre.web.feeds.news import BasicNewsRecipe
class TimesOnline(BasicNewsRecipe):
title = 'The Sunday Times UK'
__author__ = 'Darko Miletic'
description = 'news from United Kingdom and World'
language = 'en_GB'
publisher = 'Times Newspapers Ltd'
category = 'news, politics, UK'
oldest_article = 3
max_articles_per_feed = 100
no_stylesheets = True
use_embedded_content = False
encoding = 'utf-8'
delay = 1
needs_subscription = True
publication_type = 'newspaper'
masthead_url = 'http://www.thesundaytimes.co.uk/sto/public/images/logos/logo-home.gif'
INDEX = 'http://www.thesundaytimes.co.uk'
PREFIX = u'http://www.thesundaytimes.co.uk/sto/'
extra_css = """
.author-name,.authorName{font-style: italic}
.published-date,.multi-position-photo-text{font-family: Arial,Helvetica,sans-serif;
font-size: small; color: gray;
display:block; margin-bottom: 0.5em}
body{font-family: Georgia,"Times New Roman",Times,serif}
"""
conversion_options = {
'comment' : description
, 'tags' : category
, 'publisher' : publisher
, 'language' : language
}
def get_browser(self):
br = BasicNewsRecipe.get_browser()
br.open('http://www.timesplus.co.uk/tto/news/?login=false&url=http://www.thesundaytimes.co.uk/sto/')
if self.username is not None and self.password is not None:
data = urllib.urlencode({ 'userName':self.username
,'password':self.password
,'keepMeLoggedIn':'false'
})
br.open('https://www.timesplus.co.uk/iam/app/authenticate',data)
return br
remove_tags = [
dict(name=['object','link','iframe','base','meta'])
,dict(attrs={'class':'tools comments-parent' })
]
remove_attributes=['lang']
keep_only_tags = [
dict(attrs={'class':'standard-content'})
,dict(attrs={'class':'f-author'})
,dict(attrs={'id':'bodycopy'})
]
remove_tags_after=dict(attrs={'class':'tools_border'})
feeds = [
(u'UK News' , PREFIX + u'news/uk_news/' )
,(u'World' , PREFIX + u'news/world_news/' )
,(u'Politics' , PREFIX + u'news/Politics/' )
,(u'Focus' , PREFIX + u'news/focus/' )
,(u'Insight' , PREFIX + u'news/insight/' )
,(u'Ireland' , PREFIX + u'news/ireland/' )
,(u'Columns' , PREFIX + u'comment/columns/' )
,(u'Arts' , PREFIX + u'culture/arts/' )
,(u'Books' , PREFIX + u'culture/books/' )
,(u'Film and TV' , PREFIX + u'culture/film_and_tv/' )
,(u'Sport' , PREFIX + u'sport/' )
,(u'Business' , PREFIX + u'business' )
,(u'Money' , PREFIX + u'business/money/' )
,(u'Style' , PREFIX + u'style/' )
,(u'Travel' , PREFIX + u'travel/' )
,(u'Clarkson' , PREFIX + u'ingear/clarkson/' )
,(u'Cars' , PREFIX + u'ingear/cars/' )
,(u'Bikes' , PREFIX + u'ingear/2_Wheels/' )
,(u'Tech' , PREFIX + u'ingear/Tech___Games/' )
,(u'Magazine' , PREFIX + u'Magazine/' )
]
def preprocess_html(self, soup):
for item in soup.findAll(style=True):
del item['style']
return self.adeify_images(soup)
def parse_index(self):
totalfeeds = []
lfeeds = self.get_feeds()
for feedobj in lfeeds:
feedtitle, feedurl = feedobj
self.report_progress(0, _('Fetching feed')+' %s...'%(feedtitle if feedtitle else feedurl))
articles = []
soup = self.index_to_soup(feedurl)
for atag in soup.findAll('a',href=True):
parentName = atag.parent.name
title = self.tag_to_string(atag).strip()
if (parentName == 'h2' or parentName == 'h3') and title is not None and title != '':
url = self.INDEX + atag['href']
articles.append({
'title' :title
,'date' :''
,'url' :url
,'description':''
})
totalfeeds.append((feedtitle, articles))
return totalfeeds

View File

@ -121,7 +121,7 @@ if iswindows:
poppler_lib_dirs = consolidate('POPPLER_LIB_DIR', sw_lib_dir) poppler_lib_dirs = consolidate('POPPLER_LIB_DIR', sw_lib_dir)
popplerqt4_lib_dirs = poppler_lib_dirs popplerqt4_lib_dirs = poppler_lib_dirs
poppler_libs = ['poppler'] poppler_libs = ['poppler']
magick_inc_dirs = [os.path.join(prefix, 'build', 'ImageMagick-6.5.6')] magick_inc_dirs = [os.path.join(prefix, 'build', 'ImageMagick-6.6.6')]
magick_lib_dirs = [os.path.join(magick_inc_dirs[0], 'VisualMagick', 'lib')] magick_lib_dirs = [os.path.join(magick_inc_dirs[0], 'VisualMagick', 'lib')]
magick_libs = ['CORE_RL_wand_', 'CORE_RL_magick_'] magick_libs = ['CORE_RL_wand_', 'CORE_RL_magick_']
podofo_inc = os.path.join(sw_inc_dir, 'podofo') podofo_inc = os.path.join(sw_inc_dir, 'podofo')

View File

@ -18,7 +18,7 @@ QT_DLLS = ['Core', 'Gui', 'Network', 'Svg', 'WebKit', 'Xml', 'XmlPatterns']
LIBUSB_DIR = 'C:\\libusb' LIBUSB_DIR = 'C:\\libusb'
LIBUNRAR = 'C:\\Program Files\\UnrarDLL\\unrar.dll' LIBUNRAR = 'C:\\Program Files\\UnrarDLL\\unrar.dll'
SW = r'C:\cygwin\home\kovid\sw' SW = r'C:\cygwin\home\kovid\sw'
IMAGEMAGICK = os.path.join(SW, 'build', 'ImageMagick-6.5.6', IMAGEMAGICK = os.path.join(SW, 'build', 'ImageMagick-6.6.6',
'VisualMagick', 'bin') 'VisualMagick', 'bin')
VERSION = re.sub('[a-z]\d+', '', __version__) VERSION = re.sub('[a-z]\d+', '', __version__)

View File

@ -301,12 +301,14 @@ int projectType = MULTITHREADEDDLL;
Run configure.bat in a visual studio command prompt Run configure.bat in a visual studio command prompt
Run configure.exe generated by configure.bat
Edit magick/magick-config.h Edit magick/magick-config.h
Undefine ProvideDllMain and MAGICKCORE_X11_DELEGATE Undefine ProvideDllMain and MAGICKCORE_X11_DELEGATE
Now open VisualMagick/VisualDynamicMT.sln set to Release Now open VisualMagick/VisualDynamicMT.sln set to Release
Remove the CORE_xlib project Remove the CORE_xlib and UTIL_Imdisplay project CORE_Magick++
calibre calibre
--------- ---------

View File

@ -43,8 +43,8 @@ class Stage3(Command):
description = 'Stage 3 of the publish process' description = 'Stage 3 of the publish process'
sub_commands = ['upload_user_manual', 'upload_demo', 'sdist', sub_commands = ['upload_user_manual', 'upload_demo', 'sdist',
'upload_to_mobileread', 'upload_to_google_code', 'upload_to_google_code', 'tag_release', 'upload_to_server',
'tag_release', 'upload_to_server', 'upload_to_sourceforge', 'upload_to_sourceforge', 'upload_to_mobileread',
] ]
class Stage4(Command): class Stage4(Command):

View File

@ -80,6 +80,100 @@ class Plugin(object): # {{{
''' '''
pass pass
def config_widget(self):
'''
Implement this method and :meth:`save_settings` in your plugin to
use a custom configuration dialog, rather then relying on the simple
string based default customization.
This method, if implemented, must return a QWidget. The widget can have
an optional method validate() that takes no arguments and is called
immediately after the user clicks OK. Changes are applied if and only
if the method returns True.
'''
raise NotImplementedError()
def save_settings(self, config_widget):
'''
Save the settings specified by the user with config_widget.
:param config_widget: The widget returned by :meth:`config_widget`.
'''
raise NotImplementedError()
def do_user_config(self, parent=None):
'''
This method shows a configuration dialog for this plugin. It returns
True if the user clicks OK, False otherwise. The changes are
automatically applied.
'''
from PyQt4.Qt import QDialog, QDialogButtonBox, QVBoxLayout, \
QLabel, Qt, QLineEdit
from calibre.gui2 import gprefs
prefname = 'plugin config dialog:'+self.type + ':' + self.name
geom = gprefs.get(prefname, None)
config_dialog = QDialog(parent)
button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
v = QVBoxLayout(config_dialog)
def size_dialog():
if geom is None:
config_dialog.resize(config_dialog.sizeHint())
else:
config_dialog.restoreGeometry(geom)
button_box.accepted.connect(config_dialog.accept)
button_box.rejected.connect(config_dialog.reject)
config_dialog.setWindowTitle(_('Customize') + ' ' + self.name)
try:
config_widget = self.config_widget()
except NotImplementedError:
config_widget = None
if config_widget is not None:
v.addWidget(config_widget)
v.addWidget(button_box)
size_dialog()
config_dialog.exec_()
if config_dialog.result() == QDialog.Accepted:
if hasattr(config_widget, 'validate'):
if config_widget.validate():
self.save_settings(config_widget)
else:
self.save_settings(config_widget)
else:
from calibre.customize.ui import plugin_customization, \
customize_plugin
help_text = self.customization_help(gui=True)
help_text = QLabel(help_text, config_dialog)
help_text.setWordWrap(True)
help_text.setTextInteractionFlags(Qt.LinksAccessibleByMouse
| Qt.LinksAccessibleByKeyboard)
help_text.setOpenExternalLinks(True)
v.addWidget(help_text)
sc = plugin_customization(self)
if not sc:
sc = ''
sc = sc.strip()
sc = QLineEdit(sc, config_dialog)
v.addWidget(sc)
v.addWidget(button_box)
size_dialog()
config_dialog.exec_()
if config_dialog.result() == QDialog.Accepted:
sc = unicode(sc.text()).strip()
customize_plugin(self, sc)
geom = bytearray(config_dialog.saveGeometry())
gprefs[prefname] = geom
return config_dialog.result()
def load_resources(self, names): def load_resources(self, names):
''' '''
If this plugin comes in a ZIP file (user added plugin), this method If this plugin comes in a ZIP file (user added plugin), this method

View File

@ -259,7 +259,7 @@ class EEEREADER(USBMS):
PRODUCT_ID = [0x178f] PRODUCT_ID = [0x178f]
BCD = [0x0319] BCD = [0x0319]
EBOOK_DIR_MAIN = 'Books' EBOOK_DIR_MAIN = EBOOK_DIR_CARD_A = 'Book'
VENDOR_NAME = 'LINUX' VENDOR_NAME = 'LINUX'
WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = 'FILE-STOR_GADGET' WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = 'FILE-STOR_GADGET'

View File

@ -61,14 +61,26 @@ class PRS505(USBMS):
ALL_BY_TITLE = _('All by title') ALL_BY_TITLE = _('All by title')
ALL_BY_AUTHOR = _('All by author') ALL_BY_AUTHOR = _('All by author')
EXTRA_CUSTOMIZATION_MESSAGE = _('Comma separated list of metadata fields ' EXTRA_CUSTOMIZATION_MESSAGE = [
_('Comma separated list of metadata fields '
'to turn into collections on the device. Possibilities include: ')+\ 'to turn into collections on the device. Possibilities include: ')+\
'series, tags, authors' +\ 'series, tags, authors' +\
_('. Two special collections are available: %s:%s and %s:%s. Add ' _('. Two special collections are available: %s:%s and %s:%s. Add '
'these values to the list to enable them. The collections will be ' 'these values to the list to enable them. The collections will be '
'given the name provided after the ":" character.')%( 'given the name provided after the ":" character.')%(
'abt', ALL_BY_TITLE, 'aba', ALL_BY_AUTHOR) 'abt', ALL_BY_TITLE, 'aba', ALL_BY_AUTHOR),
EXTRA_CUSTOMIZATION_DEFAULT = ', '.join(['series', 'tags']) _('Upload separate cover thumbnails for books (newer readers)') +
':::'+_('Normally, the SONY readers get the cover image from the'
' ebook file itself. With this option, calibre will send a '
'separate cover image to the reader, useful if you are '
'sending DRMed books in which you cannot change the cover.'
' WARNING: This option should only be used with newer '
'SONY readers: 350, 650, 950 and newer.'),
]
EXTRA_CUSTOMIZATION_DEFAULT = [
', '.join(['series', 'tags']),
False
]
plugboard = None plugboard = None
plugboard_func = None plugboard_func = None
@ -159,7 +171,7 @@ class PRS505(USBMS):
opts = self.settings() opts = self.settings()
if opts.extra_customization: if opts.extra_customization:
collections = [x.strip() for x in collections = [x.strip() for x in
opts.extra_customization.split(',')] opts.extra_customization[0].split(',')]
else: else:
collections = [] collections = []
debug_print('PRS505: collection fields:', collections) debug_print('PRS505: collection fields:', collections)
@ -186,8 +198,12 @@ class PRS505(USBMS):
self.plugboard_func = pb_func self.plugboard_func = pb_func
def upload_cover(self, path, filename, metadata, filepath): def upload_cover(self, path, filename, metadata, filepath):
return # Disabled as the SONY's don't need this thumbnail anyway and opts = self.settings()
# older models don't auto delete it if not opts.extra_customization[1]:
# Building thumbnails disabled
debug_print('PRS505: not uploading covers')
return
debug_print('PRS505: uploading covers')
if metadata.thumbnail and metadata.thumbnail[-1]: if metadata.thumbnail and metadata.thumbnail[-1]:
path = path.replace('/', os.sep) path = path.replace('/', os.sep)
is_main = path.startswith(self._main_prefix) is_main = path.startswith(self._main_prefix)

View File

@ -10,7 +10,21 @@ from calibre.utils.config import Config, ConfigProxy
class DeviceConfig(object): class DeviceConfig(object):
HELP_MESSAGE = _('Configure Device') HELP_MESSAGE = _('Configure Device')
#: Can be None, a string or a list of strings. When it is a string
#: that string is used for the help text and the actual customization value
#: can be read from ``dev.settings().extra_customization``.
#: If it a list of strings, then dev.settings().extra_customization will
#: also be a list. In this case, you *must* ensure that
#: EXTRA_CUSTOMIZATION_DEFAULT is also a list. The list can contain either
#: boolean values or strings, in which case a checkbox or line edit will be
#: used for them in the config widget, automatically.
#: If a string contains ::: then the text after it is interpreted as the
#: tooltip
EXTRA_CUSTOMIZATION_MESSAGE = None EXTRA_CUSTOMIZATION_MESSAGE = None
#: The default value for extra customization. If you set
#: EXTRA_CUSTOMIZATION_MESSAGE you *must* set this as well.
EXTRA_CUSTOMIZATION_DEFAULT = None EXTRA_CUSTOMIZATION_DEFAULT = None
SUPPORTS_SUB_DIRS = False SUPPORTS_SUB_DIRS = False
@ -73,6 +87,14 @@ class DeviceConfig(object):
if cls.SUPPORTS_USE_AUTHOR_SORT: if cls.SUPPORTS_USE_AUTHOR_SORT:
proxy['use_author_sort'] = config_widget.use_author_sort() proxy['use_author_sort'] = config_widget.use_author_sort()
if cls.EXTRA_CUSTOMIZATION_MESSAGE: if cls.EXTRA_CUSTOMIZATION_MESSAGE:
if isinstance(cls.EXTRA_CUSTOMIZATION_MESSAGE, list):
ec = []
for i in range(0, len(cls.EXTRA_CUSTOMIZATION_MESSAGE)):
if hasattr(config_widget.opt_extra_customization[i], 'isChecked'):
ec.append(config_widget.opt_extra_customization[i].isChecked())
else:
ec.append(unicode(config_widget.opt_extra_customization[i].text()).strip())
else:
ec = unicode(config_widget.opt_extra_customization.text()).strip() ec = unicode(config_widget.opt_extra_customization.text()).strip()
if not ec: if not ec:
ec = None ec = None
@ -82,7 +104,16 @@ class DeviceConfig(object):
@classmethod @classmethod
def settings(cls): def settings(cls):
return cls._config().parse() opts = cls._config().parse()
if isinstance(cls.EXTRA_CUSTOMIZATION_DEFAULT, list):
if opts.extra_customization is None:
opts.extra_customization = []
if not isinstance(opts.extra_customization, list):
opts.extra_customization = [opts.extra_customization]
for i,d in enumerate(cls.EXTRA_CUSTOMIZATION_DEFAULT):
if i >= len(opts.extra_customization):
opts.extra_customization.append(d)
return opts
@classmethod @classmethod
def save_template(cls): def save_template(cls):

View File

@ -16,6 +16,7 @@ from calibre.ebooks.metadata.book import TOP_LEVEL_CLASSIFIERS
from calibre.ebooks.metadata.book import ALL_METADATA_FIELDS from calibre.ebooks.metadata.book import ALL_METADATA_FIELDS
from calibre.library.field_metadata import FieldMetadata from calibre.library.field_metadata import FieldMetadata
from calibre.utils.date import isoformat, format_date from calibre.utils.date import isoformat, format_date
from calibre.utils.icu import sort_key
from calibre.utils.formatter import TemplateFormatter from calibre.utils.formatter import TemplateFormatter
@ -38,15 +39,16 @@ class SafeFormat(TemplateFormatter):
def get_value(self, key, args, kwargs): def get_value(self, key, args, kwargs):
try: try:
key = key.lower()
if key != 'title_sort': if key != 'title_sort':
key = field_metadata.search_term_to_field_key(key.lower()) key = field_metadata.search_term_to_field_key(key)
b = self.book.get_user_metadata(key, False) b = self.book.get_user_metadata(key, False)
if b and b['datatype'] == 'int' and self.book.get(key, 0) == 0: if b and b['datatype'] == 'int' and self.book.get(key, 0) == 0:
v = '' v = ''
elif b and b['datatype'] == 'float' and self.book.get(key, 0.0) == 0.0: elif b and b['datatype'] == 'float' and self.book.get(key, 0.0) == 0.0:
v = '' v = ''
else: else:
ign, v = self.book.format_field(key.lower(), series_with_index=False) ign, v = self.book.format_field(key, series_with_index=False)
if v is None: if v is None:
return '' return ''
if v == '': if v == '':
@ -489,7 +491,7 @@ class Metadata(object):
return authors_to_string(self.authors) return authors_to_string(self.authors)
def format_tags(self): def format_tags(self):
return u', '.join([unicode(t) for t in self.tags]) return u', '.join([unicode(t) for t in sorted(self.tags, key=sort_key)])
def format_rating(self): def format_rating(self):
return unicode(self.rating) return unicode(self.rating)
@ -529,7 +531,7 @@ class Metadata(object):
orig_res = res orig_res = res
datatype = cmeta['datatype'] datatype = cmeta['datatype']
if datatype == 'text' and cmeta['is_multiple']: if datatype == 'text' and cmeta['is_multiple']:
res = u', '.join(res) res = u', '.join(sorted(res, key=sort_key))
elif datatype == 'series' and series_with_index: elif datatype == 'series' and series_with_index:
if self.get_extra(key) is not None: if self.get_extra(key) is not None:
res = res + \ res = res + \
@ -559,7 +561,7 @@ class Metadata(object):
elif key == 'series_index': elif key == 'series_index':
res = self.format_series_index(res) res = self.format_series_index(res)
elif datatype == 'text' and fmeta['is_multiple']: elif datatype == 'text' and fmeta['is_multiple']:
res = u', '.join(res) res = u', '.join(sorted(res, key=sort_key))
elif datatype == 'series' and series_with_index: elif datatype == 'series' and series_with_index:
res = res + ' [%s]'%self.format_series_index() res = res + ' [%s]'%self.format_series_index()
elif datatype == 'datetime': elif datatype == 'datetime':

View File

@ -55,10 +55,10 @@ class Query(object):
BASE_URL = 'http://isbndb.com/api/books.xml?' BASE_URL = 'http://isbndb.com/api/books.xml?'
def __init__(self, key, title=None, author=None, publisher=None, isbn=None, def __init__(self, key, title=None, author=None, publisher=None, isbn=None,
keywords=None, max_results=40): keywords=None, max_results=30):
assert not(title is None and author is None and publisher is None and \ assert not(title is None and author is None and publisher is None and \
isbn is None and keywords is None) isbn is None and keywords is None)
assert (max_results < 41) assert (max_results < 31)
if title == _('Unknown'): if title == _('Unknown'):
title=None title=None

View File

@ -18,7 +18,6 @@ class xISBN(object):
self._data = [] self._data = []
self._map = {} self._map = {}
self.br = browser()
self.isbn_pat = re.compile(r'[^0-9X]', re.IGNORECASE) self.isbn_pat = re.compile(r'[^0-9X]', re.IGNORECASE)
def purify(self, isbn): def purify(self, isbn):
@ -26,7 +25,7 @@ class xISBN(object):
def fetch_data(self, isbn): def fetch_data(self, isbn):
url = self.QUERY%isbn url = self.QUERY%isbn
data = self.br.open_novisit(url).read() data = browser().open_novisit(url).read()
data = json.loads(data) data = json.loads(data)
if data.get('stat', None) != 'ok': if data.get('stat', None) != 'ok':
return [] return []

View File

@ -103,7 +103,7 @@ class CoverManager(object):
from calibre.ebooks import calibre_cover from calibre.ebooks import calibre_cover
img_data = calibre_cover(title, authors_to_string(authors), img_data = calibre_cover(title, authors_to_string(authors),
series_string=series_string) series_string=series_string)
id, href = self.oeb.manifest.generate('cover_image', id, href = self.oeb.manifest.generate('cover',
'cover_image.jpg') 'cover_image.jpg')
item = self.oeb.manifest.add(id, href, guess_type('t.jpg')[0], item = self.oeb.manifest.add(id, href, guess_type('t.jpg')[0],
data=img_data) data=img_data)

View File

@ -127,9 +127,6 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="5" column="1" colspan="3">
<widget class="QLineEdit" name="opt_input_encoding"/>
</item>
<item row="6" column="0" colspan="2"> <item row="6" column="0" colspan="2">
<widget class="QCheckBox" name="opt_remove_paragraph_spacing"> <widget class="QCheckBox" name="opt_remove_paragraph_spacing">
<property name="text"> <property name="text">
@ -244,8 +241,22 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="5" column="1" colspan="3">
<widget class="EncodingComboBox" name="opt_input_encoding">
<property name="editable">
<bool>true</bool>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
<customwidgets>
<customwidget>
<class>EncodingComboBox</class>
<extends>QComboBox</extends>
<header>widgets.h</header>
</customwidget>
</customwidgets>
<resources> <resources>
<include location="../../../../resources/images.qrc"/> <include location="../../../../resources/images.qrc"/>
<include location="../../../../resources/images.qrc"/> <include location="../../../../resources/images.qrc"/>

View File

@ -4,7 +4,10 @@ __license__ = 'GPL 3'
__copyright__ = '2009, John Schember <john@nachtimwald.com>' __copyright__ = '2009, John Schember <john@nachtimwald.com>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
from PyQt4.Qt import QWidget, QListWidgetItem, Qt, QVariant, SIGNAL import textwrap
from PyQt4.Qt import QWidget, QListWidgetItem, Qt, QVariant, SIGNAL, \
QLabel, QLineEdit, QCheckBox
from calibre.gui2 import error_dialog from calibre.gui2 import error_dialog
from calibre.gui2.device_drivers.configwidget_ui import Ui_ConfigWidget from calibre.gui2.device_drivers.configwidget_ui import Ui_ConfigWidget
@ -46,12 +49,38 @@ class ConfigWidget(QWidget, Ui_ConfigWidget):
else: else:
self.opt_use_author_sort.hide() self.opt_use_author_sort.hide()
if extra_customization_message: if extra_customization_message:
self.extra_customization_label.setText(extra_customization_message) def parse_msg(m):
msg, _, tt = m.partition(':::') if m else ('', '', '')
return msg.strip(), textwrap.fill(tt.strip(), 100)
if isinstance(extra_customization_message, list):
self.opt_extra_customization = []
for i, m in enumerate(extra_customization_message):
label_text, tt = parse_msg(m)
if isinstance(settings.extra_customization[i], bool):
self.opt_extra_customization.append(QCheckBox(label_text))
self.opt_extra_customization[-1].setToolTip(tt)
self.opt_extra_customization[i].setChecked(bool(settings.extra_customization[i]))
else:
self.opt_extra_customization.append(QLineEdit(self))
l = QLabel(label_text)
l.setToolTip(tt)
l.setBuddy(self.opt_extra_customization[i])
l.setWordWrap(True)
self.opt_extra_customization[i].setText(settings.extra_customization[i])
self.extra_layout.addWidget(l)
self.extra_layout.addWidget(self.opt_extra_customization[i])
else:
self.opt_extra_customization = QLineEdit()
label_text, tt = parse_msg(extra_customization_message)
l = QLabel(label_text)
l.setToolTip(tt)
l.setBuddy(self.opt_extra_customization)
l.setWordWrap(True)
if settings.extra_customization: if settings.extra_customization:
self.opt_extra_customization.setText(settings.extra_customization) self.opt_extra_customization.setText(settings.extra_customization)
else: self.extra_layout.addWidget(l)
self.extra_customization_label.setVisible(False) self.extra_layout.addWidget(self.opt_extra_customization)
self.opt_extra_customization.setVisible(False)
self.opt_save_template.setText(settings.save_template) self.opt_save_template.setText(settings.save_template)

View File

@ -98,20 +98,7 @@
</widget> </widget>
</item> </item>
<item row="6" column="0"> <item row="6" column="0">
<widget class="QLabel" name="extra_customization_label"> <layout class="QVBoxLayout" name="extra_layout"/>
<property name="text">
<string>Extra customization</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="buddy">
<cstring>opt_extra_customization</cstring>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLineEdit" name="opt_extra_customization"/>
</item> </item>
<item row="4" column="0"> <item row="4" column="0">
<widget class="QLabel" name="label"> <widget class="QLabel" name="label">

View File

@ -321,7 +321,8 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
if (f in ['author_sort'] or if (f in ['author_sort'] or
(fm[f]['datatype'] in ['text', 'series', 'enumeration'] (fm[f]['datatype'] in ['text', 'series', 'enumeration']
and fm[f].get('search_terms', None) and fm[f].get('search_terms', None)
and f not in ['formats', 'ondevice', 'sort'])): and f not in ['formats', 'ondevice', 'sort']) or
fm[f]['datatype'] in ['int', 'float', 'bool'] ):
self.all_fields.append(f) self.all_fields.append(f)
self.writable_fields.append(f) self.writable_fields.append(f)
if f in ['sort'] or fm[f]['datatype'] == 'composite': if f in ['sort'] or fm[f]['datatype'] == 'composite':
@ -431,12 +432,14 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
val = mi.get('title_sort', None) val = mi.get('title_sort', None)
else: else:
val = mi.get(field, None) val = mi.get(field, None)
if isinstance(val, (int, float, bool)):
val = str(val)
if val is None: if val is None:
val = [] if fm['is_multiple'] else [''] val = [] if fm['is_multiple'] else ['']
elif not fm['is_multiple']: elif not fm['is_multiple']:
val = [val] val = [val]
elif field == 'authors': elif field == 'authors':
val = [v.replace(',', '|') for v in val] val = [v.replace('|', ',') for v in val]
else: else:
val = [] val = []
return val return val
@ -566,17 +569,11 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
dest_val = mi.get(dest, '') dest_val = mi.get(dest, '')
if dest_val is None: if dest_val is None:
dest_val = [] dest_val = []
elif isinstance(dest_val, list): elif not isinstance(dest_val, list):
if dest == 'authors':
dest_val = [v.replace(',', '|') for v in dest_val]
else:
dest_val = [dest_val] dest_val = [dest_val]
else: else:
dest_val = [] dest_val = []
if len(val) > 0:
if src == 'authors':
val = [v.replace(',', '|') for v in val]
if dest_mode == 1: if dest_mode == 1:
val.extend(dest_val) val.extend(dest_val)
elif dest_mode == 2: elif dest_mode == 2:

View File

@ -3,7 +3,7 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
import re, copy import re, copy
from PyQt4.QtGui import QDialog, QDialogButtonBox from PyQt4.Qt import QDialog, QDialogButtonBox, QCompleter, Qt
from calibre.gui2.dialogs.search_ui import Ui_Dialog from calibre.gui2.dialogs.search_ui import Ui_Dialog
from calibre.library.caches import CONTAINS_MATCH, EQUALS_MATCH from calibre.library.caches import CONTAINS_MATCH, EQUALS_MATCH
@ -22,6 +22,28 @@ class SearchDialog(QDialog, Ui_Dialog):
key=lambda x: sort_key(x if x[0] != '#' else x[1:])) key=lambda x: sort_key(x if x[0] != '#' else x[1:]))
self.general_combo.addItems(searchables) self.general_combo.addItems(searchables)
all_authors = db.all_authors()
all_authors.sort(key=lambda x : sort_key(x[1]))
for i in all_authors:
id, name = i
name = name.strip().replace('|', ',')
self.authors_box.addItem(name)
self.authors_box.setEditText('')
self.authors_box.completer().setCompletionMode(QCompleter.PopupCompletion)
self.authors_box.setAutoCompletionCaseSensitivity(Qt.CaseInsensitive)
all_series = db.all_series()
all_series.sort(key=lambda x : sort_key(x[1]))
for i in all_series:
id, name = i
self.series_box.addItem(name)
self.series_box.setEditText('')
self.series_box.completer().setCompletionMode(QCompleter.PopupCompletion)
self.series_box.setAutoCompletionCaseSensitivity(Qt.CaseInsensitive)
all_tags = db.all_tags()
self.tags_box.update_tags_cache(all_tags)
self.box_last_values = copy.deepcopy(box_values) self.box_last_values = copy.deepcopy(box_values)
if self.box_last_values: if self.box_last_values:
for k,v in self.box_last_values.items(): for k,v in self.box_last_values.items():
@ -121,26 +143,34 @@ class SearchDialog(QDialog, Ui_Dialog):
return tok return tok
def box_search_string(self): 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 = [] ans = []
self.box_last_values = {} self.box_last_values = {}
title = unicode(self.title_box.text()).strip() title = unicode(self.title_box.text()).strip()
self.box_last_values['title_box'] = title self.box_last_values['title_box'] = title
if title: if title:
ans.append('title:"' + title + '"') ans.append('title:"' + self.mc + title + '"')
author = unicode(self.authors_box.text()).strip() author = unicode(self.authors_box.text()).strip()
self.box_last_values['authors_box'] = author self.box_last_values['authors_box'] = author
if author: if author:
ans.append('author:"' + author + '"') ans.append('author:"' + self.mc + author + '"')
series = unicode(self.series_box.text()).strip() series = unicode(self.series_box.text()).strip()
self.box_last_values['series_box'] = series self.box_last_values['series_box'] = series
if series: if series:
ans.append('series:"' + series + '"') ans.append('series:"' + self.mc + series + '"')
self.mc = '='
tags = unicode(self.tags_box.text()) tags = unicode(self.tags_box.text())
self.box_last_values['tags_box'] = tags self.box_last_values['tags_box'] = tags
tags = self.tokens(tags) tags = [t.strip() for t in tags.split(',') if t.strip()]
if tags: if tags:
tags = ['tags:' + t for t in tags] tags = ['tags:"=' + t + '"' for t in tags]
ans.append('(' + ' or '.join(tags) + ')') ans.append('(' + ' or '.join(tags) + ')')
general = unicode(self.general_box.text()) general = unicode(self.general_box.text())
self.box_last_values['general_box'] = general self.box_last_values['general_box'] = general

View File

@ -21,7 +21,7 @@
<item row="0" column="0"> <item row="0" column="0">
<widget class="QLabel" name="label_5"> <widget class="QLabel" name="label_5">
<property name="text"> <property name="text">
<string>What kind of match to use:</string> <string>&amp;What kind of match to use:</string>
</property> </property>
<property name="buddy"> <property name="buddy">
<cstring>matchkind</cstring> <cstring>matchkind</cstring>
@ -228,7 +228,7 @@
</widget> </widget>
</item> </item>
<item row="1" column="1"> <item row="1" column="1">
<widget class="QLineEdit" name="title_box"> <widget class="EnLineEdit" name="title_box">
<property name="toolTip"> <property name="toolTip">
<string>Enter the title.</string> <string>Enter the title.</string>
</property> </property>
@ -265,21 +265,21 @@
</widget> </widget>
</item> </item>
<item row="2" column="1"> <item row="2" column="1">
<widget class="QLineEdit" name="authors_box"> <widget class="EnComboBox" name="authors_box">
<property name="toolTip"> <property name="toolTip">
<string>Enter an author's name. Only one author can be used.</string> <string>Enter an author's name. Only one author can be used.</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="1"> <item row="3" column="1">
<widget class="QLineEdit" name="series_box"> <widget class="EnComboBox" name="series_box">
<property name="toolTip"> <property name="toolTip">
<string>Enter a series name, without an index. Only one series name can be used.</string> <string>Enter a series name, without an index. Only one series name can be used.</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="4" column="1"> <item row="4" column="1">
<widget class="QLineEdit" name="tags_box"> <widget class="TagsLineEdit" name="tags_box">
<property name="toolTip"> <property name="toolTip">
<string>Enter tags separated by spaces</string> <string>Enter tags separated by spaces</string>
</property> </property>
@ -348,6 +348,23 @@
</item> </item>
</layout> </layout>
</widget> </widget>
<customwidgets>
<customwidget>
<class>EnLineEdit</class>
<extends>QLineEdit</extends>
<header>widgets.h</header>
</customwidget>
<customwidget>
<class>EnComboBox</class>
<extends>QComboBox</extends>
<header>widgets.h</header>
</customwidget>
<customwidget>
<class>TagsLineEdit</class>
<extends>QLineEdit</extends>
<header>widgets.h</header>
</customwidget>
</customwidgets>
<tabstops> <tabstops>
<tabstop>all</tabstop> <tabstop>all</tabstop>
<tabstop>phrase</tabstop> <tabstop>phrase</tabstop>

View File

@ -57,7 +57,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
(_('Never'), 'never')] (_('Never'), 'never')]
r('toolbar_text', gprefs, choices=choices) r('toolbar_text', gprefs, choices=choices)
choices = [(_('Disabled'), 'disabled'), (_('By first letter'), 'first letter'), choices = [(_('Disabled'), 'disable'), (_('By first letter'), 'first letter'),
(_('Partitioned'), 'partition')] (_('Partitioned'), 'partition')]
r('tags_browser_partition_method', gprefs, choices=choices) r('tags_browser_partition_method', gprefs, choices=choices)
r('tags_browser_collapse_at', gprefs) r('tags_browser_collapse_at', gprefs)

View File

@ -8,13 +8,12 @@ __docformat__ = 'restructuredtext en'
import textwrap, os import textwrap, os
from PyQt4.Qt import Qt, QModelIndex, QAbstractItemModel, QVariant, QIcon, \ from PyQt4.Qt import Qt, QModelIndex, QAbstractItemModel, QVariant, QIcon, \
QBrush, QDialog, QDialogButtonBox, QVBoxLayout, QLabel, QLineEdit QBrush
from calibre.gui2.preferences import ConfigWidgetBase, test_widget from calibre.gui2.preferences import ConfigWidgetBase, test_widget
from calibre.gui2.preferences.plugins_ui import Ui_Form from calibre.gui2.preferences.plugins_ui import Ui_Form
from calibre.customize.ui import initialized_plugins, is_disabled, enable_plugin, \ from calibre.customize.ui import initialized_plugins, is_disabled, enable_plugin, \
disable_plugin, customize_plugin, \ disable_plugin, plugin_customization, add_plugin, \
plugin_customization, add_plugin, \
remove_plugin remove_plugin
from calibre.gui2 import NONE, error_dialog, info_dialog, choose_files from calibre.gui2 import NONE, error_dialog, info_dialog, choose_files
@ -129,6 +128,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
self.plugin_view.setModel(self._plugin_model) self.plugin_view.setModel(self._plugin_model)
self.plugin_view.setStyleSheet( self.plugin_view.setStyleSheet(
"QTreeView::item { padding-bottom: 10px;}") "QTreeView::item { padding-bottom: 10px;}")
self.plugin_view.doubleClicked.connect(self.double_clicked)
self.toggle_plugin_button.clicked.connect(self.toggle_plugin) self.toggle_plugin_button.clicked.connect(self.toggle_plugin)
self.customize_plugin_button.clicked.connect(self.customize_plugin) self.customize_plugin_button.clicked.connect(self.customize_plugin)
self.remove_plugin_button.clicked.connect(self.remove_plugin) self.remove_plugin_button.clicked.connect(self.remove_plugin)
@ -138,6 +138,10 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
def toggle_plugin(self, *args): def toggle_plugin(self, *args):
self.modify_plugin(op='toggle') self.modify_plugin(op='toggle')
def double_clicked(self, index):
if index.parent().isValid():
self.modify_plugin(op='customize')
def customize_plugin(self, *args): def customize_plugin(self, *args):
self.modify_plugin(op='customize') self.modify_plugin(op='customize')
@ -184,49 +188,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
_('Plugin: %s does not need customization')%plugin.name).exec_() _('Plugin: %s does not need customization')%plugin.name).exec_()
return return
self.changed_signal.emit() self.changed_signal.emit()
if plugin.do_user_config():
config_dialog = QDialog(self)
button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
v = QVBoxLayout(config_dialog)
button_box.accepted.connect(config_dialog.accept)
button_box.rejected.connect(config_dialog.reject)
config_dialog.setWindowTitle(_('Customize') + ' ' + plugin.name)
if hasattr(plugin, 'config_widget'):
config_widget = plugin.config_widget()
v.addWidget(config_widget)
v.addWidget(button_box)
config_dialog.exec_()
if config_dialog.result() == QDialog.Accepted:
if hasattr(config_widget, 'validate'):
if config_widget.validate():
plugin.save_settings(config_widget)
else:
plugin.save_settings(config_widget)
self._plugin_model.refresh_plugin(plugin)
else:
help_text = plugin.customization_help(gui=True)
help_text = QLabel(help_text, config_dialog)
help_text.setWordWrap(True)
help_text.setTextInteractionFlags(Qt.LinksAccessibleByMouse
| Qt.LinksAccessibleByKeyboard)
help_text.setOpenExternalLinks(True)
v.addWidget(help_text)
sc = plugin_customization(plugin)
if not sc:
sc = ''
sc = sc.strip()
sc = QLineEdit(sc, config_dialog)
v.addWidget(sc)
v.addWidget(button_box)
config_dialog.exec_()
if config_dialog.result() == QDialog.Accepted:
sc = unicode(sc.text()).strip()
customize_plugin(plugin, sc)
self._plugin_model.refresh_plugin(plugin) self._plugin_model.refresh_plugin(plugin)
elif op == 'remove': elif op == 'remove':
if remove_plugin(plugin): if remove_plugin(plugin):

View File

@ -616,6 +616,31 @@ class ComboBoxWithHelp(QComboBox):
QComboBox.hidePopup(self) QComboBox.hidePopup(self)
self.set_state() self.set_state()
class EncodingComboBox(QComboBox):
'''
A combobox that holds text encodings support
by Python. This is only populated with the most
common and standard encodings. There is no good
way to programatically list all supported encodings
using encodings.aliases.aliases.keys(). It
will not work.
'''
ENCODINGS = ['', 'cp1252', 'latin1', 'utf-8', '', 'ascii', 'big5', 'cp1250', 'cp1251', 'cp1253',
'cp1254', 'cp1255', 'cp1256', 'euc_jp', 'euc_kr', 'gb2312', 'gb18030',
'hz', 'iso2022_jp', 'iso2022_kr', 'iso8859_5', 'shift_jis',
]
def __init__(self, parent=None):
QComboBox.__init__(self, parent)
self.setEditable(True)
self.setLineEdit(EnLineEdit(self))
for item in self.ENCODINGS:
self.addItem(item)
class PythonHighlighter(QSyntaxHighlighter): class PythonHighlighter(QSyntaxHighlighter):
Rules = [] Rules = []

View File

@ -132,6 +132,38 @@ def _match(query, value, matchkind):
pass pass
return False return False
class CacheRow(list):
def __init__(self, db, composites, val):
self.db = db
self._composites = composites
list.__init__(self, val)
self._must_do = len(composites) > 0
def __getitem__(self, col):
if self._must_do:
is_comp = False
if isinstance(col, slice):
start = 0 if col.start is None else col.start
step = 1 if col.stop is None else col.stop
for c in range(start, col.stop, step):
if c in self._composites:
is_comp = True
break
elif col in self._composites:
is_comp = True
if is_comp:
id = list.__getitem__(self, 0)
self._must_do = False
mi = self.db.get_metadata(id, index_is_id=True)
for c in self._composites:
self[c] = mi.get(self._composites[c])
return list.__getitem__(self, col)
def __getslice__(self, i, j):
return self.__getitem__(slice(i, j))
class ResultCache(SearchQueryParser): # {{{ class ResultCache(SearchQueryParser): # {{{
''' '''
@ -139,7 +171,12 @@ class ResultCache(SearchQueryParser): # {{{
''' '''
def __init__(self, FIELD_MAP, field_metadata): def __init__(self, FIELD_MAP, field_metadata):
self.FIELD_MAP = FIELD_MAP self.FIELD_MAP = FIELD_MAP
self._map = self._data = self._map_filtered = [] self.composites = {}
for key in field_metadata:
if field_metadata[key]['datatype'] == 'composite':
self.composites[field_metadata[key]['rec_index']] = key
self._data = []
self._map = self._map_filtered = []
self.first_sort = True self.first_sort = True
self.search_restriction = '' self.search_restriction = ''
self.field_metadata = field_metadata self.field_metadata = field_metadata
@ -148,10 +185,6 @@ class ResultCache(SearchQueryParser): # {{{
self.build_date_relop_dict() self.build_date_relop_dict()
self.build_numeric_relop_dict() self.build_numeric_relop_dict()
self.composites = []
for key in field_metadata:
if field_metadata[key]['datatype'] == 'composite':
self.composites.append((key, field_metadata[key]['rec_index']))
def __getitem__(self, row): def __getitem__(self, row):
return self._data[self._map_filtered[row]] return self._data[self._map_filtered[row]]
@ -583,13 +616,10 @@ class ResultCache(SearchQueryParser): # {{{
''' '''
for id in ids: for id in ids:
try: try:
self._data[id] = db.conn.get('SELECT * from meta2 WHERE id=?', (id,))[0] self._data[id] = CacheRow(db, self.composites,
db.conn.get('SELECT * from meta2 WHERE id=?', (id,))[0])
self._data[id].append(db.book_on_device_string(id)) self._data[id].append(db.book_on_device_string(id))
self._data[id].append(None) self._data[id].append(None)
if len(self.composites) > 0:
mi = db.get_metadata(id, index_is_id=True)
for k,c in self.composites:
self._data[id][c] = mi.get(k, None)
except IndexError: except IndexError:
return None return None
try: try:
@ -603,13 +633,10 @@ class ResultCache(SearchQueryParser): # {{{
return return
self._data.extend(repeat(None, max(ids)-len(self._data)+2)) self._data.extend(repeat(None, max(ids)-len(self._data)+2))
for id in ids: for id in ids:
self._data[id] = db.conn.get('SELECT * from meta2 WHERE id=?', (id,))[0] self._data[id] = CacheRow(db, self.composites,
db.conn.get('SELECT * from meta2 WHERE id=?', (id,))[0])
self._data[id].append(db.book_on_device_string(id)) self._data[id].append(db.book_on_device_string(id))
self._data[id].append(None) self._data[id].append(None)
if len(self.composites) > 0:
mi = db.get_metadata(id, index_is_id=True)
for k,c in self.composites:
self._data[id][c] = mi.get(k)
self._map[0:0] = ids self._map[0:0] = ids
self._map_filtered[0:0] = ids self._map_filtered[0:0] = ids
@ -630,16 +657,11 @@ class ResultCache(SearchQueryParser): # {{{
temp = db.conn.get('SELECT * FROM meta2') temp = db.conn.get('SELECT * FROM meta2')
self._data = list(itertools.repeat(None, temp[-1][0]+2)) if temp else [] self._data = list(itertools.repeat(None, temp[-1][0]+2)) if temp else []
for r in temp: for r in temp:
self._data[r[0]] = r self._data[r[0]] = CacheRow(db, self.composites, r)
for item in self._data: for item in self._data:
if item is not None: if item is not None:
item.append(db.book_on_device_string(item[0])) item.append(db.book_on_device_string(item[0]))
item.append(None) item.append(None)
if len(self.composites) > 0:
mi = db.get_metadata(item[0], index_is_id=True)
for k,c in self.composites:
item[c] = mi.get(k)
self._map = [i[0] for i in self._data if i is not None] self._map = [i[0] for i in self._data if i is not None]
if field is not None: if field is not None:
self.sort(field, ascending) self.sort(field, ascending)
@ -669,12 +691,6 @@ class ResultCache(SearchQueryParser): # {{{
fields = [('timestamp', False)] fields = [('timestamp', False)]
keyg = SortKeyGenerator(fields, self.field_metadata, self._data) keyg = SortKeyGenerator(fields, self.field_metadata, self._data)
# For efficiency, the key generator returns a plain value if only one
# field is in the sort field list. Because the normal cmp function will
# always assume asc, we must deal with asc/desc here.
if len(fields) == 1:
self._map.sort(key=keyg, reverse=not fields[0][1])
else:
self._map.sort(key=keyg) self._map.sort(key=keyg)
tmap = list(itertools.repeat(False, len(self._data))) tmap = list(itertools.repeat(False, len(self._data)))
@ -708,8 +724,6 @@ class SortKeyGenerator(object):
def __call__(self, record): def __call__(self, record):
values = tuple(self.itervals(self.data[record])) values = tuple(self.itervals(self.data[record]))
if len(values) == 1:
return values[0]
return SortKey(self.orders, values) return SortKey(self.orders, values)
def itervals(self, record): def itervals(self, record):
@ -732,6 +746,11 @@ class SortKeyGenerator(object):
val = (self.string_sort_key(val), sidx) val = (self.string_sort_key(val), sidx)
elif dt in ('text', 'comments', 'composite', 'enumeration'): elif dt in ('text', 'comments', 'composite', 'enumeration'):
if val:
sep = fm['is_multiple']
if sep:
val = sep.join(sorted(val.split(sep),
key=self.string_sort_key))
val = self.string_sort_key(val) val = self.string_sort_key(val)
elif dt == 'bool': elif dt == 'bool':

View File

@ -15,7 +15,7 @@ from calibre.customize import CatalogPlugin
from calibre.customize.conversion import OptionRecommendation, DummyReporter from calibre.customize.conversion import OptionRecommendation, DummyReporter
from calibre.ebooks.BeautifulSoup import BeautifulSoup, BeautifulStoneSoup, Tag, NavigableString from calibre.ebooks.BeautifulSoup import BeautifulSoup, BeautifulStoneSoup, Tag, NavigableString
from calibre.ebooks.chardet import substitute_entites from calibre.ebooks.chardet import substitute_entites
from calibre.ebooks.oeb.base import RECOVER_PARSER, XHTML_NS from calibre.ebooks.oeb.base import XHTML_NS
from calibre.ptempfile import PersistentTemporaryDirectory from calibre.ptempfile import PersistentTemporaryDirectory
from calibre.utils.config import config_dir from calibre.utils.config import config_dir
from calibre.utils.date import format_date, isoformat, now as nowf from calibre.utils.date import format_date, isoformat, now as nowf

View File

@ -133,6 +133,14 @@ class CustomColumns(object):
def adapt_bool(x, d): def adapt_bool(x, d):
if isinstance(x, (str, unicode, bytes)): if isinstance(x, (str, unicode, bytes)):
x = x.lower()
if x == 'true':
x = True
elif x == 'false':
x = False
elif x == 'none':
x = None
else:
x = bool(int(x)) x = bool(int(x))
return x return x
@ -142,9 +150,17 @@ class CustomColumns(object):
v = None v = None
return v return v
def adapt_number(x, d):
if isinstance(x, (str, unicode, bytes)):
if x.lower() == 'none':
return None
if d['datatype'] == 'int':
return int(x)
return float(x)
self.custom_data_adapters = { self.custom_data_adapters = {
'float': lambda x,d : x if x is None else float(x), 'float': adapt_number,
'int': lambda x,d : x if x is None else int(x), 'int': adapt_number,
'rating':lambda x,d : x if x is None else min(10., max(0., float(x))), 'rating':lambda x,d : x if x is None else min(10., max(0., float(x))),
'bool': adapt_bool, 'bool': adapt_bool,
'comments': lambda x,d: adapt_text(x, {'is_multiple':False}), 'comments': lambda x,d: adapt_text(x, {'is_multiple':False}),

View File

@ -131,6 +131,8 @@ class SafeFormat(TemplateFormatter):
return self.composite_values[key] return self.composite_values[key]
if key in kwargs: if key in kwargs:
val = kwargs[key] val = kwargs[key]
if isinstance(val, list):
val = ','.join(val)
return val.replace('/', '_').replace('\\', '_') return val.replace('/', '_').replace('\\', '_')
return '' return ''
except: except:

View File

@ -101,7 +101,19 @@ def html_to_lxml(raw):
root = html.fragment_fromstring(raw) root = html.fragment_fromstring(raw)
root.set('xmlns', "http://www.w3.org/1999/xhtml") root.set('xmlns', "http://www.w3.org/1999/xhtml")
raw = etree.tostring(root, encoding=None) raw = etree.tostring(root, encoding=None)
try:
return etree.fromstring(raw) return etree.fromstring(raw)
except:
for x in root.iterdescendants():
remove = []
for attr in x.attrib:
if ':' in attr:
remove.append(attr)
for a in remove:
del x.attrib[a]
raw = etree.tostring(root, encoding=None)
return etree.fromstring(raw)
def CATALOG_ENTRY(item, item_kind, base_href, version, updated, def CATALOG_ENTRY(item, item_kind, base_href, version, updated,
ignore_count=False, add_kind=False): ignore_count=False, add_kind=False):

View File

@ -150,7 +150,7 @@ The example shows several things:
* program mode is used if the expression begins with ``:'`` and ends with ``'``. Anything else is assumed to be single-function. * program mode is used if the expression begins with ``:'`` and ends with ``'``. Anything else is assumed to be single-function.
* the variable ``$`` stands for the field the expression is operating upon, ``#series`` in this case. * the variable ``$`` stands for the field the expression is operating upon, ``#series`` in this case.
* functions must be given all their arguments. There is no default value. This is true for the standard builtin functions, and is a significant difference from single-function mode. * functions must be given all their arguments. There is no default value. For example, the standard builtin functions must be given an additional initial parameter indicating the source field, which is a significant difference from single-function mode.
* white space is ignored and can be used anywhere within the expression. * white space is ignored and can be used anywhere within the expression.
* constant strings are enclosed in matching quotes, either ``'`` or ``"``. * constant strings are enclosed in matching quotes, either ``'`` or ``"``.
@ -204,7 +204,7 @@ For various values of series_index, the program returns:
All the functions listed under single-function mode can be used in program mode, noting that unlike the functions described below you must supply a first parameter providing the value the function is to act upon. All the functions listed under single-function mode can be used in program mode, noting that unlike the functions described below you must supply a first parameter providing the value the function is to act upon.
The following functions are available in addition to those described in single-function mode. 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):
* ``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

View File

@ -431,6 +431,9 @@ class TemplateFormatter(string.Formatter):
return prefix + val + suffix return prefix + val + suffix
def vformat(self, fmt, args, kwargs): def vformat(self, fmt, args, kwargs):
if fmt.startswith('program:'):
ans = self._eval_program(None, fmt[8:])
else:
ans = string.Formatter.vformat(self, fmt, args, kwargs) ans = string.Formatter.vformat(self, fmt, args, kwargs)
return self.compress_spaces.sub(' ', ans).strip() return self.compress_spaces.sub(' ', ans).strip()
@ -441,9 +444,6 @@ class TemplateFormatter(string.Formatter):
self.book = book self.book = book
self.composite_values = {} self.composite_values = {}
try: try:
if fmt.startswith('program:'):
ans = self._eval_program(None, fmt[8:])
else:
ans = self.vformat(fmt, [], kwargs).strip() ans = self.vformat(fmt, [], kwargs).strip()
except Exception, e: except Exception, e:
if DEBUG: if DEBUG:
@ -468,6 +468,7 @@ class EvalFormatter(TemplateFormatter):
A template formatter that uses a simple dict instead of an mi instance A template formatter that uses a simple dict instead of an mi instance
''' '''
def get_value(self, key, args, kwargs): def get_value(self, key, args, kwargs):
key = key.lower()
return kwargs.get(key, _('No such variable ') + key) return kwargs.get(key, _('No such variable ') + key)
eval_formatter = EvalFormatter() eval_formatter = EvalFormatter()

View File

@ -106,13 +106,16 @@ class Image(_magick.Image): # {{{
return ans return ans
def load(self, data): def load(self, data):
return _magick.Image.load(self, bytes(data)) data = bytes(data)
if not data:
raise ValueError('Cannot open image from empty data string')
return _magick.Image.load(self, data)
def open(self, path_or_file): def open(self, path_or_file):
if not hasattr(path_or_file, 'read') and \ if not hasattr(path_or_file, 'read') and \
path_or_file.lower().endswith('.wmf'): path_or_file.lower().endswith('.wmf'):
# Special handling for WMF files as ImageMagick seems # Special handling for WMF files as ImageMagick seems
# to hand while reading them from a blob on linux # to hang while reading them from a blob on linux
if isinstance(path_or_file, unicode): if isinstance(path_or_file, unicode):
path_or_file = path_or_file.encode(filesystem_encoding) path_or_file = path_or_file.encode(filesystem_encoding)
return _magick.Image.read(self, path_or_file) return _magick.Image.read(self, path_or_file)
@ -121,6 +124,8 @@ class Image(_magick.Image): # {{{
data = data.read() data = data.read()
else: else:
data = open(data, 'rb').read() data = open(data, 'rb').read()
if not data:
raise ValueError('%r is an empty file'%path_or_file)
self.load(data) self.load(data)
@dynamic_property @dynamic_property

View File

@ -0,0 +1,9 @@
#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
__license__ = 'GPL v3'
__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'

200
src/calibre/utils/wmf/wmf.c Normal file
View File

@ -0,0 +1,200 @@
#define UNICODE
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <libwmf/api.h>
#include <libwmf/svg.h>
typedef struct {
char *data;
size_t len;
size_t pos;
} buf;
//This code is taken mostly from the Abiword wmf plugin
// returns unsigned char cast to int, or EOF
static int wmf_WMF_read(void * context) {
char c;
buf *info = (buf*)context;
if (info->pos == info->len)
return EOF;
c = info->data[pos];
info->pos++;
return (int)c;
}
// returns (-1) on error, else 0
static int wmf_WMF_seek(void * context, long pos) {
buf* info = (buf*) context;
if (pos < 0 || (size_t)pos > info->len) return -1;
info->pos = (size_t)pos;
return 0;
}
// returns (-1) on error, else pos
static long wmf_WMF_tell(void * context) {
buf* info = (buf*) context;
return (long) info->pos;
}
#define CLEANUP if(API) { if (stream) wmf_free(API, stream); wmf_api_destroy(API); };
static PyObject *
wmf_render(PyObject *self, PyObject *args) {
char *data;
Py_ssize_t sz;
PyObject *ans;
unsigned int disp_width = 0;
unsigned int disp_height = 0;
float wmf_width;
float wmf_height;
float ratio_wmf;
float ratio_bounds;
unsigned long flags;
unsigned int max_width = 1600;
unsigned int max_height = 1200;
unsigned long max_flags = 0;
static const char* Default_Description = "wmf2svg";
wmf_error_t err;
wmf_svg_t* ddata = 0;
wmfAPI* API = 0;
wmfD_Rect bbox;
wmfAPI_Options api_options;
buf read_info;
char *stream = NULL;
unsigned long stream_len = 0;
if (!PyArg_ParseTuple(args, "s#", &data, &sz))
return NULL;
flags = WMF_OPT_IGNORE_NONFATAL | WMF_OPT_FUNCTION;
api_options.function = wmf_svg_function;
err = wmf_api_create(&API, flags, &api_options);
if (err != wmf_E_None) {
CLEANUP;
return PyErr_NoMemory();
}
read_info.data = data;
read_info.len = sz;
read_info.pos = 0;
err = wmf_bbuf_input(API, wmf_WMF_read, wmf_WMF_seek, wmf_WMF_tell, (void *) &read_info);
if (err != wmf_E_None) {
CLEANUP;
PyErr_SetString(PyExc_Exception, "Failed to initialize WMF input");
return NULL;
}
err = wmf_scan(API, 0, &(bbox));
if (err != wmf_E_None)
{
CLEANUP;
PyErr_SetString(PyExc_ValueError, "Failed to scan the WMF");
return NULL;
}
/* Okay, got this far, everything seems cool.
*/
ddata = WMF_SVG_GetData (API);
ddata->out = wmf_stream_create(API, NULL);
ddata->Description = (char *)Default_Description;
ddata->bbox = bbox;
wmf_display_size(API, &disp_width, &disp_height, 96, 96);
wmf_width = (float) disp_width;
wmf_height = (float) disp_height;
if ((wmf_width <= 0) || (wmf_height <= 0)) {
CLEANUP;
PyErr_SetString(PyExc_ValueError, "Bad WMF image size");
return NULL;
}
if ((wmf_width > (float) max_width )
|| (wmf_height > (float) max_height)) {
ratio_wmf = wmf_height / wmf_width;
ratio_bounds = (float) max_height / (float) max_width;
if (ratio_wmf > ratio_bounds) {
ddata->height = max_height;
ddata->width = (unsigned int) ((float) ddata->height / ratio_wmf);
}
else {
ddata->width = max_width;
ddata->height = (unsigned int) ((float) ddata->width * ratio_wmf);
}
}
else {
ddata->width = (unsigned int) ceil ((double) wmf_width );
ddata->height = (unsigned int) ceil ((double) wmf_height);
}
ddata->flags |= WMF_SVG_INLINE_IMAGES;
ddata->flags |= WMF_GD_OUTPUT_MEMORY | WMF_GD_OWN_BUFFER;
err = wmf_play(API, 0, &(bbox));
if (err != wmf_E_None) {
CLEANUP;
PyErr_SetString(PyExc_ValueError, "Playing of the WMF file failed");
return NULL;
}
wmf_stream_destroy(API, ddata->out, &stream, &stream_len);
ans = Py_BuildValue("s#", stream, stream_len);
wmf_free(API, stream);
wmf_api_destroy (API);
return ans;
}
static PyMethodDef wmf_methods[] = {
{"render", wmf_render, METH_VARARGS,
"render(path) -> Render wmf as svg."
},
{NULL} /* Sentinel */
};
PyMODINIT_FUNC
initwmf(void)
{
PyObject* m;
m = Py_InitModule3("wmf", wmf_methods,
"Wrapper for the libwmf library");
}