]*>\s*(]*>\s*\s*)
\s*){0,3}\s*]*>\s*(]*>)?\s*" % length, re.UNICODE)
+ if length < 150:
+ res = unwrap.sub(' ', res)
f.write(res)
self.write_inline_css(inline_class)
stream.seek(0)
From 81f081527da8e337df5eaaa2c8fd378f544bf518 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Tue, 24 Aug 2010 16:52:19 -0600
Subject: [PATCH 03/22] Mark library as current location when device detected
---
src/calibre/gui2/layout.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/calibre/gui2/layout.py b/src/calibre/gui2/layout.py
index 3f273f63fc..fccef29abe 100644
--- a/src/calibre/gui2/layout.py
+++ b/src/calibre/gui2/layout.py
@@ -97,6 +97,7 @@ class LocationManager(QObject): # {{{
self.free[2] = fs[2] if fs[2] is not None and cpb is not None else -1
self.update_tooltips()
if self.has_device != had_device:
+ self.location_library.setChecked(True)
self.locations_changed.emit()
if not self.has_device:
self.location_library.trigger()
From 19bcbb713f98d7150b08ab799348bbdab167b46a Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Tue, 24 Aug 2010 18:13:13 -0600
Subject: [PATCH 04/22] Framework for plugin based preferences dialog
---
src/calibre/customize/__init__.py | 62 +++++++++++++++++++---
src/calibre/customize/ui.py | 14 ++++-
src/calibre/gui2/preferences/__init__.py | 67 +++++++++++++++++++-----
src/calibre/gui2/ui.py | 2 +
4 files changed, 123 insertions(+), 22 deletions(-)
diff --git a/src/calibre/customize/__init__.py b/src/calibre/customize/__init__.py
index 88c9324239..2b7c476579 100644
--- a/src/calibre/customize/__init__.py
+++ b/src/calibre/customize/__init__.py
@@ -8,7 +8,7 @@ from calibre.constants import numeric_version
from calibre.ptempfile import PersistentTemporaryFile
-class Plugin(object):
+class Plugin(object): # {{{
'''
A calibre plugin. Useful members include:
@@ -147,9 +147,9 @@ class Plugin(object):
if hasattr(it, '__exit__'):
it.__exit__(*args)
+# }}}
-
-class FileTypePlugin(Plugin):
+class FileTypePlugin(Plugin): # {{{
'''
A plugin that is associated with a particular set of file types.
'''
@@ -191,7 +191,9 @@ class FileTypePlugin(Plugin):
# Default implementation does nothing
return path_to_ebook
-class MetadataReaderPlugin(Plugin):
+# }}}
+
+class MetadataReaderPlugin(Plugin): # {{{
'''
A plugin that implements reading metadata from a set of file types.
'''
@@ -219,8 +221,9 @@ class MetadataReaderPlugin(Plugin):
:return: A :class:`calibre.ebooks.metadata.MetaInformation` object
'''
return None
+# }}}
-class MetadataWriterPlugin(Plugin):
+class MetadataWriterPlugin(Plugin): # {{{
'''
A plugin that implements reading metadata from a set of file types.
'''
@@ -249,7 +252,9 @@ class MetadataWriterPlugin(Plugin):
'''
pass
-class CatalogPlugin(Plugin):
+# }}}
+
+class CatalogPlugin(Plugin): # {{{
'''
A plugin that implements a catalog generator.
'''
@@ -352,7 +357,9 @@ class CatalogPlugin(Plugin):
raise NotImplementedError('CatalogPlugin.generate_catalog() default '
'method, should be overridden in subclass')
-class InterfaceActionBase(Plugin):
+# }}}
+
+class InterfaceActionBase(Plugin): # {{{
supported_platforms = ['windows', 'osx', 'linux']
author = 'Kovid Goyal'
@@ -360,3 +367,44 @@ class InterfaceActionBase(Plugin):
can_be_disabled = False
actual_plugin = None
+# }}}
+
+class PreferencesPlugin(Plugin): # {{{
+
+ supported_platforms = ['windows', 'osx', 'linux']
+ author = 'Kovid Goyal'
+ type = _('Preferences')
+ can_be_disabled = False
+
+ #: Import path to module that contains a class named ConfigWidget
+ #: which implements the ConfigWidgetInterface. Used by
+ #: :meth:`create_widget`.
+ config_widget = None
+
+ #: Where in the list of categories the :attr:`category` of this plugin should be.
+ category_order = 100
+
+ #: Where in the list of names in a category, the :attr:`gui_name` of this
+ #: plugin should be
+ name_order = 100
+
+ #: The category this plugin should be in
+ category = None
+
+ #: The name displayed to the user for this plugin
+ gui_name = None
+
+ def create_widget(self, parent=None):
+ '''
+ Create and return the actual Qt widget used for setting this group of
+ preferences. The widget must implement the ConfigWidgetInterface.
+
+ The default implementation uses :attr:`config_widget` to instantiate
+ the widget.
+ '''
+ base = __import__(self.config_widget, fromlist=[1])
+ widget = base.ConfigWidget(parent)
+ return widget
+
+# }}}
+
diff --git a/src/calibre/customize/ui.py b/src/calibre/customize/ui.py
index b720964c92..265b42bad2 100644
--- a/src/calibre/customize/ui.py
+++ b/src/calibre/customize/ui.py
@@ -7,7 +7,8 @@ from contextlib import closing
from calibre.customize import Plugin, CatalogPlugin, FileTypePlugin, \
MetadataReaderPlugin, MetadataWriterPlugin, \
- InterfaceActionBase as InterfaceAction
+ InterfaceActionBase as InterfaceAction, \
+ PreferencesPlugin
from calibre.customize.conversion import InputFormatPlugin, OutputFormatPlugin
from calibre.customize.profiles import InputProfile, OutputProfile
from calibre.customize.builtins import plugins as builtin_plugins
@@ -257,6 +258,17 @@ def interface_actions():
yield plugin
# }}}
+# Preferences Plugins # {{{
+
+def preferences_plugins():
+ customization = config['plugin_customization']
+ for plugin in _initialized_plugins:
+ if isinstance(plugin, PreferencesPlugin):
+ if not is_disabled(plugin):
+ plugin.site_customization = customization.get(plugin.name, '')
+ yield plugin
+# }}}
+
# Metadata read/write {{{
_metadata_readers = {}
_metadata_writers = {}
diff --git a/src/calibre/gui2/preferences/__init__.py b/src/calibre/gui2/preferences/__init__.py
index 20ec932fb8..5f9535bb93 100644
--- a/src/calibre/gui2/preferences/__init__.py
+++ b/src/calibre/gui2/preferences/__init__.py
@@ -7,28 +7,67 @@ __docformat__ = 'restructuredtext en'
from PyQt4.Qt import QWidget, pyqtSignal
-class PreferenceWidget(QWidget):
+from calibre.customize.ui import preferences_plugins
- category = None
- name = None
+class ConfigWidgetInterface(object):
- changed_signal = pyqtSignal()
-
- def __init__(self, parent=None):
- QWidget.__init__(self, parent)
-
- self.has_changed = False
- self.changed.connect(lambda : setattr(self, 'has_changed', True))
- self.setupUi(self)
+ changed_signal = None
def genesis(self, gui):
raise NotImplementedError()
- def reset_to_defaults(self):
+ def restore_defaults(self):
pass
def commit(self):
pass
- def add_boolean(self, widget_name, preference_interface, pref_name):
- pass
+
+class ConfigWidgetBase(QWidget, ConfigWidgetInterface):
+
+ changed_signal = pyqtSignal()
+
+ def __init__(self, parent=None):
+ QWidget.__init__(self, parent)
+ if hasattr(self, 'setupUi'):
+ self.setupUi(self)
+
+def get_plugin(category, name):
+ for plugin in preferences_plugins():
+ if plugin.category == category and plugin.name == name:
+ return plugin
+ raise ValueError(
+ 'No Preferences PLugin with category: %s and name: %s found' %
+ (category, name))
+
+def test_widget(category, name, gui=None): # {{{
+ from PyQt4.Qt import QDialog, QVBoxLayout, QDialogButtonBox
+ pl = get_plugin(category, name)
+ d = QDialog()
+ d.resize(750, 550)
+ bb = QDialogButtonBox(d)
+ bb.setStandardButtons(bb.Apply|bb.Cancel|bb.RestoreDefaults)
+ bb.accepted.connect(d.accept)
+ bb.rejected.connect(d.reject)
+ w = pl.create_widget(d)
+ bb.button(bb.RestoreDefaults).clicked.connect(w.restore_defaults)
+ bb.button(bb.Apply).setEnabled(False)
+ w.changed_signal.connect(lambda : bb.button(bb.Apply).setEnable(True))
+ l = QVBoxLayout()
+ pl.setLayout(l)
+ l.addWidget(w)
+ if gui is None:
+ from calibre.gui2.ui import Main
+ from calibre.gui2.main import option_parser
+ from calibre.library.db import db
+ parser = option_parser()
+ opts, args = parser.parse_args([])
+ actions = tuple(Main.create_application_menubar())
+ db = db()
+ gui = Main(opts)
+ gui.initialize(db.library_path, db, None, actions)
+ w.genesis(gui)
+ if d.exec_() == QDialog.Accepted:
+ w.commit()
+# }}}
+
diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py
index 41b166b13f..c2635e8b44 100644
--- a/src/calibre/gui2/ui.py
+++ b/src/calibre/gui2/ui.py
@@ -50,6 +50,8 @@ class Listener(Thread): # {{{
self.start()
def run(self):
+ if self.listener is None:
+ return
while self._run:
try:
conn = self.listener.accept()
From 238b06dfc494d5daa66acfebee0668b9091e33fc Mon Sep 17 00:00:00 2001
From: Timothy Legge
Date: Tue, 24 Aug 2010 21:40:31 -0300
Subject: [PATCH 05/22] Fix silly typo/copy-paste
---
src/calibre/devices/kobo/driver.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/calibre/devices/kobo/driver.py b/src/calibre/devices/kobo/driver.py
index 74468845f6..bd96ecd3cd 100644
--- a/src/calibre/devices/kobo/driver.py
+++ b/src/calibre/devices/kobo/driver.py
@@ -193,7 +193,7 @@ class KOBO(USBMS):
connection.commit()
cursor.close()
- if ImageID != None:
+ if ImageID == None:
print "Error condition ImageID was not found"
print "You likely tried to delete a book that the kobo has not yet added to the database"
From 725e9dd68f53ce83f58fec387baeb722bc99a649 Mon Sep 17 00:00:00 2001
From: Timothy Legge
Date: Tue, 24 Aug 2010 21:58:45 -0300
Subject: [PATCH 06/22] Show I'm Reading list as a read-only collection
---
src/calibre/devices/kobo/books.py | 2 +-
src/calibre/devices/kobo/driver.py | 17 +++++++++++++----
2 files changed, 14 insertions(+), 5 deletions(-)
diff --git a/src/calibre/devices/kobo/books.py b/src/calibre/devices/kobo/books.py
index a5b2e98d2f..124a10b380 100644
--- a/src/calibre/devices/kobo/books.py
+++ b/src/calibre/devices/kobo/books.py
@@ -20,7 +20,7 @@ class Book(MetaInformation):
'title_sort', 'comments', 'category', 'publisher', 'series',
'series_index', 'rating', 'isbn', 'language', 'application_id',
'book_producer', 'lccn', 'lcc', 'ddc', 'rights', 'publication_type',
- 'uuid',
+ 'uuid', 'device_collections',
]
def __init__(self, prefix, lpath, title, authors, mime, date, ContentType, thumbnail_name, other=None):
diff --git a/src/calibre/devices/kobo/driver.py b/src/calibre/devices/kobo/driver.py
index bd96ecd3cd..35fceb80f7 100644
--- a/src/calibre/devices/kobo/driver.py
+++ b/src/calibre/devices/kobo/driver.py
@@ -72,7 +72,7 @@ class KOBO(USBMS):
for idx,b in enumerate(bl):
bl_cache[b.lpath] = idx
- def update_booklist(prefix, path, title, authors, mime, date, ContentType, ImageID):
+ def update_booklist(prefix, path, title, authors, mime, date, ContentType, ImageID, readstatus):
changed = False
# if path_to_ext(path) in self.FORMATS:
try:
@@ -82,6 +82,13 @@ class KOBO(USBMS):
lpath = lpath.replace('\\', '/')
# print "LPATH: " + lpath
+ playlist_map = {}
+
+ if readstatus == 1:
+ if lpath not in playlist_map:
+ playlist_map[lpath] = []
+ playlist_map[lpath].append("I\'m Reading")
+
path = self.normalize_path(path)
# print "Normalized FileName: " + path
@@ -97,11 +104,13 @@ class KOBO(USBMS):
if self.update_metadata_item(bl[idx]):
# print 'update_metadata_item returned true'
changed = True
+ bl[idx].device_collections = playlist_map.get(lpath, [])
else:
book = Book(prefix, lpath, title, authors, mime, date, ContentType, ImageID)
# print 'Update booklist'
if bl.add_book(book, replace_metadata=False):
changed = True
+ book.device_collections = playlist_map.get(book.lpath, [])
except: # Probably a path encoding error
import traceback
traceback.print_exc()
@@ -117,7 +126,7 @@ class KOBO(USBMS):
#cursor.close()
query= 'select Title, Attribution, DateCreated, ContentID, MimeType, ContentType, ' \
- 'ImageID from content where BookID is Null'
+ 'ImageID, ReadStatus from content where BookID is Null'
cursor.execute (query)
@@ -129,10 +138,10 @@ class KOBO(USBMS):
mime = mime_type_ext(path_to_ext(row[3]))
if oncard != 'carda' and oncard != 'cardb' and not row[3].startswith("file:///mnt/sd/"):
- changed = update_booklist(self._main_prefix, path, row[0], row[1], mime, row[2], row[5], row[6])
+ changed = update_booklist(self._main_prefix, path, row[0], row[1], mime, row[2], row[5], row[6], row[7])
# print "shortbook: " + path
elif oncard == 'carda' and row[3].startswith("file:///mnt/sd/"):
- changed = update_booklist(self._card_a_prefix, path, row[0], row[1], mime, row[2], row[5], row[6])
+ changed = update_booklist(self._card_a_prefix, path, row[0], row[1], mime, row[2], row[5], row[6], row[7])
if changed:
need_sync = True
From 94791416c6c73f248a2bdf5481c10c2688f06e0c Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Tue, 24 Aug 2010 21:18:06 -0600
Subject: [PATCH 07/22] ...
---
src/calibre/gui2/preferences/__init__.py | 48 +++++++++++++++++++++++-
1 file changed, 47 insertions(+), 1 deletion(-)
diff --git a/src/calibre/gui2/preferences/__init__.py b/src/calibre/gui2/preferences/__init__.py
index 5f9535bb93..eabb33d03b 100644
--- a/src/calibre/gui2/preferences/__init__.py
+++ b/src/calibre/gui2/preferences/__init__.py
@@ -5,9 +5,10 @@ __license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal '
__docformat__ = 'restructuredtext en'
-from PyQt4.Qt import QWidget, pyqtSignal
+from PyQt4.Qt import QWidget, pyqtSignal, QCheckBox
from calibre.customize.ui import preferences_plugins
+from calibre.utils.config import DynamicConfig, XMLConfig
class ConfigWidgetInterface(object):
@@ -22,6 +23,51 @@ class ConfigWidgetInterface(object):
def commit(self):
pass
+class Setting(object):
+
+ def __init__(self, name, config_obj, widget, gui_name=None):
+ self.name, self.gui_name = name, gui_name
+ if gui_name is None:
+ self.gui_name = 'opt_'+name
+ self.config_obj = config_obj
+ self.gui_obj = getattr(widget, self.gui_name)
+
+ if isinstance(self.gui_obj, QCheckBox):
+ self.datatype = 'bool'
+ self.gui_obj.stateChanged.connect(lambda x:
+ widget.changed_signal.emit())
+ else:
+ raise ValueError('Unknown data type')
+
+ if isinstance(config_obj, (DynamicConfig, XMLConfig)):
+ self.config_type = 'dict'
+ else:
+ raise ValueError('Unknown config type')
+
+ def initialize(self):
+ self.set_gui_val()
+
+ def commit(self):
+ self.set_config_val()
+
+ def restore_defaults(self):
+ self.set_gui_val(to_default=True)
+
+ def set_gui_val(self, to_default=False):
+ if self.config_type == 'dict':
+ if to_default:
+ val = self.config_obj.defaults[self.name]
+ else:
+ val = self.config_obj[self.name]
+ if self.datatype == 'bool':
+ self.gui_obj.setChecked(bool(val))
+
+ def set_config_val(self):
+ if self.datatype == 'bool':
+ val = bool(self.gui_obj.isChecked())
+ if self.config_type == 'dict':
+ self.config_obj[self.name] = val
+
class ConfigWidgetBase(QWidget, ConfigWidgetInterface):
From b6e17824e33211143e05999d025d340cbb60d9bb Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Tue, 24 Aug 2010 21:41:45 -0600
Subject: [PATCH 08/22] Fix #6603 (Failure to fetch RSS feeds)
---
resources/recipes/nationalreviewonline.recipe | 31 ++++++++++---------
1 file changed, 16 insertions(+), 15 deletions(-)
diff --git a/resources/recipes/nationalreviewonline.recipe b/resources/recipes/nationalreviewonline.recipe
index 5f002019d9..8915c8524c 100644
--- a/resources/recipes/nationalreviewonline.recipe
+++ b/resources/recipes/nationalreviewonline.recipe
@@ -19,7 +19,7 @@ class NRO(BasicNewsRecipe):
encoding = 'utf-8'
use_embedded_content = True
remove_javascript = True
-
+
conversion_options = {
'comment' : description
@@ -34,17 +34,18 @@ class NRO(BasicNewsRecipe):
]
feeds = [
-
- (u'National Review', u'http://www.nationalreview.com/index.xml'),
- (u'The Corner', u'http://corner.nationalreview.com/corner.xml'),
- (u'The Agenda', u'http://agenda.nationalreview.com/agenda.xml'),
- (u'Bench Memos', u'http://bench.nationalreview.com/bench.xml'),
- (u'Campaign Spot', u'http://campaignspot.nationalreview.com/campaignspot.xml'),
- (u'Critical Care', u'http://healthcare.nationalreview.com/healthcare.xml'),
- (u'Doctor, Doctor', u'http://www.nationalreview.com/doctor/doctor.xml'),
- (u"Kudlow's Money Politic$", u'http://kudlow.nationalreview.com/kudlow.xml'),
- (u'Media Blog', u'http://media.nationalreview.com/media.xml'),
- (u'Phi Beta Cons', u'http://phibetacons.nationalreview.com/phibetacons.xml'),
- (u'Planet Gore', u'http://planetgore.nationalreview.com/planetgore.xml')
-
- ]
+
+ (u'National Review', u'http://www.nationalreview.com/articles/feed'),
+ (u'The Corner', u'http://www.nationalreview.com/corner/feed'),
+ (u'The Agenda', u'http://www.nationalreview.com/agenda/feed'),
+ (u'Bench Memos', u'http://www.nationalreview.com/bench-memos/feed'),
+ (u'Campaign Spot', u'http://www.nationalreview.com/campaign-spot/feed'),
+ (u'Battle 10', u'http://www.nationalreview.com/battle10/feed'),
+ (u'Critical Care', u'http://www.nationalreview.com/critical-condition/feed'),
+ (u"Kudlow's Money Politic$", u'http://www.nationalreview.com/kudlows-money-politics/feed'),
+ (u'Media Blog', u'http://www.nationalreview.com/media-blog/feed'),
+ (u'Exchequer', u'http://www.nationalreview.com/exchequer/feed'),
+ (u'Phi Beta Cons', u'http://www.nationalreview.com/phi-beta-cons/feed'),
+ (u'Planet Gore', u'http://www.nationalreview.com/planet-gore/feed')
+
+ ]
\ No newline at end of file
From e8a0d606205806486838b7747644529f49dfa414 Mon Sep 17 00:00:00 2001
From: John Schember
Date: Wed, 25 Aug 2010 08:24:43 -0400
Subject: [PATCH 09/22] FB2 Output: Experimental option to break book into
sections based upon chapters.
---
.bzrignore | 1 +
src/calibre/ebooks/fb2/fb2ml.py | 9 ++++++++-
src/calibre/ebooks/fb2/output.py | 6 ++++++
src/calibre/gui2/convert/fb2_output.py | 2 +-
src/calibre/gui2/convert/fb2_output.ui | 9 ++++++++-
5 files changed, 24 insertions(+), 3 deletions(-)
diff --git a/.bzrignore b/.bzrignore
index 17d232238f..e2d1636ddd 100644
--- a/.bzrignore
+++ b/.bzrignore
@@ -28,3 +28,4 @@ nbproject/
*.userprefs
.project
.pydevproject
+.settings/
diff --git a/src/calibre/ebooks/fb2/fb2ml.py b/src/calibre/ebooks/fb2/fb2ml.py
index d61f4369e6..d57389445c 100644
--- a/src/calibre/ebooks/fb2/fb2ml.py
+++ b/src/calibre/ebooks/fb2/fb2ml.py
@@ -86,10 +86,12 @@ class FB2MLizer(object):
output.append(self.fb2_footer())
output = ''.join(output).replace(u'ghji87yhjko0Caliblre-toc-placeholder-for-insertion-later8ujko0987yjk', self.get_toc())
output = self.clean_text(output)
+ if self.opts.sectionize_chapters:
+ output = self.sectionize_chapters(output)
return u'\n%s' % etree.tostring(etree.fromstring(output), encoding=unicode, pretty_print=True)
def clean_text(self, text):
- text = re.sub('[ ]*
', '', text)
+ text = re.sub('\s*
', '', text)
return text
@@ -149,6 +151,11 @@ class FB2MLizer(object):
self.oeb.warn('Ignoring toc item: %s not found in document.' % item)
return ''.join(toc)
+ def sectionize_chapters(self, text):
+ text = re.sub(r'(?imsu)(?P)\s*(?P()*\s*.+?\s*(
)*)', lambda mo: '%s%s' % (mo.group('anchor'), mo.group('strong')), text)
+ text = re.sub(r'(?imsu)\s*(?P)\s*
\s*(?P()*\s*.+?\s*(
)*)', lambda mo: '%s%s' % (mo.group('anchor'), mo.group('strong')), text)
+ return text
+
def get_text(self):
text = []
for item in self.oeb_book.spine:
diff --git a/src/calibre/ebooks/fb2/output.py b/src/calibre/ebooks/fb2/output.py
index 2bb49318af..d0125afe89 100644
--- a/src/calibre/ebooks/fb2/output.py
+++ b/src/calibre/ebooks/fb2/output.py
@@ -19,6 +19,12 @@ class FB2Output(OutputFormatPlugin):
OptionRecommendation(name='inline_toc',
recommended_value=False, level=OptionRecommendation.LOW,
help=_('Add Table of Contents to beginning of the book.')),
+ OptionRecommendation(name='sectionize_chapters',
+ recommended_value=False, level=OptionRecommendation.LOW,
+ help=_('Try to turn chapters into individual sections. ' \
+ 'WARNING: ' \
+ 'This option is experimental. It can cause conversion ' \
+ 'to fail. It can also produce unexpected output.')),
])
def convert(self, oeb_book, output_path, input_plugin, opts, log):
diff --git a/src/calibre/gui2/convert/fb2_output.py b/src/calibre/gui2/convert/fb2_output.py
index 55c708c2a4..145b14f8c9 100644
--- a/src/calibre/gui2/convert/fb2_output.py
+++ b/src/calibre/gui2/convert/fb2_output.py
@@ -16,6 +16,6 @@ class PluginWidget(Widget, Ui_Form):
COMMIT_NAME = 'fb2_output'
def __init__(self, parent, get_option, get_help, db=None, book_id=None):
- Widget.__init__(self, parent, ['inline_toc'])
+ Widget.__init__(self, parent, ['inline_toc', 'sectionize_chapters'])
self.db, self.book_id = db, book_id
self.initialize_options(get_option, get_help, db, book_id)
diff --git a/src/calibre/gui2/convert/fb2_output.ui b/src/calibre/gui2/convert/fb2_output.ui
index 007af454c4..a43a8b72ea 100644
--- a/src/calibre/gui2/convert/fb2_output.ui
+++ b/src/calibre/gui2/convert/fb2_output.ui
@@ -14,7 +14,7 @@
Form
- -
+
-
Qt::Vertical
@@ -34,6 +34,13 @@
+ -
+
+
+ Sectionize Chapters (Use with care!)
+
+
+
From 440e34d1b856a91551e5c87400e5fbb85d2a4ba0 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Wed, 25 Aug 2010 08:16:50 -0600
Subject: [PATCH 10/22] Unify defaults interface across all config types
---
src/calibre/gui2/preferences/__init__.py | 105 ++++++++++++++++++-----
src/calibre/library/prefs.py | 6 +-
src/calibre/utils/config.py | 6 ++
3 files changed, 96 insertions(+), 21 deletions(-)
diff --git a/src/calibre/gui2/preferences/__init__.py b/src/calibre/gui2/preferences/__init__.py
index eabb33d03b..04ba9dd225 100644
--- a/src/calibre/gui2/preferences/__init__.py
+++ b/src/calibre/gui2/preferences/__init__.py
@@ -5,10 +5,10 @@ __license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal '
__docformat__ = 'restructuredtext en'
-from PyQt4.Qt import QWidget, pyqtSignal, QCheckBox
+from PyQt4.Qt import QWidget, pyqtSignal, QCheckBox, QAbstractSpinBox, \
+ QLineEdit, QComboBox, QVariant
from calibre.customize.ui import preferences_plugins
-from calibre.utils.config import DynamicConfig, XMLConfig
class ConfigWidgetInterface(object):
@@ -25,8 +25,11 @@ class ConfigWidgetInterface(object):
class Setting(object):
- def __init__(self, name, config_obj, widget, gui_name=None):
+ def __init__(self, name, config_obj, widget, gui_name=None,
+ empty_string_is_None=True, choices=None):
self.name, self.gui_name = name, gui_name
+ self.empty_string_is_None = empty_string_is_None
+ self.choices = choices
if gui_name is None:
self.gui_name = 'opt_'+name
self.config_obj = config_obj
@@ -36,37 +39,76 @@ class Setting(object):
self.datatype = 'bool'
self.gui_obj.stateChanged.connect(lambda x:
widget.changed_signal.emit())
+ elif isinstance(self.gui_obj, QAbstractSpinBox):
+ self.datatype = 'number'
+ self.gui_obj.valueChanged.connect(lambda x:
+ widget.changed_signal.emit())
+ elif isinstance(self.gui_obj, QLineEdit):
+ self.datatype = 'string'
+ self.gui_obj.textChanged.connect(lambda x:
+ widget.changed_signal.emit())
+ elif isinstance(self.gui_obj, QComboBox):
+ self.datatype = 'choice'
+ self.gui_obj.editTextChanged.connect(lambda x:
+ widget.changed_signal.emit())
+ self.gui_obj.currentIndexChanged.connect(lambda x:
+ widget.changed_signal.emit())
else:
raise ValueError('Unknown data type')
- if isinstance(config_obj, (DynamicConfig, XMLConfig)):
- self.config_type = 'dict'
- else:
- raise ValueError('Unknown config type')
-
def initialize(self):
- self.set_gui_val()
+ self.gui_obj.blockSignals(True)
+ if self.datatype == 'choices':
+ self.gui_obj.clear()
+ for x in self.choices:
+ if isinstance(x, basestring):
+ x = (x, x)
+ self.gui_obj.addItem(x[0], QVariant(x[1]))
+ self.set_gui_val(self.get_config_val(default=False))
+ self.gui_obj.blockSignals(False)
def commit(self):
- self.set_config_val()
+ self.set_config_val(self.get_gui_val())
def restore_defaults(self):
- self.set_gui_val(to_default=True)
+ self.set_gui_val(self.get_config_val(default=True))
- def set_gui_val(self, to_default=False):
- if self.config_type == 'dict':
- if to_default:
- val = self.config_obj.defaults[self.name]
- else:
- val = self.config_obj[self.name]
+ def get_config_val(self, default=False):
+ if default:
+ val = self.config_obj.defaults[self.name]
+ else:
+ val = self.config_obj[self.name]
+ return val
+
+ def set_config_val(self, val):
+ self.config_obj[self.name] = val
+
+ def set_gui_val(self, val):
if self.datatype == 'bool':
self.gui_obj.setChecked(bool(val))
+ elif self.datatype == 'number':
+ self.gui_obj.setValue(val)
+ elif self.datatype == 'string':
+ self.gui_obj.setText(val if val else '')
+ elif self.datatype == 'choices':
+ idx = self.gui_obj.findData(QVariant(val))
+ if idx == -1:
+ idx = 0
+ self.gui_obj.setCurrentIndex(idx)
- def set_config_val(self):
+ def get_gui_val(self):
if self.datatype == 'bool':
val = bool(self.gui_obj.isChecked())
- if self.config_type == 'dict':
- self.config_obj[self.name] = val
+ elif self.datatype == 'number':
+ val = self.gui_obj.value(val)
+ elif self.datatype == 'string':
+ val = unicode(self.gui_name.text()).strip()
+ if self.empty_string_is_None and not val:
+ val = None
+ elif self.datatype == 'choices':
+ idx = self.gui_obj.currentIndex()
+ val = unicode(self.gui_obj.itemData(idx).toString())
+ return val
class ConfigWidgetBase(QWidget, ConfigWidgetInterface):
@@ -77,6 +119,29 @@ class ConfigWidgetBase(QWidget, ConfigWidgetInterface):
QWidget.__init__(self, parent)
if hasattr(self, 'setupUi'):
self.setupUi(self)
+ self.settings = {}
+
+ def register(self, name, config_obj, widget, gui_name=None):
+ setting = Setting(name, config_obj, widget, gui_name=gui_name)
+ self.register_setting(setting)
+
+ def register_setting(self, setting):
+ self.settings[setting.name] = setting
+ return setting
+
+ def initialize(self):
+ for setting in self.settings.values():
+ setting.initialize()
+
+ def commit(self):
+ for setting in self.settings.values():
+ setting.commit()
+
+ def restore_defaults(self, *args):
+ for setting in self.settings.values():
+ setting.restore_defaults()
+
+
def get_plugin(category, name):
for plugin in preferences_plugins():
diff --git a/src/calibre/library/prefs.py b/src/calibre/library/prefs.py
index ff9733aaa3..b125fe9067 100644
--- a/src/calibre/library/prefs.py
+++ b/src/calibre/library/prefs.py
@@ -15,6 +15,7 @@ class DBPrefs(dict):
def __init__(self, db):
dict.__init__(self)
self.db = db
+ self.defaults = {}
for key, val in self.db.conn.get('SELECT key,val FROM preferences'):
val = self.raw_to_object(val)
dict.__setitem__(self, key, val)
@@ -28,7 +29,10 @@ class DBPrefs(dict):
return json.dumps(val, indent=2, default=to_json)
def __getitem__(self, key):
- return dict.__getitem__(self, key)
+ try:
+ return dict.__getitem__(self, key)
+ except KeyError:
+ return self.defaults[key]
def __delitem__(self, key):
dict.__delitem__(self, key)
diff --git a/src/calibre/utils/config.py b/src/calibre/utils/config.py
index 9fd1315caa..c001b6da36 100644
--- a/src/calibre/utils/config.py
+++ b/src/calibre/utils/config.py
@@ -194,6 +194,7 @@ class OptionSet(object):
def __init__(self, description=''):
self.description = description
+ self.defaults = {}
self.preferences = []
self.group_list = []
self.groups = {}
@@ -274,6 +275,7 @@ class OptionSet(object):
if pref in self.preferences:
raise ValueError('An option with the name %s already exists in this set.'%name)
self.preferences.append(pref)
+ self.defaults[name] = default
def option_parser(self, user_defaults=None, usage='', gui_mode=False):
parser = OptionParser(usage, gui_mode=gui_mode)
@@ -466,6 +468,10 @@ class ConfigProxy(object):
self.__config = config
self.__opts = None
+ @property
+ def defaults(self):
+ return self.__config.option_set.defaults
+
def refresh(self):
self.__opts = self.__config.parse()
From 364c6101ec221800e3b5fd288e25a5679fb27d05 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Wed, 25 Aug 2010 08:24:02 -0600
Subject: [PATCH 11/22] ...
---
src/calibre/ebooks/fb2/fb2ml.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/calibre/ebooks/fb2/fb2ml.py b/src/calibre/ebooks/fb2/fb2ml.py
index d57389445c..fb3c7f34f7 100644
--- a/src/calibre/ebooks/fb2/fb2ml.py
+++ b/src/calibre/ebooks/fb2/fb2ml.py
@@ -91,7 +91,7 @@ class FB2MLizer(object):
return u'\n%s' % etree.tostring(etree.fromstring(output), encoding=unicode, pretty_print=True)
def clean_text(self, text):
- text = re.sub('\s*
', '', text)
+ text = re.sub(r'\s*
', '', text)
return text
From 2635f836fbd262b272615395260c9fb4dde3da45 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Wed, 25 Aug 2010 10:02:13 -0600
Subject: [PATCH 12/22] Add/remove header wizard: When running on PDF input,
replace non breaking spaces with normal spaces, since it is hard to write
regexps to match non breaking spaces with the regex builder wizard.
---
src/calibre/ebooks/conversion/preprocess.py | 49 ++++++++++++++++-----
src/calibre/gui2/convert/regex_builder.py | 6 +--
2 files changed, 42 insertions(+), 13 deletions(-)
diff --git a/src/calibre/ebooks/conversion/preprocess.py b/src/calibre/ebooks/conversion/preprocess.py
index da652c1a38..973e508746 100644
--- a/src/calibre/ebooks/conversion/preprocess.py
+++ b/src/calibre/ebooks/conversion/preprocess.py
@@ -5,8 +5,7 @@ __license__ = 'GPL v3'
__copyright__ = '2009, Kovid Goyal '
__docformat__ = 'restructuredtext en'
-import functools
-import re
+import functools, re
from calibre import entity_to_unicode
@@ -73,7 +72,7 @@ def line_length(format, raw, percent):
'''
raw = raw.replace(' ', ' ')
if format == 'html':
- linere = re.compile('(?<=)', re.DOTALL)
+ linere = re.compile('(?<=
)', re.DOTALL)
elif format == 'pdf':
linere = re.compile('(?<=
).*?(?=
)', re.DOTALL)
lines = linere.findall(raw)
@@ -205,9 +204,6 @@ class HTMLPreProcessor(object):
# Remove gray background
(re.compile(r'
]+>'), lambda match : ''),
- # Remove non breaking spaces
- (re.compile(ur'\u00a0'), lambda match : ' '),
-
# Detect Chapters to match default XPATH in GUI
(re.compile(r'(?=<(/?br|p))(<(/?br|p)[^>]*)?>\s*(?P(<(i|b)>(<(i|b)>)?)?(.?Chapter|Epilogue|Prologue|Book|Part)\s*([\d\w-]+(\s\w+)?)?((i|b)>((i|b)>)?)?)?(br|p)[^>]*>\s*(?P(<(i|b)>)?\s*\w+(\s*\w+)?\s*((i|b)>)?\s*(?(br|p)[^>]*>))?', re.IGNORECASE), chap_head),
(re.compile(r'(?=<(/?br|p))(<(/?br|p)[^>]*)?>\s*(?P([A-Z \'"!]{5,})\s*(\d+|\w+)?)(?p[^>]*>|
]*>)\n?((?=()?\s*\w+(\s+\w+)?()?(
]*>|?p[^>]*>))((?P.*)(
]*>|?p[^>]*>)))?'), chap_head),
@@ -254,20 +250,27 @@ class HTMLPreProcessor(object):
def is_pdftohtml(self, src):
return '' in src[:1000]
- def __call__(self, html, remove_special_chars=None):
+ def __call__(self, html, remove_special_chars=None,
+ get_preprocess_html=False):
if remove_special_chars is not None:
html = remove_special_chars.sub('', html)
html = html.replace('\0', '')
+ is_pdftohtml = self.is_pdftohtml(html)
if self.is_baen(html):
rules = []
elif self.is_book_designer(html):
rules = self.BOOK_DESIGNER
- elif self.is_pdftohtml(html):
+ elif is_pdftohtml:
rules = self.PDFTOHTML
else:
rules = []
- if not self.extra_opts.keep_ligatures:
+ start_rules = []
+ if is_pdftohtml:
+ # Remove non breaking spaces
+ start_rules.append((re.compile(ur'\u00a0'), lambda match : ' '))
+
+ if not getattr(self.extra_opts, 'keep_ligatures', False):
html = _ligpat.sub(lambda m:LIGATURES[m.group()], html)
end_rules = []
@@ -299,9 +302,35 @@ class HTMLPreProcessor(object):
(re.compile(r'(?<=.{%i}[a-z\.,;:)\-IA])\s*(?P(i|b|u)>)?\s*()\s*(?=(<(i|b|u)>)?\s*[\w\d(])' % length, re.UNICODE), wrap_lines),
)
- for rule in self.PREPROCESS + rules + end_rules:
+ for rule in self.PREPROCESS + start_rules:
html = rule[0].sub(rule[1], html)
+ if get_preprocess_html:
+ return html
+
+ def dump(raw, where):
+ import os
+ dp = getattr(self.extra_opts, 'debug_pipeline', None)
+ if dp and os.path.exists(dp):
+ odir = os.path.join(dp, 'input')
+ if os.path.exists(odir):
+ odir = os.path.join(odir, where)
+ if not os.path.exists(odir):
+ os.makedirs(odir)
+ name, i = None, 0
+ while not name or os.path.exists(os.path.join(odir, name)):
+ i += 1
+ name = '%04d.html'%i
+ with open(os.path.join(odir, name), 'wb') as f:
+ f.write(raw.encode('utf-8'))
+
+ #dump(html, 'pre-preprocess')
+
+ for rule in rules + end_rules:
+ html = rule[0].sub(rule[1], html)
+
+ #dump(html, 'post-preprocess')
+
# Handle broken XHTML w/ SVG (ugh)
if 'svg:' in html and SVG_NS not in html:
html = html.replace(
diff --git a/src/calibre/gui2/convert/regex_builder.py b/src/calibre/gui2/convert/regex_builder.py
index b10772b86c..c5c8d84a88 100644
--- a/src/calibre/gui2/convert/regex_builder.py
+++ b/src/calibre/gui2/convert/regex_builder.py
@@ -14,7 +14,7 @@ from calibre.gui2.convert.regex_builder_ui import Ui_RegexBuilder
from calibre.gui2.convert.xexp_edit_ui import Ui_Form as Ui_Edit
from calibre.gui2 import error_dialog, choose_files
from calibre.ebooks.oeb.iterator import EbookIterator
-from calibre.ebooks.conversion.preprocess import convert_entities
+from calibre.ebooks.conversion.preprocess import HTMLPreProcessor
from calibre.gui2.dialogs.choose_format import ChooseFormatDialog
class RegexBuilder(QDialog, Ui_RegexBuilder):
@@ -91,10 +91,10 @@ class RegexBuilder(QDialog, Ui_RegexBuilder):
self.iterator = EbookIterator(pathtoebook)
self.iterator.__enter__(only_input_plugin=True)
text = [u'']
- ent_pat = re.compile(r'&(\S+?);')
+ preprocessor = HTMLPreProcessor(None, False)
for path in self.iterator.spine:
html = open(path, 'rb').read().decode('utf-8', 'replace')
- html = ent_pat.sub(convert_entities, html)
+ html = preprocessor(html, get_preprocess_html=True)
text.append(html)
self.preview.setPlainText('\n---\n'.join(text))
From 8432474472d0cf1fecee54a1bae861ef31028f55 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Wed, 25 Aug 2010 10:39:53 -0600
Subject: [PATCH 13/22] Update podofo in all binary builds to 0.8.2
---
setup/installer/linux/freeze.py | 2 +-
setup/installer/windows/notes.rst | 12 ++++++------
2 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/setup/installer/linux/freeze.py b/setup/installer/linux/freeze.py
index 49798b8c15..954033fb1b 100644
--- a/setup/installer/linux/freeze.py
+++ b/setup/installer/linux/freeze.py
@@ -48,7 +48,7 @@ class LinuxFreeze(Command):
'/usr/lib/libsqlite3.so.0',
'/usr/lib/libsqlite3.so.0',
'/usr/lib/libmng.so.1',
- '/usr/lib/libpodofo.so.0.8.1',
+ '/usr/lib/libpodofo.so.0.8.2',
'/lib/libz.so.1',
'/lib/libuuid.so.1',
'/usr/lib/libtiff.so.5',
diff --git a/setup/installer/windows/notes.rst b/setup/installer/windows/notes.rst
index 8f1cc80bb5..f41b7215b5 100644
--- a/setup/installer/windows/notes.rst
+++ b/setup/installer/windows/notes.rst
@@ -230,14 +230,14 @@ SET(WANT_LIB64 FALSE)
SET(PODOFO_BUILD_SHARED TRUE)
SET(PODOFO_BUILD_STATIC FALSE)
-cp build/podofo/build/src/Release/podofo.dll bin/
-cp build/podofo/build/src/Release/podofo.lib lib/
-cp build/podofo/build/src/Release/podofo.exp lib/
+cp build/podofo-*/build/src/Release/podofo.dll bin/
+cp build/podofo-*/build/src/Release/podofo.lib lib/
+cp build/podofo-*/build/src/Release/podofo.exp lib/
-cp build/podofo/build/podofo_config.h include/podofo/
-cp -r build/podofo/src/* include/podofo/
+cp build/podofo-*/build/podofo_config.h include/podofo/
+cp -r build/podofo-*/src/* include/podofo/
-You have to use >0.8.1 (>= revision 1269)
+You have to use >=0.8.2
The following patch (against -r1269) was required to get it to compile:
From d4a72194e4717c503a1b2a4095363197bb719c41 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Wed, 25 Aug 2010 11:14:35 -0600
Subject: [PATCH 14/22] ...
---
setup/installer/osx/app/main.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/setup/installer/osx/app/main.py b/setup/installer/osx/app/main.py
index 973473bc3f..565b5dd07d 100644
--- a/setup/installer/osx/app/main.py
+++ b/setup/installer/osx/app/main.py
@@ -401,7 +401,7 @@ class Py2App(object):
@flush
def add_podofo(self):
info('\nAdding PoDoFo')
- pdf = join(SW, 'lib', 'libpodofo.0.8.1.dylib')
+ pdf = join(SW, 'lib', 'libpodofo.0.8.2.dylib')
self.install_dylib(pdf)
@flush
From 6176b8d7ea67c8b14da707eae42aef31bb970c4e Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Wed, 25 Aug 2010 11:42:45 -0600
Subject: [PATCH 15/22] Add ZIP and RAR to the input format order preferences.
Fixes #5879 (Input Preference order to convert HTM(L) first is being ignored)
---
src/calibre/gui2/dialogs/config/__init__.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/calibre/gui2/dialogs/config/__init__.py b/src/calibre/gui2/dialogs/config/__init__.py
index 0976055218..1f04c50e19 100644
--- a/src/calibre/gui2/dialogs/config/__init__.py
+++ b/src/calibre/gui2/dialogs/config/__init__.py
@@ -357,7 +357,7 @@ class ConfigDialog(ResizableDialog, Ui_Dialog):
input_map = prefs['input_format_order']
all_formats = set()
- for fmt in all_input_formats():
+ for fmt in all_input_formats().union(set(['ZIP', 'RAR'])):
all_formats.add(fmt.upper())
for format in input_map + list(all_formats.difference(input_map)):
item = QListWidgetItem(format, self.input_order)
From dec671f75126780b394e203165ffad1cbb9adea8 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Wed, 25 Aug 2010 11:44:37 -0600
Subject: [PATCH 16/22] ...
---
src/calibre/utils/config.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/calibre/utils/config.py b/src/calibre/utils/config.py
index c001b6da36..432018e9b3 100644
--- a/src/calibre/utils/config.py
+++ b/src/calibre/utils/config.py
@@ -707,7 +707,7 @@ def _prefs():
c.add_opt('output_format', default='EPUB',
help=_('The default output format for ebook conversions.'))
c.add_opt('input_format_order', default=['EPUB', 'MOBI', 'LIT', 'PRC',
- 'FB2', 'HTML', 'HTM', 'XHTM', 'SHTML', 'XHTML', 'ODT', 'RTF', 'PDF',
+ 'FB2', 'HTML', 'HTM', 'XHTM', 'SHTML', 'XHTML', 'ZIP', 'ODT', 'RTF', 'PDF',
'TXT'],
help=_('Ordered list of formats to prefer for input.'))
c.add_opt('read_file_metadata', default=True,
From 6a572b9a41279af16a8fbce07c863a9d061a035c Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Wed, 25 Aug 2010 13:52:20 -0600
Subject: [PATCH 17/22] Create (almost) all temporary files in a subdirectory
so as not to clutter up temp directory
---
src/calibre/ptempfile.py | 38 ++++++++++++++++++++++++++++++++------
1 file changed, 32 insertions(+), 6 deletions(-)
diff --git a/src/calibre/ptempfile.py b/src/calibre/ptempfile.py
index b7b4d49509..ca7343ee9b 100644
--- a/src/calibre/ptempfile.py
+++ b/src/calibre/ptempfile.py
@@ -5,18 +5,36 @@ __copyright__ = '2008, Kovid Goyal '
Provides platform independent temporary files that persist even after
being closed.
"""
-import tempfile, os, atexit, shutil
+import tempfile, os, atexit
from calibre import __version__, __appname__
def cleanup(path):
try:
- import os
- if os.path.exists(path):
- os.remove(path)
+ import os as oss
+ if oss.path.exists(path):
+ oss.remove(path)
except:
pass
+
+_base_dir = None
+
+def remove_dir(x):
+ try:
+ import shutil
+ shutil.rmtree(x, ignore_errors=True)
+ except:
+ pass
+
+def base_dir():
+ global _base_dir
+ if _base_dir is None:
+ _base_dir = tempfile.mkdtemp(prefix='%s_%s_tmp_'%(__appname__,
+ __version__))
+ atexit.register(remove_dir, _base_dir)
+ return _base_dir
+
class PersistentTemporaryFile(object):
"""
A file-like object that is a temporary file that is available even after being closed on
@@ -27,6 +45,8 @@ class PersistentTemporaryFile(object):
def __init__(self, suffix="", prefix="", dir=None, mode='w+b'):
if prefix == None:
prefix = ""
+ if dir is None:
+ dir = base_dir()
fd, name = tempfile.mkstemp(suffix, __appname__+"_"+ __version__+"_" + prefix,
dir=dir)
self._file = os.fdopen(fd, mode)
@@ -56,8 +76,10 @@ def PersistentTemporaryDirectory(suffix='', prefix='', dir=None):
Return the path to a newly created temporary directory that will
be automatically deleted on application exit.
'''
+ if dir is None:
+ dir = base_dir()
tdir = tempfile.mkdtemp(suffix, __appname__+"_"+ __version__+"_" +prefix, dir)
- atexit.register(shutil.rmtree, tdir, True)
+ atexit.register(remove_dir, tdir)
return tdir
class TemporaryDirectory(object):
@@ -67,6 +89,8 @@ class TemporaryDirectory(object):
def __init__(self, suffix='', prefix='', dir=None, keep=False):
self.suffix = suffix
self.prefix = prefix
+ if dir is None:
+ dir = base_dir()
self.dir = dir
self.keep = keep
@@ -76,7 +100,7 @@ class TemporaryDirectory(object):
def __exit__(self, *args):
if not self.keep and os.path.exists(self.tdir):
- shutil.rmtree(self.tdir, ignore_errors=True)
+ remove_dir(self.tdir)
class TemporaryFile(object):
@@ -85,6 +109,8 @@ class TemporaryFile(object):
prefix = ''
if suffix is None:
suffix = ''
+ if dir is None:
+ dir = base_dir()
self.prefix, self.suffix, self.dir, self.mode = prefix, suffix, dir, mode
self._file = None
From 0916d15e8ab8f7247b53675461544279830092c6 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Wed, 25 Aug 2010 19:15:23 -0600
Subject: [PATCH 18/22] More work on new preferences infrastructure
---
src/calibre/customize/builtins.py | 22 +-
src/calibre/gui2/__init__.py | 5 +
src/calibre/gui2/layout.py | 6 +-
src/calibre/gui2/main.py | 2 +-
src/calibre/gui2/preferences/__init__.py | 13 +-
src/calibre/gui2/preferences/behavior.ui | 263 ++++++++++++++++++
.../gui2/preferences/custom_columns.ui | 170 +++++++++++
src/calibre/gui2/preferences/look_feel.py | 62 +++++
src/calibre/gui2/preferences/look_feel.ui | 196 +++++++++++++
src/calibre/gui2/ui.py | 5 +-
10 files changed, 731 insertions(+), 13 deletions(-)
create mode 100644 src/calibre/gui2/preferences/behavior.ui
create mode 100644 src/calibre/gui2/preferences/custom_columns.ui
create mode 100644 src/calibre/gui2/preferences/look_feel.py
create mode 100644 src/calibre/gui2/preferences/look_feel.ui
diff --git a/src/calibre/customize/builtins.py b/src/calibre/customize/builtins.py
index 183f83c047..f7bc29784e 100644
--- a/src/calibre/customize/builtins.py
+++ b/src/calibre/customize/builtins.py
@@ -5,7 +5,8 @@ __copyright__ = '2008, Kovid Goyal '
import textwrap
import os
import glob
-from calibre.customize import FileTypePlugin, MetadataReaderPlugin, MetadataWriterPlugin
+from calibre.customize import FileTypePlugin, MetadataReaderPlugin, \
+ MetadataWriterPlugin, PreferencesPlugin, InterfaceActionBase
from calibre.constants import numeric_version
from calibre.ebooks.metadata.archive import ArchiveExtract, get_cbz_metadata
@@ -577,7 +578,7 @@ plugins += [x for x in list(locals().values()) if isinstance(x, type) and \
x.__name__.endswith('MetadataWriter')]
plugins += input_profiles + output_profiles
-from calibre.customize import InterfaceActionBase
+# Interface Actions {{{
class ActionAdd(InterfaceActionBase):
name = 'Add Books'
@@ -670,3 +671,20 @@ plugins += [ActionAdd, ActionFetchAnnotations, ActionGenerateCatalog,
ActionSendToDevice, ActionHelp, ActionPreferences, ActionSimilarBooks,
ActionAddToLibrary, ActionEditCollections, ActionChooseLibrary,
ActionCopyToLibrary]
+
+# }}}
+
+# Preferences Plugins {{{
+
+class LookAndFeel(PreferencesPlugin):
+ name = 'Look & Feel'
+ gui_name = _('Look and Feel')
+ category = _('Interface')
+ category_order = 1
+ name_order = 1
+ config_widget = 'calibre.gui2.preferences.look_feel'
+
+plugins += [LookAndFeel]
+
+#}}}
+
diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py
index d75db1bae1..5ffd8d723a 100644
--- a/src/calibre/gui2/__init__.py
+++ b/src/calibre/gui2/__init__.py
@@ -46,6 +46,11 @@ gprefs.defaults['action-layout-context-menu-device'] = (
'View', 'Save To Disk', None, 'Remove Books', None,
'Add To Library', 'Edit Collections',
)
+
+gprefs.defaults['show_splash_screen'] = True
+gprefs.defaults['toolbar_icon_size'] = 'medium'
+gprefs.defaults['toolbar_text'] = 'auto'
+
# }}}
NONE = QVariant() #: Null value to return from the data function of item models
diff --git a/src/calibre/gui2/layout.py b/src/calibre/gui2/layout.py
index fccef29abe..9b755ccb97 100644
--- a/src/calibre/gui2/layout.py
+++ b/src/calibre/gui2/layout.py
@@ -219,11 +219,11 @@ class ToolBar(QToolBar): # {{{
self.preferred_width = self.sizeHint().width()
def apply_settings(self):
- sz = gprefs.get('toolbar_icon_size', 'medium')
+ sz = gprefs['toolbar_icon_size']
sz = {'small':24, 'medium':48, 'large':64}[sz]
self.setIconSize(QSize(sz, sz))
style = Qt.ToolButtonTextUnderIcon
- if gprefs.get('toolbar_text', 'auto') == 'never':
+ if gprefs['toolbar_text'] == 'never':
style = Qt.ToolButtonIconOnly
self.setToolButtonStyle(style)
self.donate_button.set_normal_icon_size(sz, sz)
@@ -265,7 +265,7 @@ class ToolBar(QToolBar): # {{{
def resizeEvent(self, ev):
QToolBar.resizeEvent(self, ev)
style = Qt.ToolButtonTextUnderIcon
- p = gprefs.get('toolbar_text', 'auto')
+ p = gprefs['toolbar_text']
if p == 'never':
style = Qt.ToolButtonIconOnly
diff --git a/src/calibre/gui2/main.py b/src/calibre/gui2/main.py
index f9d7d80b24..24ba7ef47c 100644
--- a/src/calibre/gui2/main.py
+++ b/src/calibre/gui2/main.py
@@ -241,7 +241,7 @@ class GuiRunner(QObject):
QApplication.instance().processEvents()
def initialize(self, *args):
- if gprefs.get('show_splash_screen', True):
+ if gprefs['show_splash_screen']:
self.show_splash_screen()
self.library_path = get_library_path(parent=self.splash_screen)
diff --git a/src/calibre/gui2/preferences/__init__.py b/src/calibre/gui2/preferences/__init__.py
index 04ba9dd225..abb5f76609 100644
--- a/src/calibre/gui2/preferences/__init__.py
+++ b/src/calibre/gui2/preferences/__init__.py
@@ -107,6 +107,7 @@ class Setting(object):
val = None
elif self.datatype == 'choices':
idx = self.gui_obj.currentIndex()
+ if idx < 0: idx = 0
val = unicode(self.gui_obj.itemData(idx).toString())
return val
@@ -121,8 +122,9 @@ class ConfigWidgetBase(QWidget, ConfigWidgetInterface):
self.setupUi(self)
self.settings = {}
- def register(self, name, config_obj, widget, gui_name=None):
- setting = Setting(name, config_obj, widget, gui_name=gui_name)
+ def register(self, name, config_obj, gui_name=None, choices=None, setting=Setting):
+ setting = setting(name, config_obj, self, gui_name=gui_name,
+ choices=choices)
self.register_setting(setting)
def register_setting(self, setting):
@@ -165,18 +167,19 @@ def test_widget(category, name, gui=None): # {{{
bb.button(bb.Apply).setEnabled(False)
w.changed_signal.connect(lambda : bb.button(bb.Apply).setEnable(True))
l = QVBoxLayout()
- pl.setLayout(l)
+ d.setLayout(l)
l.addWidget(w)
+ l.addWidget(bb)
if gui is None:
from calibre.gui2.ui import Main
from calibre.gui2.main import option_parser
- from calibre.library.db import db
+ from calibre.library import db
parser = option_parser()
opts, args = parser.parse_args([])
actions = tuple(Main.create_application_menubar())
db = db()
gui = Main(opts)
- gui.initialize(db.library_path, db, None, actions)
+ gui.initialize(db.library_path, db, None, actions, show_gui=False)
w.genesis(gui)
if d.exec_() == QDialog.Accepted:
w.commit()
diff --git a/src/calibre/gui2/preferences/behavior.ui b/src/calibre/gui2/preferences/behavior.ui
new file mode 100644
index 0000000000..09d83f29de
--- /dev/null
+++ b/src/calibre/gui2/preferences/behavior.ui
@@ -0,0 +1,263 @@
+
+
+ Form
+
+
+
+ 0
+ 0
+ 672
+ 563
+
+
+
+ Form
+
+
+ -
+
+
+ &Overwrite author and title by default when fetching metadata
+
+
+
+ -
+
+
+ Download &social metadata (tags/ratings/etc.) by default
+
+
+
+ -
+
+
+ Show notification when &new version is available
+
+
+
+ -
+
+
+ Automatically send downloaded &news to ebook reader
+
+
+
+ -
+
+
+ &Delete news from library when it is automatically sent to reader
+
+
+
+ -
+
+
-
+
+
+ Default network &timeout:
+
+
+ timeout
+
+
+
+ -
+
+
+ Set the default timeout for network fetches (i.e. anytime we go out to the internet to get information)
+
+
+ seconds
+
+
+ 2
+
+
+ 120
+
+
+ 5
+
+
+
+ -
+
+
+ QComboBox::AdjustToMinimumContentsLengthWithIcon
+
+
+ 20
+
+
-
+
+ Normal
+
+
+ -
+
+ High
+
+
+ -
+
+ Low
+
+
+
+
+ -
+
+
+ Job &priority:
+
+
+ priority
+
+
+
+ -
+
+
+ Preferred &output format:
+
+
+ output_format
+
+
+
+ -
+
+
+ QComboBox::AdjustToMinimumContentsLengthWithIcon
+
+
+ 10
+
+
+
+ -
+
+
+ Restriction to apply when the current library is opened:
+
+
+ opt_gui_restriction
+
+
+
+ -
+
+
+
+ 250
+ 16777215
+
+
+
+ Apply this restriction on calibre startup if the current library is being used. Also applied when switching to this library. Note that this setting is per library.
+
+
+ QComboBox::AdjustToMinimumContentsLengthWithIcon
+
+
+ 15
+
+
+
+
+
+ -
+
+
+ Reset all disabled &confirmation dialogs
+
+
+
+ -
+
+
+ Preferred &input format order:
+
+
+
-
+
+
-
+
+
+ true
+
+
+ QAbstractItemView::SelectRows
+
+
+
+ -
+
+
-
+
+
+ ...
+
+
+
+ :/images/arrow-up.svg:/images/arrow-up.svg
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+ -
+
+
+ ...
+
+
+
+ :/images/arrow-down.svg:/images/arrow-down.svg
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+ Use internal &viewer for:
+
+
+
-
+
+
+ true
+
+
+ QAbstractItemView::NoSelection
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/calibre/gui2/preferences/custom_columns.ui b/src/calibre/gui2/preferences/custom_columns.ui
new file mode 100644
index 0000000000..3f26838a07
--- /dev/null
+++ b/src/calibre/gui2/preferences/custom_columns.ui
@@ -0,0 +1,170 @@
+
+
+ Form
+
+
+
+ 0
+ 0
+ 504
+ 399
+
+
+
+ Form
+
+
+ -
+
+
+ Here you can re-arrange the layout of the columns in the calibre library book list. You can hide columns by unchecking them. You can also create your own, custom columns.
+
+
+ true
+
+
+
+ -
+
+
+ true
+
+
+ QAbstractItemView::SelectRows
+
+
+
+ -
+
+
-
+
+
+ ...
+
+
+
+ :/images/arrow-up.svg:/images/arrow-up.svg
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+ -
+
+
+ Remove a user-defined column
+
+
+ ...
+
+
+
+ :/images/minus.svg:/images/minus.svg
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+ -
+
+
+ Add a user-defined column
+
+
+ ...
+
+
+
+ :/images/plus.svg:/images/plus.svg
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+ -
+
+
+ Edit settings of a user-defined column
+
+
+ ...
+
+
+
+ :/images/edit_input.svg:/images/edit_input.svg
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+ -
+
+
+ ...
+
+
+
+ :/images/arrow-down.svg:/images/arrow-down.svg
+
+
+
+
+
+ -
+
+
+ Add &custom column
+
+
+
+
+
+
+
+
+
+
diff --git a/src/calibre/gui2/preferences/look_feel.py b/src/calibre/gui2/preferences/look_feel.py
new file mode 100644
index 0000000000..eb13dd5429
--- /dev/null
+++ b/src/calibre/gui2/preferences/look_feel.py
@@ -0,0 +1,62 @@
+#!/usr/bin/env python
+# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
+
+__license__ = 'GPL v3'
+__copyright__ = '2010, Kovid Goyal '
+__docformat__ = 'restructuredtext en'
+
+
+from calibre.gui2.preferences import ConfigWidgetBase, test_widget
+from calibre.gui2.preferences.look_feel_ui import Ui_Form
+from calibre.gui2 import config, gprefs
+from calibre.utils.localization import available_translations, \
+ get_language, get_lang
+from calibre.utils.config import prefs
+
+class ConfigWidget(ConfigWidgetBase, Ui_Form):
+
+ def genesis(self, gui):
+ self.gui = gui
+
+ r = self.register
+
+ r('gui_layout', config, choices=
+ [(_('Wide'), 'wide'), (_('Narrow'), 'narrow')])
+
+ r('cover_flow_queue_length', config)
+
+ lang = get_lang()
+ if lang is None or lang not in available_translations():
+ lang = 'en'
+ items = [(l, get_language(l)) for l in available_translations() \
+ if l != lang]
+ if lang != 'en':
+ items.append(('en', get_language('en')))
+ items.sort(cmp=lambda x, y: cmp(x[1], y[1]))
+ choices = [(y, x) for x, y in items]
+ # Default language is the autodetected one
+ choices = [get_language(lang), lang] + choices
+ r('language', prefs, choices=choices)
+
+ r('show_avg_rating', config)
+ r('disable_animations', config)
+ r('systray_icon', config)
+ r('show_splash_screen', gprefs)
+ r('disable_tray_notification', config)
+ r('use_roman_numerals_for_series_number', config)
+ r('separate_cover_flow', config)
+ r('search_as_you_type', config)
+
+ choices = [(_('Small'), 'small'), (_('Medium'), 'medium'),
+ (_('Large'), 'large')]
+ r('toolbar_icon_size', gprefs, choices=choices)
+
+ choices = [(_('Automatic'), 'auto'), (_('Always'), 'always'),
+ (_('Never'), 'never')]
+ r('toolbar_text', gprefs, choices=choices)
+
+if __name__ == '__main__':
+ from PyQt4.Qt import QApplication
+ app = QApplication([])
+ test_widget('Interface', 'Look & Feel')
+
diff --git a/src/calibre/gui2/preferences/look_feel.ui b/src/calibre/gui2/preferences/look_feel.ui
new file mode 100644
index 0000000000..7c6c736b24
--- /dev/null
+++ b/src/calibre/gui2/preferences/look_feel.ui
@@ -0,0 +1,196 @@
+
+
+ Form
+
+
+
+ 0
+ 0
+ 670
+ 385
+
+
+
+ Form
+
+
+ -
+
+
+ User Interface &layout (needs restart):
+
+
+ opt_gui_layout
+
+
+
+ -
+
+
+
+ 250
+ 16777215
+
+
+
+ QComboBox::AdjustToMinimumContentsLengthWithIcon
+
+
+ 20
+
+
+
+ -
+
+
+ &Number of covers to show in browse mode (needs restart):
+
+
+ opt_cover_flow_queue_length
+
+
+
+ -
+
+
+ -
+
+
+ Choose &language (requires restart):
+
+
+ opt_language
+
+
+
+ -
+
+
+ QComboBox::AdjustToMinimumContentsLengthWithIcon
+
+
+ 20
+
+
+
+ -
+
+
+ Show &average ratings in the tags browser
+
+
+ true
+
+
+
+ -
+
+
+ Disable all animations. Useful if you have a slow/old computer.
+
+
+ Disable &animations
+
+
+
+ -
+
+
+ Enable system &tray icon (needs restart)
+
+
+
+ -
+
+
+ Show &splash screen at startup
+
+
+
+ -
+
+
+ Disable ¬ifications in system tray
+
+
+
+ -
+
+
+ Use &Roman numerals for series
+
+
+ true
+
+
+
+ -
+
+
+ Show cover &browser in a separate window (needs restart)
+
+
+
+ -
+
+
+ Search as you type
+
+
+ true
+
+
+
+ -
+
+
+ &Toolbar
+
+
+
-
+
+
+ -
+
+
+ &Icon size:
+
+
+ opt_toolbar_icon_size
+
+
+
+ -
+
+
+ -
+
+
+ Show &text under icons:
+
+
+ opt_toolbar_text
+
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+
+
+
diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py
index c2635e8b44..f2cd7d5e7b 100644
--- a/src/calibre/gui2/ui.py
+++ b/src/calibre/gui2/ui.py
@@ -110,7 +110,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, # {{{
self.iactions = acmap
- def initialize(self, library_path, db, listener, actions):
+ def initialize(self, library_path, db, listener, actions, show_gui=True):
opts = self.opts
self.preferences_action, self.quit_action = actions
self.library_path = library_path
@@ -203,7 +203,8 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, # {{{
####################### Library view ########################
LibraryViewMixin.__init__(self, db)
- self.show()
+ if show_gui:
+ self.show()
if self.system_tray_icon.isVisible() and opts.start_in_tray:
self.hide_windows()
From 7a7e9d2f28cd70c78d6ca48c66d37cf33ec47f47 Mon Sep 17 00:00:00 2001
From: John Schember
Date: Thu, 26 Aug 2010 07:25:08 -0400
Subject: [PATCH 19/22] FB2 Output: Clean up output a bit more.
---
src/calibre/ebooks/fb2/fb2ml.py | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/calibre/ebooks/fb2/fb2ml.py b/src/calibre/ebooks/fb2/fb2ml.py
index d57389445c..11596b9df9 100644
--- a/src/calibre/ebooks/fb2/fb2ml.py
+++ b/src/calibre/ebooks/fb2/fb2ml.py
@@ -91,7 +91,9 @@ class FB2MLizer(object):
return u'\n%s' % etree.tostring(etree.fromstring(output), encoding=unicode, pretty_print=True)
def clean_text(self, text):
- text = re.sub('\s*
', '', text)
+ text = re.sub(r'(?miu)\s*
', '', text)
+ text = re.sub(r'(?miu)\s+
', '', text)
+ text = re.sub(r'(?miu)', '
\n\n', text)
return text
From 615008a65018947025fa73e8b3a62f58be3ecc1b Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Thu, 26 Aug 2010 11:42:50 -0600
Subject: [PATCH 20/22] Fix hidden donate button not really being hidden
---
src/calibre/gui2/layout.py | 4 +++-
src/calibre/gui2/ui.py | 3 ++-
2 files changed, 5 insertions(+), 2 deletions(-)
diff --git a/src/calibre/gui2/layout.py b/src/calibre/gui2/layout.py
index 9b755ccb97..4eccf597c2 100644
--- a/src/calibre/gui2/layout.py
+++ b/src/calibre/gui2/layout.py
@@ -232,6 +232,7 @@ class ToolBar(QToolBar): # {{{
pass
def build_bar(self):
+ self.showing_donate = False
showing_device = self.location_manager.has_device
actions = '-device' if showing_device else ''
actions = gprefs['action-layout-toolbar'+actions]
@@ -250,6 +251,7 @@ class ToolBar(QToolBar): # {{{
self.d_widget.setLayout(QVBoxLayout())
self.d_widget.layout().addWidget(self.donate_button)
self.addWidget(self.d_widget)
+ self.showing_donate = True
elif what in self.gui.iactions:
action = self.gui.iactions[what]
self.addAction(action.qaction)
@@ -292,7 +294,7 @@ class MainWindowMixin(object): # {{{
self._central_widget_layout = QVBoxLayout()
self.centralwidget.setLayout(self._central_widget_layout)
self.resize(1012, 740)
- self.donate_button = ThrobbingButton(self.centralwidget)
+ self.donate_button = ThrobbingButton()
self.location_manager = LocationManager(self)
self.iactions['Fetch News'].init_scheduler(db)
diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py
index f2cd7d5e7b..c42cf7d6c3 100644
--- a/src/calibre/gui2/ui.py
+++ b/src/calibre/gui2/ui.py
@@ -252,7 +252,8 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, # {{{
self.read_settings()
self.finalize_layout()
- self.donate_button.start_animation()
+ if self.tool_bar.showing_donate:
+ self.donate_button.start_animation()
self.set_window_title()
for ac in self.iactions.values():
From db045466c3a05d607586467158ac67bf0149a1e3 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Thu, 26 Aug 2010 13:23:49 -0600
Subject: [PATCH 21/22] Kindle 3 driver and additions to User Manual
---
src/calibre/devices/kindle/driver.py | 6 +--
src/calibre/manual/faq.rst | 16 +++++-
src/calibre/manual/portable.rst | 74 ++++++++++++++++++++++++++++
3 files changed, 92 insertions(+), 4 deletions(-)
create mode 100644 src/calibre/manual/portable.rst
diff --git a/src/calibre/devices/kindle/driver.py b/src/calibre/devices/kindle/driver.py
index 98e16bd207..101d168ae4 100644
--- a/src/calibre/devices/kindle/driver.py
+++ b/src/calibre/devices/kindle/driver.py
@@ -165,11 +165,11 @@ class KINDLE(USBMS):
class KINDLE2(KINDLE):
- name = 'Kindle 2 Device Interface'
- description = _('Communicate with the Kindle 2 eBook reader.')
+ name = 'Kindle 2/3 Device Interface'
+ description = _('Communicate with the Kindle 2/3 eBook reader.')
FORMATS = KINDLE.FORMATS + ['pdf']
- PRODUCT_ID = [0x0002]
+ PRODUCT_ID = [0x0002, 0x0004]
BCD = [0x0100]
def books(self, oncard=None, end_session=True):
diff --git a/src/calibre/manual/faq.rst b/src/calibre/manual/faq.rst
index deb693c199..efc05a3f38 100644
--- a/src/calibre/manual/faq.rst
+++ b/src/calibre/manual/faq.rst
@@ -81,7 +81,7 @@ Device Integration
What devices does |app| support?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-At the moment |app| has full support for the SONY PRS 300/500/505/600/700/900, Barnes & Noble Nook, Cybook Gen 3/Opus, Amazon Kindle 1/2/DX, Entourage Edge, Longshine ShineBook, Ectaco Jetbook, BeBook/BeBook Mini, Irex Illiad/DR1000, Foxit eSlick, PocketBook 360, Italica, eClicto, Iriver Story, Airis dBook, Hanvon N515, Binatone Readme, Teclast K3, SpringDesign Alex, Kobo Reader, various Android phones and the iPhone/iPad. In addition, using the :guilabel:`Save to disk` function you can use it with any ebook reader that exports itself as a USB disk.
+At the moment |app| has full support for the SONY PRS 300/500/505/600/700/900, Barnes & Noble Nook, Cybook Gen 3/Opus, Amazon Kindle 1/2/3/DX/DXG, Entourage Edge, Longshine ShineBook, Ectaco Jetbook, BeBook/BeBook Mini, Irex Illiad/DR1000, Foxit eSlick, PocketBook 360, Italica, eClicto, Iriver Story, Airis dBook, Hanvon N515, Binatone Readme, Teclast K3, SpringDesign Alex, Kobo Reader, various Android phones and the iPhone/iPad. In addition, using the :guilabel:`Save to disk` function you can use it with any ebook reader that exports itself as a USB disk.
How can I help get my device supported in |app|?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -338,6 +338,16 @@ Why does |app| show only some of my fonts on OS X?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
There can be several causes for this:
+ * If you get an error about calibre not being able to open a file because it is in use by another program, do the following:
+
+ * Uninstall calibre
+ * Reboot your computer
+ * Re-install calibre. But do not start calibre from the installation wizard.
+ * Temporarily disable your antivirus program (disconnect from the internet before doing so, to be safe)
+ * Look inside the folder you chose for your calibre library. If you see a file named metadata.db, delete it.
+ * Start calibre
+ * From now on you should be able to start calibre normally.
+
* If you get an error about a Python function terminating unexpectedly after upgrading calibre, first uninstall calibre, then delete the folders (if they exists)
:file:`C:\\Program Files\\Calibre` and :file:`C:\\Program Files\\Calibre2`. Now re-install and you should be fine.
* If you get an error in the welcome wizard on an initial run of calibre, try choosing a folder like :file:`C:\\library` as the calibre library (calibre sometimes
@@ -394,4 +404,8 @@ Can I include |app| on a CD to be distributed with my product/magazine?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|app| is licensed under the GNU General Public License v3 (an open source license). This means that you are free to redistribute |app| as long as you make the source code available. So if you want to put |app| on a CD with your product, you must also put the |app| source code on the CD. The source code is available for download `from googlecode `_.
+How do I run calibre from my USB stick?
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+A portable version of calibre is available at: `portableapps.com `_. However, this is usually out of date. You can also setup your own portable calibre install by following :ref:`these instructions `.
diff --git a/src/calibre/manual/portable.rst b/src/calibre/manual/portable.rst
new file mode 100644
index 0000000000..2a88107842
--- /dev/null
+++ b/src/calibre/manual/portable.rst
@@ -0,0 +1,74 @@
+
+.. include:: global.rst
+
+.. _portablecalibre:
+
+Creating your own portable/customized calibre install
+=======================================================
+
+You can "install" calibre onto a USB stick that you can take with you and use on any computer. The magic is in a .bat file called calibre-portable.bat found in the resources folder in your calibre install. Typical uses of this files are:
+
+ * Run a Mobile Calibre installation with both the Calibre binaries and your ebook library resident on a USB disk or other portable media. In particular it is not necessary to have Calibre installed on the Windows PC that is to run Calibre. This batch file also does not care what drive letter is assigned when you plug in the USB device. It also will not affect any settings on the host machine being a completely self-contained Calibre installation.
+ * Run a networked Calibre installation optimised for performance when the ebook files are located on a networked share.
+
+
+This calibre-portable.bat file is intended for use on Windows based systems, but the principles are easily adapted for use on Linux or OS X based systems. Note that calibre requires the Microsoft Visual C++ 2008 runtimes to run. Most windows computers have them installed already, but it may be a good idea to have the installer for installing them on your USB stick. The installer is available from `Microsoft `_.
+
+Assumptions
+------------
+
+The calibre-portable.bat file makes the following assumptions about the folder layout that is being used::
+
+ Calibre_Root_Folder
+ calibre-portable.bat The batch file used to start Calibre
+ Calibre2 The Calibre binaries
+ CalibreLibrary The Calibre Library
+ CalibreConfig The Calibre user preferences
+ CalibreSource The calibre source (optional) if running a development environment.
+
+If you want to use a different folder layout then the calibre-portable.bat file will need editing appropriately. This file can be edited using any appropriate text editor.
+
+Preparation
+------------
+
+The steps required to prepare the USB stick are as follows:
+
+ * Decide what folder will be used to hold all the Calibre related files. If the portable media is to be dedicated to Calibre use then this can be the root folder, but if not is suggested that a folder called Calibre should be created – this will then be the Calibre_Root_Folder mentioned above and in the following steps.
+ * Copy the calibre-portable.bat file into the Calibre_Root_Folder.
+ * Create the Calibre2 folder inside the Calibre_Root_Folder to hold the Calibre binaries. There are 2 ways of populating the contents of this folder:
+
+ * The easiest is to simply copy an existing Calibre installation. Typically this would involve copying the contents of the C:\Program Files\Calibre2 folder
+ * Run the Calibre Windows installer:
+
+ * Tick the box to accept the GNU GPL license
+ * Select the Advanced option
+ * Change the install location to be the Calibre2 folder on the USB drive
+ * Deselect the options for creating Menu shortcuts; creating a calibre shortcut on the desktop; and adding Calibre to the path
+
+ * Create the CalibreLibrary folder inside the Calibre_Root_Folder. If you have an existing Calibre library copy it and all its contents to the CalibreLibrary folder. If you do not already have a library do not worry as a new one will be created at this location when Calibre is started.
+ * Create the CalibreConfig folder inside the Calibre_Root_Folder. This will hold your personal Calibre configuration settings. If you have an existing Calibre installation and want to copy the current settings then copy the contents of your current configuration folder to the CalibreConfig folder. You can find the location of your current configuration folder by going to Preferences->Advanced and clicking the “Open calibre configuration Directory” button.
+ * When you have started Calibre, go into Preferences->General and check that you have set the Job Priority to ‘Low’. This setting keeps single-processor Windows systems responsive without affecting Calibre performance to any noticeable degree. On multi-processor or multi-core systems this setting does not matter as much, but setting it will do no harm.
+
+Using calibre-portable.bat
+---------------------------
+
+Once you have got the USB stick set up then the steps to use Calibre are:
+
+ * Plug the USB stick into the host machine
+ * Use Windows Explorer to navigate to the location of the calibre-portable.bat file on the USB stick
+ * Start Calibre by double-clicking the calibre-portable.bat file
+ * A Command Window will be opened showing the settings that are about to be used. If you are not happy with these setting use CTRL-C to abandon the batch file without starting Calibre. If you are happy then press any other key to launch Calibre with the specified settings. Once you are happy with your setup you may wish to edit the calibre-portable.bat file to eliminate this pause (add REM to the start of the line) but it a useful check that you are running with the expected settings.
+
+Networked Installations
+--------------------------
+
+The performance of Calibre can be severely degraded if running with the Calibre library on a network share. This is primarily due to the fact that the access to the metadata.db file is slow across a network. The calibre-portable.bat file is designed to help in such scenarios. To use the calibre-portable.bat file in such a scenario the following deviations from those detailed above for the Mobile Calibre installation are needed:
+
+ * Edit the calibre-portable.bat file to specify the location of your Calibre library on the network.
+ * Create a CalibreMetadata folder in the Calibre_Root_Folder location. If you have an existing Calibre library then copy the metadata.db files from there to the CalibreMetadata folder.
+ * You can now run Calibre using the calibre-portable.bat file as specified in the previous section. One thing you should remember is to periodically copy the metadata.db file from the CalibreMetadatqa folder back to your Calibre library located on the network share.
+
+Precautions
+--------------
+
+Portable media can occasionally fail so you should make periodic backups of you Calibre library. This can be done by making a copy of the CalibreLibrary folder and all its contents. There are many freely available tools around that can optimise such back processes, well known ones being RoboCopy and RichCopy. However you can simply use a Windows copy facility if you cannot be bothered to use a specialised tools.
From ead6a72a46a5ee3a0700017862516b1a8497b5f4 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Thu, 26 Aug 2010 13:25:29 -0600
Subject: [PATCH 22/22] The TMZ by Tony Stegall
---
resources/recipes/tmz.recipe | 67 ++++++++++++++++++++++++++++++++++++
1 file changed, 67 insertions(+)
create mode 100644 resources/recipes/tmz.recipe
diff --git a/resources/recipes/tmz.recipe b/resources/recipes/tmz.recipe
new file mode 100644
index 0000000000..8c24fe0c99
--- /dev/null
+++ b/resources/recipes/tmz.recipe
@@ -0,0 +1,67 @@
+from calibre.web.feeds.news import BasicNewsRecipe
+
+class AdvancedUserRecipe1282101454(BasicNewsRecipe):
+ title = 'The TMZ'
+ __author__ = 'Tony Stegall'
+ description = 'Celeb Gossip and News'
+ language = 'en'
+ publisher = 'The TMZ'
+ category = 'news, celebrity, USA'
+ oldest_article = 1
+ max_articles_per_feed = 100
+ no_stylesheets = True
+ remove_javascript = True
+ masthead_url = 'http://t0.gstatic.com/images?q=tbn:t43QkABe_BmaWM:http://www.thetreymoore.com/logos/TMZ%20logo%20(crop).JPG'
+
+ 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;}
+ '''
+
+ remove_tags = [
+ dict(name='div' , attrs={'id':['sidebar','print-actions'] })
+
+ ]
+
+
+
+
+ feeds = [
+ ('TOP 20', 'http://www.tmz.com/rss.xml'),
+ ('Exclusives', 'http://www.tmz.com/category/exclusives/rss.xml'),
+ ('Celeb Justice', 'http://www.tmz.com/category/celebrity-justice/rss.xml'),
+ ('Celeb Feuds', 'http://www.tmz.com/category/celebrity-feuds/rss.xml'),
+ ('Politix', 'http://www.tmz.com/category/politix/rss.xml'),
+ ('Music', 'http://www.tmz.com/category/music/rss.xml'),
+ ('Movies', 'http://www.tmz.com/category/movies/rss.xml'),
+ ('TV', 'http://www.tmz.com/category/tv/rss.xml'),
+ ('Sports', 'http://www.tmz.com/category/TMZsports/rss.xml'),
+ ('Hook-Ups', 'http://www.tmz.com/category/hook-ups/rss.xml'),
+ ('Beauty', 'http://www.tmz.com/category/beauty/rss.xml'),
+ ('Fashion', 'http://www.tmz.com/category/fashion/rss.xml'),
+ ('Gossip & Rumor', 'http://www.tmz.com/category/gossip-rumors/rss.xml'),
+ ('Hot Mama', 'http://www.tmz.com/category/hot-mamas/rss.xml'),
+ ('Party All The Time', 'http://www.tmz.com/category/party-all-the-time/rss.xml'),
+ ('Ride Me!', 'http://www.tmz.com/category/ride-me/rss.xml'),
+ ('Stars in Heat', 'http://www.tmz.com/category/stars-in-heat/rss.xml'),
+ ('Vegas', 'http://www.tmz.com/category/hot-vegas/rss.xml')
+ ]
+
+ def print_version(self, url):
+ print_url = url +'print'
+ return print_url
+
+
+
+
+
+
+
+
+
+
+
+
+