From 238b06dfc494d5daa66acfebee0668b9091e33fc Mon Sep 17 00:00:00 2001 From: Timothy Legge Date: Tue, 24 Aug 2010 21:40:31 -0300 Subject: [PATCH 01/14] 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 02/14] 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 03/14] ... --- 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 04/14] 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 05/14] 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 06/14] 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 07/14] ... --- 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 08/14] 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+)?)?(()?)?)]*>\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<chap>([A-Z \'"!]{5,})\s*(\d+|\w+)?)(</?p[^>]*>|<br[^>]*>)\n?((?=(<i>)?\s*\w+(\s+\w+)?(</i>)?(<br[^>]*>|</?p[^>]*>))((?P<title>.*)(<br[^>]*>|</?p[^>]*>)))?'), chap_head), @@ -254,20 +250,27 @@ class HTMLPreProcessor(object): def is_pdftohtml(self, src): return '<!-- created by calibre\'s pdftohtml -->' 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<ital></(i|b|u)>)?\s*(<p.*?>)\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 <kovid@kovidgoyal.net> Date: Wed, 25 Aug 2010 10:39:53 -0600 Subject: [PATCH 09/14] 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 <kovid@kovidgoyal.net> Date: Wed, 25 Aug 2010 11:14:35 -0600 Subject: [PATCH 10/14] ... --- 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 <kovid@kovidgoyal.net> Date: Wed, 25 Aug 2010 11:42:45 -0600 Subject: [PATCH 11/14] 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 <kovid@kovidgoyal.net> Date: Wed, 25 Aug 2010 11:44:37 -0600 Subject: [PATCH 12/14] ... --- 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 <kovid@kovidgoyal.net> Date: Wed, 25 Aug 2010 13:52:20 -0600 Subject: [PATCH 13/14] 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 <kovid at kovidgoyal.net>' 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 <kovid@kovidgoyal.net> Date: Wed, 25 Aug 2010 19:15:23 -0600 Subject: [PATCH 14/14] 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 <kovid at kovidgoyal.net>' 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 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>Form</class> + <widget class="QWidget" name="Form"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>672</width> + <height>563</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0" colspan="2"> + <widget class="QCheckBox" name="opt_overwrite_author_title_metadata"> + <property name="text"> + <string>&Overwrite author and title by default when fetching metadata</string> + </property> + </widget> + </item> + <item row="1" column="0" colspan="2"> + <widget class="QCheckBox" name="opt_get_social_metadata"> + <property name="text"> + <string>Download &social metadata (tags/ratings/etc.) by default</string> + </property> + </widget> + </item> + <item row="2" column="0" colspan="2"> + <widget class="QCheckBox" name="new_version_notification"> + <property name="text"> + <string>Show notification when &new version is available</string> + </property> + </widget> + </item> + <item row="3" column="0" colspan="2"> + <widget class="QCheckBox" name="sync_news"> + <property name="text"> + <string>Automatically send downloaded &news to ebook reader</string> + </property> + </widget> + </item> + <item row="4" column="0" colspan="2"> + <widget class="QCheckBox" name="delete_news"> + <property name="text"> + <string>&Delete news from library when it is automatically sent to reader</string> + </property> + </widget> + </item> + <item row="5" column="0" colspan="2"> + <layout class="QGridLayout" name="gridLayout_2"> + <item row="1" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Default network &timeout:</string> + </property> + <property name="buddy"> + <cstring>timeout</cstring> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QSpinBox" name="timeout"> + <property name="toolTip"> + <string>Set the default timeout for network fetches (i.e. anytime we go out to the internet to get information)</string> + </property> + <property name="suffix"> + <string> seconds</string> + </property> + <property name="minimum"> + <number>2</number> + </property> + <property name="maximum"> + <number>120</number> + </property> + <property name="value"> + <number>5</number> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QComboBox" name="priority"> + <property name="sizeAdjustPolicy"> + <enum>QComboBox::AdjustToMinimumContentsLengthWithIcon</enum> + </property> + <property name="minimumContentsLength"> + <number>20</number> + </property> + <item> + <property name="text"> + <string>Normal</string> + </property> + </item> + <item> + <property name="text"> + <string>High</string> + </property> + </item> + <item> + <property name="text"> + <string>Low</string> + </property> + </item> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="priority_label"> + <property name="text"> + <string>Job &priority:</string> + </property> + <property name="buddy"> + <cstring>priority</cstring> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="label_23"> + <property name="text"> + <string>Preferred &output format:</string> + </property> + <property name="buddy"> + <cstring>output_format</cstring> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QComboBox" name="output_format"> + <property name="sizeAdjustPolicy"> + <enum>QComboBox::AdjustToMinimumContentsLengthWithIcon</enum> + </property> + <property name="minimumContentsLength"> + <number>10</number> + </property> + </widget> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="label_170"> + <property name="text"> + <string>Restriction to apply when the current library is opened:</string> + </property> + <property name="buddy"> + <cstring>opt_gui_restriction</cstring> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QComboBox" name="opt_gui_restriction"> + <property name="maximumSize"> + <size> + <width>250</width> + <height>16777215</height> + </size> + </property> + <property name="toolTip"> + <string>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. </string> + </property> + <property name="sizeAdjustPolicy"> + <enum>QComboBox::AdjustToMinimumContentsLengthWithIcon</enum> + </property> + <property name="minimumContentsLength"> + <number>15</number> + </property> + </widget> + </item> + </layout> + </item> + <item row="6" column="0" colspan="2"> + <widget class="QPushButton" name="reset_confirmation_button"> + <property name="text"> + <string>Reset all disabled &confirmation dialogs</string> + </property> + </widget> + </item> + <item row="7" column="0"> + <widget class="QGroupBox" name="groupBox_5"> + <property name="title"> + <string>Preferred &input format order:</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_11"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_10"> + <item> + <widget class="QListWidget" name="input_order"> + <property name="alternatingRowColors"> + <bool>true</bool> + </property> + <property name="selectionBehavior"> + <enum>QAbstractItemView::SelectRows</enum> + </property> + </widget> + </item> + <item> + <layout class="QVBoxLayout" name="verticalLayout_10"> + <item> + <widget class="QToolButton" name="input_up"> + <property name="text"> + <string>...</string> + </property> + <property name="icon"> + <iconset resource="../../../../resources/images.qrc"> + <normaloff>:/images/arrow-up.svg</normaloff>:/images/arrow-up.svg</iconset> + </property> + </widget> + </item> + <item> + <spacer name="verticalSpacer_5"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QToolButton" name="input_down"> + <property name="text"> + <string>...</string> + </property> + <property name="icon"> + <iconset resource="../../../../resources/images.qrc"> + <normaloff>:/images/arrow-down.svg</normaloff>:/images/arrow-down.svg</iconset> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item row="7" column="1"> + <widget class="QGroupBox" name="groupBox_3"> + <property name="title"> + <string>Use internal &viewer for:</string> + </property> + <layout class="QGridLayout" name="gridLayout_4"> + <item row="0" column="0"> + <widget class="QListWidget" name="viewer"> + <property name="alternatingRowColors"> + <bool>true</bool> + </property> + <property name="selectionMode"> + <enum>QAbstractItemView::NoSelection</enum> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + <resources> + <include location="../../../../resources/images.qrc"/> + </resources> + <connections/> +</ui> 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 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>Form</class> + <widget class="QWidget" name="Form"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>504</width> + <height>399</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0" colspan="2"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>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.</string> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QListWidget" name="columns"> + <property name="alternatingRowColors"> + <bool>true</bool> + </property> + <property name="selectionBehavior"> + <enum>QAbstractItemView::SelectRows</enum> + </property> + </widget> + </item> + <item row="1" column="1"> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <item> + <widget class="QToolButton" name="column_up"> + <property name="text"> + <string>...</string> + </property> + <property name="icon"> + <iconset resource="../../../../resources/images.qrc"> + <normaloff>:/images/arrow-up.svg</normaloff>:/images/arrow-up.svg</iconset> + </property> + </widget> + </item> + <item> + <spacer name="verticalSpacer_2"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QToolButton" name="del_custcol_button"> + <property name="toolTip"> + <string>Remove a user-defined column</string> + </property> + <property name="text"> + <string>...</string> + </property> + <property name="icon"> + <iconset resource="../../../../resources/images.qrc"> + <normaloff>:/images/minus.svg</normaloff>:/images/minus.svg</iconset> + </property> + </widget> + </item> + <item> + <spacer name="verticalSpacer_3"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QToolButton" name="add_custcol_button"> + <property name="toolTip"> + <string>Add a user-defined column</string> + </property> + <property name="text"> + <string>...</string> + </property> + <property name="icon"> + <iconset resource="../../../../resources/images.qrc"> + <normaloff>:/images/plus.svg</normaloff>:/images/plus.svg</iconset> + </property> + </widget> + </item> + <item> + <spacer name="verticalSpacer_4"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QToolButton" name="edit_custcol_button"> + <property name="toolTip"> + <string>Edit settings of a user-defined column</string> + </property> + <property name="text"> + <string>...</string> + </property> + <property name="icon"> + <iconset resource="../../../../resources/images.qrc"> + <normaloff>:/images/edit_input.svg</normaloff>:/images/edit_input.svg</iconset> + </property> + </widget> + </item> + <item> + <spacer name="verticalSpacer_5"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QToolButton" name="column_down"> + <property name="text"> + <string>...</string> + </property> + <property name="icon"> + <iconset resource="../../../../resources/images.qrc"> + <normaloff>:/images/arrow-down.svg</normaloff>:/images/arrow-down.svg</iconset> + </property> + </widget> + </item> + </layout> + </item> + <item row="2" column="0" colspan="2"> + <widget class="QPushButton" name="pushButton"> + <property name="text"> + <string>Add &custom column</string> + </property> + </widget> + </item> + </layout> + </widget> + <resources> + <include location="../../../../resources/images.qrc"/> + </resources> + <connections/> +</ui> 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 <kovid@kovidgoyal.net>' +__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 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>Form</class> + <widget class="QWidget" name="Form"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>670</width> + <height>385</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QGridLayout" name="gridLayout_2"> + <item row="0" column="0"> + <widget class="QLabel" name="label_17"> + <property name="text"> + <string>User Interface &layout (needs restart):</string> + </property> + <property name="buddy"> + <cstring>opt_gui_layout</cstring> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QComboBox" name="opt_gui_layout"> + <property name="maximumSize"> + <size> + <width>250</width> + <height>16777215</height> + </size> + </property> + <property name="sizeAdjustPolicy"> + <enum>QComboBox::AdjustToMinimumContentsLengthWithIcon</enum> + </property> + <property name="minimumContentsLength"> + <number>20</number> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_6"> + <property name="text"> + <string>&Number of covers to show in browse mode (needs restart):</string> + </property> + <property name="buddy"> + <cstring>opt_cover_flow_queue_length</cstring> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QSpinBox" name="opt_cover_flow_queue_length"/> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_7"> + <property name="text"> + <string>Choose &language (requires restart):</string> + </property> + <property name="buddy"> + <cstring>opt_language</cstring> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QComboBox" name="opt_language"> + <property name="sizeAdjustPolicy"> + <enum>QComboBox::AdjustToMinimumContentsLengthWithIcon</enum> + </property> + <property name="minimumContentsLength"> + <number>20</number> + </property> + </widget> + </item> + <item row="3" column="0"> + <widget class="QCheckBox" name="opt_show_avg_rating"> + <property name="text"> + <string>Show &average ratings in the tags browser</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QCheckBox" name="opt_disable_animations"> + <property name="toolTip"> + <string>Disable all animations. Useful if you have a slow/old computer.</string> + </property> + <property name="text"> + <string>Disable &animations</string> + </property> + </widget> + </item> + <item row="4" column="0"> + <widget class="QCheckBox" name="opt_systray_icon"> + <property name="text"> + <string>Enable system &tray icon (needs restart)</string> + </property> + </widget> + </item> + <item row="4" column="1"> + <widget class="QCheckBox" name="opt_show_splash_screen"> + <property name="text"> + <string>Show &splash screen at startup</string> + </property> + </widget> + </item> + <item row="5" column="0"> + <widget class="QCheckBox" name="opt_disable_tray_notification"> + <property name="text"> + <string>Disable &notifications in system tray</string> + </property> + </widget> + </item> + <item row="5" column="1"> + <widget class="QCheckBox" name="opt_use_roman_numerals_for_series_number"> + <property name="text"> + <string>Use &Roman numerals for series</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="6" column="0"> + <widget class="QCheckBox" name="opt_separate_cover_flow"> + <property name="text"> + <string>Show cover &browser in a separate window (needs restart)</string> + </property> + </widget> + </item> + <item row="6" column="1"> + <widget class="QCheckBox" name="opt_search_as_you_type"> + <property name="text"> + <string>Search as you type</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="7" column="0" colspan="2"> + <widget class="QGroupBox" name="groupBox_2"> + <property name="title"> + <string>&Toolbar</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="1"> + <widget class="QComboBox" name="opt_toolbar_icon_size"/> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>&Icon size:</string> + </property> + <property name="buddy"> + <cstring>opt_toolbar_icon_size</cstring> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QComboBox" name="opt_toolbar_text"/> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>Show &text under icons:</string> + </property> + <property name="buddy"> + <cstring>opt_toolbar_text</cstring> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item row="8" column="0" colspan="2"> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> 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()