Merge from trunk

This commit is contained in:
Charles Haley 2010-08-11 21:48:35 +01:00
commit ee685cdac8
29 changed files with 3507 additions and 2931 deletions

View File

@ -6,7 +6,7 @@ __docformat__ = 'restructuredtext en'
'''
Contains various tweaks that affect calibre behavior. Only edit this file if
you know what you are dong. If you delete this file, it will be recreated from
you know what you are doing. If you delete this file, it will be recreated from
defaults.
'''

Binary file not shown.

After

Width:  |  Height:  |  Size: 356 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 464 B

View File

@ -0,0 +1,64 @@
__license__ = 'GPL v3'
__copyright__ = '2010, Darko Miletic <darko.miletic at gmail.com>'
'''
www.la-razon.com
'''
from calibre import strftime
from calibre.web.feeds.news import BasicNewsRecipe
class LaRazon_Bol(BasicNewsRecipe):
title = 'La Razón - Bolivia'
__author__ = 'Darko Miletic'
description = 'El diario nacional de Bolivia'
publisher = 'Praxsis S.R.L.'
category = 'news, politics, Bolivia'
oldest_article = 1
max_articles_per_feed = 200
no_stylesheets = True
encoding = 'cp1252'
use_embedded_content = False
language = 'es'
publication_type = 'newspaper'
delay = 1
remove_empty_feeds = True
cover_url = strftime('http://www.la-razon.com/portadas/%Y%m%d_LaRazon.jpg')
masthead_url = 'http://www.la-razon.com/imagenes/logo.jpg'
extra_css = """ body{font-family: Arial,Helvetica,sans-serif }
img{margin-bottom: 0.4em}
.noticia-titulo{font-family: Georgia,"Times New Roman",Times,serif}
.lead{font-weight: bold; font-size: 0.8em}
"""
conversion_options = {
'comment' : description
, 'tags' : category
, 'publisher' : publisher
, 'language' : language
}
keep_only_tags = [dict(name='div', attrs={'class':['noticia-titulo','noticia-desarrollo']})]
remove_tags = [dict(name=['meta','link','form','iframe','embed','object'])]
remove_attributes = ['width','height']
feeds = [
(u'Editorial' , u'http://www.la-razon.com/rss_editorial.php' )
,(u'Opinión' , u'http://www.la-razon.com/rss_opinion.php' )
,(u'Nacional' , u'http://www.la-razon.com/rss_nacional.php' )
,(u'Economia' , u'http://www.la-razon.com/rss_economia.php' )
,(u'Ciudades' , u'http://www.la-razon.com/rss_ciudades.php' )
,(u'Sociedad' , u'http://www.la-razon.com/rss_sociedad.php' )
,(u'Mundo' , u'http://www.la-razon.com/rss_sociedad.php' )
,(u'La Revista' , u'http://www.la-razon.com/rss_larevista.php' )
,(u'Sociales' , u'http://www.la-razon.com/rss_sociales.php' )
,(u'Mia' , u'http://www.la-razon.com/rss_mia.php' )
,(u'Marcas' , u'http://www.la-razon.com/rss_marcas.php' )
,(u'Escape' , u'http://www.la-razon.com/rss_escape.php' )
,(u'El Financiero' , u'http://www.la-razon.com/rss_financiero.php')
,(u'Tendencias' , u'http://www.la-razon.com/rss_tendencias.php')
]
def preprocess_html(self, soup):
for item in soup.findAll(style=True):
del item['style']
return soup

View File

@ -0,0 +1,63 @@
__license__ = 'GPL v3'
__copyright__ = '2010, Darko Miletic <darko.miletic at gmail.com>'
'''
www.lostiempos.com
'''
from calibre import strftime
from calibre.web.feeds.news import BasicNewsRecipe
class LosTiempos_Bol(BasicNewsRecipe):
title = 'Los Tiempos - Bolivia'
__author__ = 'Darko Miletic'
description = 'El periódico de mayor circulación en la ciudad de Cochabamba, Bolivia'
publisher = 'Los Tiempos'
category = 'news, politics, Bolivia'
oldest_article = 1
max_articles_per_feed = 200
no_stylesheets = True
encoding = 'cp1252'
use_embedded_content = False
language = 'es'
publication_type = 'newspaper'
delay = 1
remove_empty_feeds = True
cover_url = strftime('http://www.lostiempos.com/media_recortes/%Y/%m/%d/portada_md_1.jpg')
masthead_url = 'http://www.lostiempos.com/img_stat/logo_tiempos_sin_beta.jpg'
extra_css = """ body{font-family: Arial,Helvetica,sans-serif }
img{margin-bottom: 0.4em}
h1,.hora,.breadcum,.pie_foto{font-family: Georgia,"Times New Roman",Times,serif}
.hora,.breadcum,.pie_foto{font-size: small}
.en_gris,.pie_foto{color: #666666}
"""
conversion_options = {
'comment' : description
, 'tags' : category
, 'publisher' : publisher
, 'language' : language
}
keep_only_tags = [dict(name='div', attrs={'id':'articulo'})]
remove_tags = [
dict(name=['meta','link','form','iframe','embed','object','hr'])
,dict(attrs={'class':['caja_fonts sin_border_bot','pub']})
]
remove_attributes = ['width','height']
feeds = [
(u'Nacional' , u'http://www.lostiempos.com/rss/lostiempos-nacional.xml' )
,(u'Local' , u'http://www.lostiempos.com/rss/lostiempos-local.xml' )
,(u'Deportes' , u'http://www.lostiempos.com/rss/lostiempos-deportes.xml' )
,(u'Economía' , u'http://www.lostiempos.com/rss/lostiempos-economia.xml' )
,(u'Internacional' , u'http://www.lostiempos.com/rss/lostiempos-internacional.xml' )
,(u'Vida y Futuro' , u'http://www.lostiempos.com/rss/lostiempos-vida-y-futuro.xml' )
,(u'Tragaluz' , u'http://www.lostiempos.com/rss/lostiempos-tragaluz.xml' )
,(u'Opiniones' , u'http://www.lostiempos.com/rss/lostiempos-opiniones.xml' )
]
def preprocess_html(self, soup):
for item in soup.findAll(style=True):
del item['style']
return soup

View File

@ -6,7 +6,6 @@ nspm.rs
import re
from calibre.web.feeds.news import BasicNewsRecipe
from calibre.ebooks.BeautifulSoup import Tag
class Nspm(BasicNewsRecipe):
title = 'Nova srpska politicka misao'
@ -22,14 +21,14 @@ class Nspm(BasicNewsRecipe):
encoding = 'utf-8'
language = 'sr'
delay = 2
publication_type = 'magazine'
publication_type = 'magazine'
masthead_url = 'http://www.nspm.rs/templates/jsn_epic_pro/images/logol.jpg'
extra_css = """ @font-face {font-family: "serif1";src:url(res:///opt/sony/ebook/FONT/tt0011m_.ttf)}
@font-face {font-family: "sans1";src:url(res:///opt/sony/ebook/FONT/tt0003m_.ttf)}
body{font-family: "Times New Roman", serif1, serif}
.article_description{font-family: Arial, sans1, sans-serif}
img{margin-top:0.5em; margin-bottom: 0.7em}
.author{color: #990000; font-weight: bold}
extra_css = """ @font-face {font-family: "serif1";src:url(res:///opt/sony/ebook/FONT/tt0011m_.ttf)}
@font-face {font-family: "sans1";src:url(res:///opt/sony/ebook/FONT/tt0003m_.ttf)}
body{font-family: "Times New Roman", serif1, serif}
.article_description{font-family: Arial, sans1, sans-serif}
img{margin-top:0.5em; margin-bottom: 0.7em}
.author{color: #990000; font-weight: bold}
.author,.createdate{font-size: 0.9em} """
conversion_options = {
@ -68,4 +67,4 @@ class Nspm(BasicNewsRecipe):
def preprocess_html(self, soup):
for item in soup.body.findAll(style=True):
del item['style']
return self.adeify_images(soup)
return self.adeify_images(soup)

View File

@ -0,0 +1,34 @@
from calibre.web.feeds.news import BasicNewsRecipe
import re
class YahooNews(BasicNewsRecipe):
title = 'Yahoo News'
__author__ = 'Starson17'
description = 'Yahoo-Science'
language = 'en'
use_embedded_content= False
no_stylesheets = True
linearize_tables = True
oldest_article = 24
remove_javascript = True
remove_empty_feeds = True
max_articles_per_feed = 10
feeds = [#There are dozens of other feeds at http://news.yahoo.com/rss
(u'Top Stories', u'http://rss.news.yahoo.com/rss/topstories'),
(u'Science', u'http://rss.news.yahoo.com/rss/science')
]
keep_only_tags = [dict(name='div', attrs={'id':'yn-story'})]
remove_tags = [dict(name='div', attrs={'class':['hd', 'ft', 'yn-share-social']}),
dict(name='div', attrs={'id':['yn-story-minor-media']})]
preprocess_regexps = [(re.compile(r'<span>Play Video</span>', re.DOTALL),lambda match: '<span></span>')]
extra_css = '''
h1{font-family:Arial,Helvetica,sans-serif; font-weight:bold;font-size:large;}
h2{font-family:Arial,Helvetica,sans-serif; font-weight:normal;font-size:small;}
p{font-family:Arial,Helvetica,sans-serif;font-size:small;}
body{font-family:Helvetica,Arial,sans-serif;font-size:small;}
'''

View File

@ -19,8 +19,8 @@ class ANDROID(USBMS):
VENDOR_ID = {
# HTC
0x0bb4 : { 0x0c02 : [0x100, 0x227], 0x0c01 : [0x100, 0x227], 0x0ff9
: [0x0100, 0x227]},
0x0bb4 : { 0x0c02 : [0x100, 0x0227], 0x0c01 : [0x100, 0x0227], 0x0ff9
: [0x0100, 0x0227, 0x0226]},
# Motorola
0x22b8 : { 0x41d9 : [0x216], 0x2d67 : [0x100], 0x41db : [0x216],

View File

@ -95,11 +95,11 @@ class ITUNES(DriverBase):
# Product IDs:
# 0x1291 iPod Touch
# 0x1292 iPhone 3G
# 0x1293 iPod Touch 2G
# 0x1299 iPod Touch 3G
# 0x1292 iPhone 3G
# 0x1294 iPhone 3GS
# 0x1297 iPhone 4
# 0x1299 iPod Touch 3G
# 0x129a iPad
VENDOR_ID = [0x05ac]
PRODUCT_ID = [0x1292,0x1293,0x1294,0x1297,0x1299,0x129a]
@ -1029,8 +1029,13 @@ class ITUNES(DriverBase):
sys.stdout.write("\n")
sys.stdout.flush()
# This doesn't seem to work with Device, just Library
if False:
'''
Preferred
Disabled because op_status.Tracks never returns a value after adding file
This would be the preferred approach (as under OSX)
It works in _add_library_book()
'''
if DEBUG:
sys.stdout.write(" waiting for handle to added '%s' ..." % metadata.title)
sys.stdout.flush()
@ -1044,15 +1049,19 @@ class ITUNES(DriverBase):
print
added = op_status.Tracks[0]
else:
# This approach simply scans Library|Books for the book we just added
# Try the calibre metadata first
'''
Hackish
Search Library|Books for the book we just added
PDF file name is added title - need to search for base filename w/o extension
'''
format = fpath.rpartition('.')[2].lower()
base_fn = fpath.rpartition(os.sep)[2]
base_fn = base_fn.rpartition('.')[0]
db_added = self._find_device_book(
{'title': metadata.title,
'author': metadata.authors[0],
'uuid': metadata.uuid,
'format': fpath.rpartition('.')[2].lower()})
{ 'title': base_fn if format == 'pdf' else metadata.title,
'author': metadata.authors[0],
'uuid': metadata.uuid,
'format': format})
return db_added
def _add_library_book(self,file, metadata):
@ -1087,7 +1096,12 @@ class ITUNES(DriverBase):
sys.stdout.write("\n")
sys.stdout.flush()
if False:
if True:
'''
Preferable
Originally disabled because op_status.Tracks never returned a value
after adding file. Seems to be working with iTunes 9.2.1.5 06 Aug 2010
'''
if DEBUG:
sys.stdout.write(" waiting for handle to added '%s' ..." % metadata.title)
sys.stdout.flush()
@ -1100,12 +1114,19 @@ class ITUNES(DriverBase):
print
added = op_status.Tracks[0]
else:
# This approach simply scans Library|Books for the book we just added
'''
Hackish
Search Library|Books for the book we just added
PDF file name is added title - need to search for base filename w/o extension
'''
format = file.rpartition('.')[2].lower()
base_fn = file.rpartition(os.sep)[2]
base_fn = base_fn.rpartition('.')[0]
added = self._find_library_book(
{ 'title': metadata.title,
{ 'title': base_fn if format == 'pdf' else metadata.title,
'author': metadata.author[0],
'uuid': metadata.uuid,
'format': file.rpartition('.')[2].lower()})
'format': format})
return added
def _add_new_copy(self, fpath, metadata):
@ -1820,7 +1841,6 @@ class ITUNES(DriverBase):
except:
if DEBUG:
self.log.error(" error generating thumb for '%s', caching empty marker" % book.Name)
self._dump_hex(data[:32])
thumb_data = None
# Cache the empty cover
zfw.writestr(thumb_path,'None')

View File

@ -9,6 +9,7 @@ from threading import Thread
from calibre import prints
from calibre.utils.config import OptionParser
from calibre.utils.logging import default_log
from calibre.utils.titlecase import titlecase
from calibre.customize import Plugin
from calibre.ebooks.metadata.covers import check_for_cover
@ -384,6 +385,16 @@ def search(title=None, author=None, publisher=None, isbn=None, isbndb_key=None,
if r.pubdate is None:
r.pubdate = pubdate
def fix_case(x):
if x and x.isupper():
x = titlecase(x)
return x
for r in results:
r.title = fix_case(r.title)
if r.authors:
r.authors = list(map(fix_case, r.authors))
return results, [(x.name, x.exception, x.tb) for x in fetchers]
def get_social_metadata(mi, verbose=0):

View File

@ -296,6 +296,17 @@ class AddAction(object): # {{{
self.library_view.model().db.import_book(MetaInformation(None), [])
self.library_view.model().books_added(num)
def add_isbns(self, isbns):
from calibre.ebooks.metadata import MetaInformation
ids = set([])
for x in isbns:
mi = MetaInformation(None)
mi.isbn = x
ids.add(self.library_view.model().db.import_book(mi, []))
self.library_view.model().books_added(len(isbns))
self.do_download_metadata(ids)
def files_dropped(self, paths):
to_device = self.stack.currentIndex() != 0
self._add_books(paths, to_device)
@ -342,6 +353,12 @@ class AddAction(object): # {{{
def add_filesystem_book(self, paths, allow_device=True):
self._add_filesystem_book(paths, allow_device=allow_device)
def add_from_isbn(self, *args):
from calibre.gui2.dialogs.add_from_isbn import AddFromISBN
d = AddFromISBN(self)
if d.exec_() == d.Accepted:
self.add_isbns(d.isbns)
def add_books(self, *args):
'''
Add books from the local filesystem to either the library or the device.
@ -625,6 +642,13 @@ class EditMetadataAction(object): # {{{
return
db = self.library_view.model().db
ids = [db.id(row.row()) for row in rows]
self.do_download_metadata(ids, covers=covers,
set_metadata=set_metadata,
set_social_metadata=set_social_metadata)
def do_download_metadata(self, ids, covers=True, set_metadata=True,
set_social_metadata=None):
db = self.library_view.model().db
if set_social_metadata is None:
get_social_metadata = config['get_social_metadata']
else:
@ -931,7 +955,7 @@ class SaveToDiskAction(object): # {{{
lpath = self.library_view.model().db.library_path.replace('/', os.sep)
if dpath.startswith(lpath):
return error_dialog(self, _('Not allowed'),
_('You are tying to save files into the calibre '
_('You are trying to save files into the calibre '
'library. This can cause corruption of your '
'library. Save to disk is meant to export '
'files from your calibre library elsewhere.'), show=True)

View File

@ -63,7 +63,7 @@
<item row="2" column="0" colspan="2">
<widget class="QCheckBox" name="opt_force_max_line_length">
<property name="text">
<string>Force maximum line lenght</string>
<string>Force maximum line length</string>
</property>
</widget>
</item>

View File

@ -0,0 +1,40 @@
#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
__license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
from PyQt4.Qt import QDialog, QApplication
from calibre.gui2.dialogs.add_from_isbn_ui import Ui_Dialog
from calibre.ebooks.metadata import check_isbn
class AddFromISBN(QDialog, Ui_Dialog):
def __init__(self, parent=None):
QDialog.__init__(self, parent)
self.setupUi(self)
self.isbns = []
self.paste_button.clicked.connect(self.paste)
def paste(self, *args):
app = QApplication.instance()
c = app.clipboard()
txt = unicode(c.text()).strip()
if txt:
old = unicode(self.isbn_box.toPlainText()).strip()
new = old + '\n' + txt
self.isbn_box.setPlainText(new)
def accept(self, *args):
for line in unicode(self.isbn_box.toPlainText()).strip().splitlines():
if line:
isbn = check_isbn(line)
if isbn is not None:
isbn = isbn.upper()
if isbn not in self.isbns:
self.isbns.append(isbn)
QDialog.accept(self, *args)

View File

@ -0,0 +1,90 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Dialog</class>
<widget class="QDialog" name="Dialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>678</width>
<height>430</height>
</rect>
</property>
<property name="windowTitle">
<string>Add books by ISBN</string>
</property>
<property name="windowIcon">
<iconset resource="../../../../resources/images.qrc">
<normaloff>:/images/add_book.svg</normaloff>:/images/add_book.svg</iconset>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QPlainTextEdit" name="isbn_box"/>
</item>
<item row="0" column="1">
<widget class="QLabel" name="label">
<property name="text">
<string>&lt;p&gt;Enter a list of ISBNs in the box to the left, one per line. calibre will automatically create entries for books based on the ISBN and download metadata and covers for them.&lt;p&gt;Any invalid ISBNs in the list will be ignored.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QPushButton" name="paste_button">
<property name="text">
<string>&amp;Paste from clipboard</string>
</property>
</widget>
</item>
</layout>
</widget>
<resources>
<include location="../../../../resources/images.qrc"/>
</resources>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>Dialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>Dialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -18,7 +18,7 @@ from calibre.gui2 import error_dialog, config, gprefs, \
open_url, open_local_file, \
ALL_COLUMNS, NONE, info_dialog, choose_files, \
warning_dialog, ResizableDialog, question_dialog
from calibre.utils.config import prefs
from calibre.utils.config import prefs, read_raw_tweaks, write_tweaks
from calibre.ebooks import BOOK_EXTENSIONS
from calibre.ebooks.oeb.iterator import is_supported
from calibre.library.server import server_config
@ -514,8 +514,18 @@ class ConfigDialog(ResizableDialog, Ui_Dialog):
self.opt_toolbar_text.setCurrentIndex(idx)
self.reset_confirmation_button.clicked.connect(self.reset_confirmation)
deft, curt = read_raw_tweaks()
self.current_tweaks.setPlainText(curt)
self.default_tweaks.setPlainText(deft)
self.restore_tweaks_to_default_button.clicked.connect(self.restore_tweaks_to_default)
self.category_view.setCurrentIndex(self.category_view.model().index_for_name(initial_category))
def restore_tweaks_to_default(self, *args):
deft, curt = read_raw_tweaks()
self.current_tweaks.setPlainText(deft)
def reset_confirmation(self):
from calibre.gui2 import dynamic
for key in dynamic.keys():
@ -687,6 +697,22 @@ class ConfigDialog(ResizableDialog, Ui_Dialog):
self.input_order.insertItem(idx-1, self.input_order.takeItem(idx))
self.input_order.setCurrentRow(idx-1)
def set_tweaks(self):
raw = unicode(self.current_tweaks.toPlainText())
raw = re.sub(r'(?m)^#.*fileencoding.*', '# ', raw)
try:
exec raw
except:
import traceback
error_dialog(self, _('Invalid tweaks'),
_('The tweaks you entered are invalid, try resetting the'
' tweaks to default and changing them one by one until'
' you find the invalid setting.'),
det_msg=traceback.format_exc(), show=True)
return False
write_tweaks(raw)
return True
def down_input(self):
idx = self.input_order.currentRow()
if idx < self.input_order.count()-1:
@ -852,6 +878,8 @@ class ConfigDialog(ResizableDialog, Ui_Dialog):
return
if not self.add_save.save_settings():
return
if not self.set_tweaks():
return
wl = self.opt_worker_limit.value()
if wl%2 != 0:
wl += 1

View File

@ -719,61 +719,192 @@
<widget class="QWidget" name="page_2">
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>&amp;Maximum number of waiting worker processes (needs restart):</string>
</property>
<property name="buddy">
<cstring>opt_worker_limit</cstring>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QSpinBox" name="opt_worker_limit">
<property name="minimum">
<number>2</number>
</property>
<property name="maximum">
<number>10000</number>
</property>
<property name="singleStep">
<number>2</number>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QPushButton" name="compact_button">
<property name="text">
<string>&amp;Check database integrity</string>
</property>
</widget>
</item>
<item row="5" column="0" colspan="2">
<widget class="QPushButton" name="button_osx_symlinks">
<property name="text">
<string>&amp;Install command line tools</string>
</property>
</widget>
</item>
<item row="4" column="0" colspan="2">
<widget class="QPushButton" name="button_open_config_dir">
<property name="text">
<string>Open calibre &amp;configuration directory</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="opt_enforce_cpu_limit">
<property name="text">
<string>Limit the max. simultaneous jobs to the available CPU &amp;cores</string>
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QPushButton" name="device_detection_button">
<property name="text">
<string>Debug &amp;device detection</string>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="tab">
<attribute name="title">
<string>&amp;Miscellaneous</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_9">
<item row="0" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>&amp;Maximum number of waiting worker processes (needs restart):</string>
</property>
<property name="buddy">
<cstring>opt_worker_limit</cstring>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QSpinBox" name="opt_worker_limit">
<property name="minimum">
<number>2</number>
</property>
<property name="maximum">
<number>10000</number>
</property>
<property name="singleStep">
<number>2</number>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="opt_enforce_cpu_limit">
<property name="text">
<string>Limit the max. simultaneous jobs to the available CPU &amp;cores</string>
</property>
</widget>
</item>
<item row="2" column="0">
<spacer name="verticalSpacer_4">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>79</height>
</size>
</property>
</spacer>
</item>
<item row="3" column="0" colspan="2">
<widget class="QPushButton" name="device_detection_button">
<property name="text">
<string>Debug &amp;device detection</string>
</property>
</widget>
</item>
<item row="4" column="0">
<spacer name="verticalSpacer_6">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>80</height>
</size>
</property>
</spacer>
</item>
<item row="5" column="0" colspan="2">
<widget class="QPushButton" name="compact_button">
<property name="text">
<string>&amp;Check database integrity</string>
</property>
</widget>
</item>
<item row="6" column="0">
<spacer name="verticalSpacer_7">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>79</height>
</size>
</property>
</spacer>
</item>
<item row="7" column="0" colspan="2">
<widget class="QPushButton" name="button_open_config_dir">
<property name="text">
<string>Open calibre &amp;configuration directory</string>
</property>
</widget>
</item>
<item row="8" column="0">
<spacer name="verticalSpacer_8">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>80</height>
</size>
</property>
</spacer>
</item>
<item row="9" column="0" colspan="2">
<widget class="QPushButton" name="button_osx_symlinks">
<property name="text">
<string>&amp;Install command line tools</string>
</property>
</widget>
</item>
<item row="10" column="0">
<spacer name="verticalSpacer_9">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>79</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_2">
<attribute name="title">
<string>&amp;Tweaks</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QLabel" name="label_18">
<property name="text">
<string>Values for the tweaks are shown below. Edit them to change the behavior of calibre</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_6">
<property name="title">
<string>All available tweaks</string>
</property>
<layout class="QGridLayout" name="gridLayout_11">
<item row="0" column="0">
<widget class="QPlainTextEdit" name="default_tweaks">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_7">
<property name="title">
<string>&amp;Current tweaks</string>
</property>
<layout class="QGridLayout" name="gridLayout_10">
<item row="0" column="0">
<widget class="QPlainTextEdit" name="current_tweaks"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QPushButton" name="restore_tweaks_to_default_button">
<property name="text">
<string>&amp;Restore to defaults</string>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>

View File

@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>730</width>
<width>768</width>
<height>342</height>
</rect>
</property>
@ -51,6 +51,9 @@
</item>
<item>
<widget class="QPushButton" name="recalc_author_sort">
<property name="toolTip">
<string>Reset all the author sort values to a value automatically generated from the author. Exactly how this value is automatically generated can be controlled via Preferences-&gt;Advanced-&gt;Tweaks</string>
</property>
<property name="text">
<string>Recalculate all author sort values</string>
</property>

View File

@ -538,8 +538,10 @@ class MainWindowMixin(object):
self.add_menu.addAction(_('Add books from directories, including '
'sub directories (Multiple books per directory, assumes every '
'ebook file is a different book)'), self.add_recursive_multiple)
self.add_menu.addSeparator()
self.add_menu.addAction(_('Add Empty book. (Book entry with no '
'formats)'), self.add_empty)
self.add_menu.addAction(_('Add from ISBN'), self.add_from_isbn)
self.action_add.setMenu(self.add_menu)
self.action_add.triggered.connect(self.add_books)
self.action_del.triggered.connect(self.delete_books)

View File

@ -146,7 +146,8 @@ class SearchBox2(QComboBox):
self._in_a_search = False
if event.key() in (Qt.Key_Return, Qt.Key_Enter):
self.do_search()
self.timer.start(1500)
if self.as_you_type:
self.timer.start(1500)
def mouse_released(self, event):
self.normalize_state()

View File

@ -675,9 +675,7 @@ class Wizard(QWizard):
self.connect(self.library_page, SIGNAL('retranslate()'),
self.retranslate)
self.finish_page = FinishPage()
bt = unicode(self.buttonText(self.FinishButton)).replace('&', '')
t = unicode(self.finish_page.finish_text.text())
self.finish_page.finish_text.setText(t%bt)
self.set_finish_text()
self.kindle_page = KindlePage()
self.stanza_page = StanzaPage()
self.word_player_page = WordPlayerPage()
@ -702,6 +700,7 @@ class Wizard(QWizard):
for pid in self.pageIds():
page = self.page(pid)
page.retranslateUi(page)
self.set_finish_text()
def accept(self):
pages = map(self.page, self.visitedPages())
@ -715,6 +714,13 @@ class Wizard(QWizard):
def completed(self, newloc):
return QWizard.accept(self)
def set_finish_text(self, *args):
bt = unicode(self.buttonText(self.FinishButton)).replace('&', '')
t = unicode(self.finish_page.finish_text.text())
if '%s' in t:
self.finish_page.finish_text.setText(t%bt)
def wizard(parent=None):
w = Wizard(parent)
return w

View File

@ -15,6 +15,7 @@ from calibre import prepare_string_for_xml
# Hackish - ignoring sentences ending or beginning in numbers to avoid
# confusion with decimal points.
lost_cr_pat = re.compile('([a-z])([\.\?!])([A-Z])')
lost_cr_exception_pat = re.compile(r'(Ph\.D)|(D\.Phil)|((Dr|Mr|Mrs|Ms)\.[A-Z])')
def comments_to_html(comments):
'''
@ -51,6 +52,8 @@ def comments_to_html(comments):
return '\n'.join(parts)
# Explode lost CRs to \n\n
comments = lost_cr_exception_pat.sub(lambda m: m.group().replace('.',
'.\r'), comments)
for lost_cr in lost_cr_pat.finditer(comments):
comments = comments.replace(lost_cr.group(),
'%s%s\n\n%s' % (lost_cr.group(1),

View File

@ -1673,6 +1673,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
self.data.refresh_ids(self, [id]) # Needed to update format list and size
if notify:
self.notify('add', [id])
return id
def get_top_level_move_items(self):
items = set(os.listdir(self.library_path))

View File

@ -12,7 +12,7 @@ from ctypes import Structure as _Structure, c_char_p, c_uint, c_void_p, POINTER,
from tempfile import NamedTemporaryFile
from StringIO import StringIO
from calibre import iswindows, load_library, CurrentDir, prints
from calibre import iswindows, load_library, CurrentDir
from calibre.ptempfile import TemporaryDirectory
_librar_name = 'libunrar'

View File

@ -30,8 +30,8 @@ Environment variables
Tweaks
------------
Tweaks are small changes that you can specify to control various aspects of |app|'s behavior. You specify them by editing the 2tweaks.py file in the config directory.
The default tweaks.py file is reproduced below
Tweaks are small changes that you can specify to control various aspects of |app|'s behavior. You can change them by going to Preferences->Advanced->Tweaks.
The default values for the tweaks are reproduced below
.. literalinclude:: ../../../resources/default_tweaks.py

View File

@ -277,7 +277,8 @@ In |app|, you would instead use tags to mark genre and read status and then just
Why doesn't |app| have a column for foo?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|app| is designed to have columns for the most frequently and widely used fields. If it does not have a coulmn for your favorite field, you can always add a tag to the book for that piece of information. |app| also supports a general purpose "comments" fields for longer items.
|app| is designed to have columns for the most frequently and widely used fields. In addition, you can add any columns you like. Columns can be added via Preferences->Interface.
Watch the tutorial `UI Power tips <http://calibre-ebook.com/demo#tutorials>`_ to learn how to create your own columns.
How do I move my |app| library from one computer to another?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -247,6 +247,7 @@ You can search for the absence or presence of a field using the special "true" a
cover:false will give you all books without a cover
series:true will give you all books that belong to a series
comments:false will give you all books with an empty comment
format:false will give you all books with no actual files (empty records)
Yes/no custom columns are searchable. Searching for ``false``, ``empty``, or ``blank`` will find all books
with undefined values in the column. Searching for ``true`` will find all books that do not have undefined

File diff suppressed because it is too large Load Diff

View File

@ -703,16 +703,21 @@ if prefs['installation_uuid'] is None:
prefs['installation_uuid'] = str(uuid.uuid4())
# Read tweaks
def read_tweaks():
def read_raw_tweaks():
make_config_dir()
default_tweaks = P('default_tweaks.py', data=True)
tweaks_file = os.path.join(config_dir, 'tweaks.py')
if not os.path.exists(tweaks_file):
with open(tweaks_file, 'wb') as f:
f.write(default_tweaks)
with open(tweaks_file, 'rb') as f:
return default_tweaks, f.read()
def read_tweaks():
default_tweaks, tweaks = read_raw_tweaks()
l, g = {}, {}
try:
exec open(tweaks_file, 'rb') in g, l
exec tweaks in g, l
except:
print 'Failed to load custom tweaks file'
traceback.print_exc()
@ -721,6 +726,13 @@ def read_tweaks():
dl.update(l)
return dl
def write_tweaks(raw):
make_config_dir()
tweaks_file = os.path.join(config_dir, 'tweaks.py')
with open(tweaks_file, 'wb') as f:
f.write(raw)
tweaks = read_tweaks()
def migrate():

View File

@ -194,7 +194,7 @@ class Image(_magick.Image): # {{{
# }}}
def create_canvas(width, height, bgcolor):
def create_canvas(width, height, bgcolor='white'):
canvas = Image()
canvas.create_canvas(int(width), int(height), str(bgcolor))
return canvas