diff --git a/src/calibre/customize/__init__.py b/src/calibre/customize/__init__.py index 083bc03723..57e4758906 100644 --- a/src/calibre/customize/__init__.py +++ b/src/calibre/customize/__init__.py @@ -294,7 +294,7 @@ class CatalogPlugin(Plugin): # {{{ # Return a list of requested fields, with opts.sort_by first all_fields = set( ['author_sort','authors','comments','cover','formats', - 'id','isbn','pubdate','publisher','rating', + 'id','isbn','ondevice','pubdate','publisher','rating', 'series_index','series','size','tags','timestamp', 'title','uuid']) @@ -306,6 +306,9 @@ class CatalogPlugin(Plugin): # {{{ else: fields = list(all_fields) + if not opts.connected_device['is_device_connected'] and 'ondevice' in fields: + fields.pop(int(fields.index('ondevice'))) + fields.sort() if opts.sort_by and opts.sort_by in fields: fields.insert(0,fields.pop(int(fields.index(opts.sort_by)))) diff --git a/src/calibre/gui2/actions/catalog.py b/src/calibre/gui2/actions/catalog.py index 74d3874c1e..77fa4755c1 100644 --- a/src/calibre/gui2/actions/catalog.py +++ b/src/calibre/gui2/actions/catalog.py @@ -34,11 +34,10 @@ class GenerateCatalogAction(InterfaceAction): db = self.gui.library_view.model().db dbspec = {} for id in ids: - dbspec[id] = {'ondevice':db.ondevice(id, index_is_id=True)} - db.catalog_plugin_on_device_temp_mapping = dbspec + dbspec[id] = {'ondevice': db.ondevice(id, index_is_id=True)} # Calling gui2.tools:generate_catalog() - ret = generate_catalog(self.gui, db, ids, self.gui.device_manager) + ret = generate_catalog(self.gui, dbspec, ids, self.gui.device_manager) if ret is None: return diff --git a/src/calibre/gui2/catalog/catalog_epub_mobi.py b/src/calibre/gui2/catalog/catalog_epub_mobi.py index 5acda0948c..1f0fdae7e2 100644 --- a/src/calibre/gui2/catalog/catalog_epub_mobi.py +++ b/src/calibre/gui2/catalog/catalog_epub_mobi.py @@ -19,6 +19,7 @@ class PluginWidget(QWidget,Ui_Form): OPTION_FIELDS = [('exclude_genre','\[.+\]'), ('exclude_tags','~,'+_('Catalog')), ('generate_titles', True), + ('generate_series', True), ('generate_recently_added', True), ('note_tag','*'), ('numbers_as_text', False), @@ -40,7 +41,7 @@ class PluginWidget(QWidget,Ui_Form): # Update dialog fields from stored options for opt in self.OPTION_FIELDS: opt_value = gprefs.get(self.name + '_' + opt[0], opt[1]) - if opt[0] in ['numbers_as_text','generate_titles','generate_recently_added']: + if opt[0] in ['numbers_as_text','generate_titles','generate_series','generate_recently_added']: getattr(self, opt[0]).setChecked(opt_value) else: getattr(self, opt[0]).setText(opt_value) @@ -52,13 +53,13 @@ class PluginWidget(QWidget,Ui_Form): # others store as lists opts_dict = {} for opt in self.OPTION_FIELDS: - if opt[0] in ['numbers_as_text','generate_titles','generate_recently_added']: + if opt[0] in ['numbers_as_text','generate_titles','generate_series','generate_recently_added']: opt_value = getattr(self,opt[0]).isChecked() else: opt_value = unicode(getattr(self, opt[0]).text()) gprefs.set(self.name + '_' + opt[0], opt_value) - if opt[0] in ['exclude_genre','numbers_as_text','generate_titles','generate_recently_added']: + if opt[0] in ['exclude_genre','numbers_as_text','generate_titles','generate_series','generate_recently_added']: opts_dict[opt[0]] = opt_value else: opts_dict[opt[0]] = opt_value.split(',') diff --git a/src/calibre/gui2/catalog/catalog_epub_mobi.ui b/src/calibre/gui2/catalog/catalog_epub_mobi.ui index cdf91eed6f..fa6b53e3a4 100644 --- a/src/calibre/gui2/catalog/catalog_epub_mobi.ui +++ b/src/calibre/gui2/catalog/catalog_epub_mobi.ui @@ -108,20 +108,27 @@ - + Include 'Recently Added' Section - + Sort numbers as text + + + + Include 'Series' Section + + + diff --git a/src/calibre/gui2/convert/gui_conversion.py b/src/calibre/gui2/convert/gui_conversion.py index 148dc328ad..69dabe28b8 100644 --- a/src/calibre/gui2/convert/gui_conversion.py +++ b/src/calibre/gui2/convert/gui_conversion.py @@ -27,13 +27,9 @@ def gui_catalog(fmt, title, dbspec, ids, out_file_name, sync, fmt_options, conne notification=DummyReporter(), log=None): if log is None: log = Log() - if dbspec is None: - from calibre.utils.config import prefs - from calibre.library.database2 import LibraryDatabase2 - dbpath = prefs['library_path'] - db = LibraryDatabase2(dbpath) - else: # To be implemented in the future - pass + from calibre.library import db + db = db() + db.catalog_plugin_on_device_temp_mapping = dbspec # Create a minimal OptionParser that we can append to parser = OptionParser() diff --git a/src/calibre/gui2/preferences/main.py b/src/calibre/gui2/preferences/main.py index 6524faec2d..d55f2d90d1 100644 --- a/src/calibre/gui2/preferences/main.py +++ b/src/calibre/gui2/preferences/main.py @@ -10,8 +10,8 @@ from functools import partial from PyQt4.Qt import QMainWindow, Qt, QIcon, QStatusBar, QFont, QWidget, \ QScrollArea, QStackedWidget, QVBoxLayout, QLabel, QFrame, QKeySequence, \ - QToolBar, QSize, pyqtSignal, QSizePolicy, QToolButton, QAction, \ - QPushButton, QHBoxLayout + QToolBar, QSize, pyqtSignal, QPixmap, QToolButton, QAction, \ + QDialogButtonBox, QHBoxLayout from calibre.constants import __appname__, __version__, islinux, isosx from calibre.gui2 import gprefs, min_available_height, available_width, \ @@ -20,6 +20,8 @@ from calibre.gui2.preferences import init_gui, AbortCommit, get_plugin from calibre.customize.ui import preferences_plugins from calibre.utils.ordered_dict import OrderedDict +ICON_SIZE = 32 + class StatusBar(QStatusBar): # {{{ def __init__(self, parent=None): @@ -42,6 +44,32 @@ class StatusBar(QStatusBar): # {{{ # }}} +class BarTitle(QWidget): + + def __init__(self, parent=None): + QWidget.__init__(self, parent) + self._layout = QHBoxLayout() + self.setLayout(self._layout) + self._layout.addStretch(10) + self.icon = QLabel('') + self._layout.addWidget(self.icon) + self.title = QLabel('') + self.title.setStyleSheet('QLabel { font-weight: bold }') + self.title.setAlignment(Qt.AlignLeft | Qt.AlignCenter) + self._layout.addWidget(self.title) + self._layout.addStretch(10) + + def show_plugin(self, plugin): + self.pmap = QPixmap(plugin.icon).scaled(ICON_SIZE, ICON_SIZE, + Qt.KeepAspectRatio, Qt.SmoothTransformation) + self.icon.setPixmap(self.pmap) + self.title.setText(plugin.gui_name) + tt = plugin.description + self.setStatusTip(tt) + tt = textwrap.fill(tt) + self.setToolTip(tt) + self.setWhatsThis(tt) + class Category(QWidget): # {{{ plugin_activated = pyqtSignal(object) @@ -88,7 +116,6 @@ class Category(QWidget): # {{{ class Browser(QScrollArea): # {{{ show_plugin = pyqtSignal(object) - close_signal = pyqtSignal() def __init__(self, parent=None): QScrollArea.__init__(self, parent) @@ -119,14 +146,6 @@ class Browser(QScrollArea): # {{{ self.container = QWidget(self) self.container.setLayout(self._layout) self.setWidget(self.container) - if isosx: - self._osxl = QHBoxLayout() - self.close_button = QPushButton(_('Close')) - self.close_button.clicked.connect(self.close_requested) - self._osxl.addStretch(10) - self._osxl.addWidget(self.close_button) - #self._osxl.addStretch(10) - self._layout.addLayout(self._osxl) for name, plugins in self.category_map.items(): w = Category(name, plugins, self) @@ -134,8 +153,6 @@ class Browser(QScrollArea): # {{{ self._layout.addWidget(w) w.plugin_activated.connect(self.show_plugin.emit) - def close_requested(self, *args): - self.close_signal.emit() # }}} @@ -177,9 +194,15 @@ class Preferences(QMainWindow): self.setStatusBar(self.status_bar) self.stack = QStackedWidget(self) - self.setCentralWidget(self.stack) + self.cw = QWidget(self) + self.cw.setLayout(QVBoxLayout()) + self.cw.layout().addWidget(self.stack) + self.bb = QDialogButtonBox(QDialogButtonBox.Close) + self.cw.layout().addWidget(self.bb) + self.bb.rejected.connect(self.close, type=Qt.QueuedConnection) + self.setCentralWidget(self.cw) + self.bb.setVisible(isosx) self.browser = Browser(self) - self.browser.close_signal.connect(self.close, type=Qt.QueuedConnection) self.browser.show_plugin.connect(self.show_plugin) self.stack.addWidget(self.browser) self.scroll_area = QScrollArea(self) @@ -189,7 +212,7 @@ class Preferences(QMainWindow): self.bar = QToolBar(self) self.addToolBar(self.bar) self.bar.setVisible(False) - self.bar.setIconSize(QSize(32, 32)) + self.bar.setIconSize(QSize(ICON_SIZE, ICON_SIZE)) self.bar.setMovable(False) self.bar.setFloatable(False) self.bar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) @@ -197,13 +220,8 @@ class Preferences(QMainWindow): self.commit) self.cancel_action = self.bar.addAction(QIcon(I('window-close.png')), _('&Cancel'), self.cancel) - self.bar_filler = QLabel('') - self.bar_filler.setSizePolicy(QSizePolicy.Expanding, - QSizePolicy.Preferred) - self.bar_filler.setStyleSheet( - 'QLabel { font-weight: bold }') - self.bar_filler.setAlignment(Qt.AlignHCenter | Qt.AlignCenter) - self.bar.addWidget(self.bar_filler) + self.bar_title = BarTitle(self.bar) + self.bar.addWidget(self.bar_title) self.restore_action = self.bar.addAction(QIcon(I('clear_left.png')), _('Restore &defaults'), self.restore_defaults) for ac, tt in [('apply', _('Save changes')), @@ -247,7 +265,7 @@ class Preferences(QMainWindow): self.restore_action.setToolTip(textwrap.fill(tt)) self.restore_action.setWhatsThis(textwrap.fill(tt)) self.restore_action.setStatusTip(tt) - self.bar_filler.setText(plugin.gui_name) + self.bar_title.show_plugin(plugin) self.setWindowIcon(QIcon(plugin.icon)) self.bar.setVisible(True) diff --git a/src/calibre/library/catalog.py b/src/calibre/library/catalog.py index a23b86bdef..05e596d3fb 100644 --- a/src/calibre/library/catalog.py +++ b/src/calibre/library/catalog.py @@ -20,7 +20,7 @@ from calibre.utils.date import isoformat, now as nowf from calibre.utils.logging import default_log as log FIELDS = ['all', 'author_sort', 'authors', 'comments', - 'cover', 'formats', 'id', 'isbn', 'pubdate', 'publisher', 'rating', + 'cover', 'formats', 'id', 'isbn', 'ondevice', 'pubdate', 'publisher', 'rating', 'series_index', 'series', 'size', 'tags', 'timestamp', 'title', 'uuid'] @@ -96,6 +96,11 @@ class CSV_XML(CatalogPlugin): # Get the requested output fields as a list fields = self.get_output_fields(opts) + # If connected device, add 'On Device' values to data + if opts.connected_device['is_device_connected'] and 'ondevice' in fields: + for entry in data: + entry['ondevice'] = db.catalog_plugin_on_device_temp_mapping[entry['id']]['ondevice'] + if self.fmt == 'csv': outfile = codecs.open(path_to_output, 'w', 'utf8') @@ -104,7 +109,6 @@ class CSV_XML(CatalogPlugin): # Output the entry fields for entry in data: - print "%s [%s] ondevice: %s" % (entry['title'],entry['id'], repr(db.catalog_plugin_on_device_temp_mapping[entry['id']])) outstr = [] for field in fields: item = entry[field] @@ -142,10 +146,10 @@ class CSV_XML(CatalogPlugin): root.append(record) for field in ('id', 'uuid', 'title', 'publisher', 'rating', 'size', - 'isbn'): + 'isbn','ondevice'): if field in fields: val = r[field] - if val is None: + if not val: continue if not isinstance(val, (str, unicode)): val = unicode(val) @@ -563,6 +567,13 @@ class EPUB_MOBI(CatalogPlugin): help=_("Include 'Titles' section in catalog.\n" "Default: '%default'\n" "Applies to: ePub, MOBI output formats")), + Option('--generate-series', + default=False, + dest='generate_series', + action = 'store_true', + help=_("Include 'Series' section in catalog.\n" + "Default: '%default'\n" + "Applies to: ePub, MOBI output formats")), Option('--generate-recently-added', default=False, dest='generate_recently_added', @@ -888,6 +899,9 @@ class EPUB_MOBI(CatalogPlugin): self.__totalSteps += 2 if self.generateRecentlyRead: self.__totalSteps += 2 + if self.opts.generate_series: + self.__totalSteps += 2 + # Accessors if True: @@ -1198,6 +1212,8 @@ class EPUB_MOBI(CatalogPlugin): self.generateHTMLByAuthor() if self.opts.generate_titles: self.generateHTMLByTitle() + if self.opts.generate_series: + self.generateHTMLBySeries() if self.opts.generate_recently_added: self.generateHTMLByDateAdded() if self.generateRecentlyRead: @@ -1211,6 +1227,8 @@ class EPUB_MOBI(CatalogPlugin): self.generateNCXByAuthor("Authors") if self.opts.generate_titles: self.generateNCXByTitle("Titles") + if self.opts.generate_series: + self.generateNCXBySeries("Series") if self.opts.generate_recently_added: self.generateNCXByDateAdded("Recently Added") if self.generateRecentlyRead: @@ -1571,13 +1589,26 @@ class EPUB_MOBI(CatalogPlugin): emTag = Tag(soup, "em") if title['series']: # title
series series_index - brTag = Tag(soup,'br') - title_tokens = list(title['title'].partition(':')) - emTag.insert(0, escape(NavigableString(title_tokens[2].strip()))) - emTag.insert(1, brTag) - smallTag = Tag(soup,'small') - smallTag.insert(0, escape(NavigableString(title_tokens[0]))) - emTag.insert(2, smallTag) + if self.opts.generate_series: + brTag = Tag(soup,'br') + title_tokens = list(title['title'].partition(':')) + emTag.insert(0, escape(NavigableString(title_tokens[2].strip()))) + emTag.insert(1, brTag) + smallTag = Tag(soup,'small') + aTag = Tag(soup,'a') + aTag['href'] = "%s.html#%s_series" % ('BySeries', + re.sub('\W','',title['series']).lower()) + aTag.insert(0, title_tokens[0]) + smallTag.insert(0, aTag) + emTag.insert(2, smallTag) + else: + brTag = Tag(soup,'br') + title_tokens = list(title['title'].partition(':')) + emTag.insert(0, escape(NavigableString(title_tokens[2].strip()))) + emTag.insert(1, brTag) + smallTag = Tag(soup,'small') + smallTag.insert(0, escape(NavigableString(title_tokens[0]))) + emTag.insert(2, smallTag) else: emTag.insert(0, NavigableString(escape(title['title']))) titleTag = body.find(attrs={'class':'title'}) @@ -1726,17 +1757,16 @@ class EPUB_MOBI(CatalogPlugin): body.insert(btc, aTag) btc += 1 - ''' - # We don't need this because the Kindle shows section titles - #

By Title

- h2Tag = Tag(soup, "h2") - aTag = Tag(soup, "a") - aTag['name'] = "bytitle" - h2Tag.insert(0,aTag) - h2Tag.insert(1,NavigableString('By Title (%d)' % len(self.booksByTitle))) - body.insert(btc,h2Tag) - btc += 1 - ''' + if not self.__generateForKindle: + # We don't need this because the Kindle shows section titles + #

By Title

+ h2Tag = Tag(soup, "h2") + aTag = Tag(soup, "a") + aTag['name'] = "bytitle" + h2Tag.insert(0,aTag) + h2Tag.insert(1,NavigableString('By Title (%d)' % len(self.booksByTitle))) + body.insert(btc,h2Tag) + btc += 1 #

#

@@ -1931,7 +1961,7 @@ class EPUB_MOBI(CatalogPlugin): current_series = book['series'] pSeriesTag = Tag(soup,'p') pSeriesTag['class'] = "series" - pSeriesTag.insert(0,NavigableString(self.NOT_READ_SYMBOL + '%s Series' % book['series'])) + pSeriesTag.insert(0,NavigableString(self.NOT_READ_SYMBOL + '%s' % book['series'])) divTag.insert(dtc,pSeriesTag) dtc += 1 if current_series and not book['series']: @@ -1971,18 +2001,17 @@ class EPUB_MOBI(CatalogPlugin): divTag.insert(dtc, pBookTag) dtc += 1 - ''' - # Insert the

tag with book_count at the head - #

By Author

- h2Tag = Tag(soup, "h2") - aTag = Tag(soup, "a") - anchor_name = friendly_name.lower() - aTag['name'] = anchor_name.replace(" ","") - h2Tag.insert(0,aTag) - h2Tag.insert(1,NavigableString('%s (%d)' % (friendly_name, book_count))) - body.insert(btc,h2Tag) - btc += 1 - ''' + if not self.__generateForKindle: + # Insert the

tag with book_count at the head + #

By Author

+ h2Tag = Tag(soup, "h2") + aTag = Tag(soup, "a") + anchor_name = friendly_name.lower() + aTag['name'] = anchor_name.replace(" ","") + h2Tag.insert(0,aTag) + h2Tag.insert(1,NavigableString('%s (%d)' % (friendly_name, book_count))) + body.insert(btc,h2Tag) + btc += 1 # Add the divTag to the body body.insert(btc, divTag) @@ -2048,7 +2077,7 @@ class EPUB_MOBI(CatalogPlugin): current_series = new_entry['series'] pSeriesTag = Tag(soup,'p') pSeriesTag['class'] = "series" - pSeriesTag.insert(0,NavigableString(self.NOT_READ_SYMBOL + '%s Series' % new_entry['series'])) + pSeriesTag.insert(0,NavigableString(self.NOT_READ_SYMBOL + '%s' % new_entry['series'])) divTag.insert(dtc,pSeriesTag) dtc += 1 if current_series and not new_entry['series']: @@ -2162,18 +2191,17 @@ class EPUB_MOBI(CatalogPlugin): aTag['name'] = anchor_name.replace(" ","") body.insert(btc, aTag) btc += 1 - ''' - # We don't need this because the kindle inserts section titles - #

By Author

- h2Tag = Tag(soup, "h2") - aTag = Tag(soup, "a") - anchor_name = friendly_name.lower() - aTag['name'] = anchor_name.replace(" ","") - h2Tag.insert(0,aTag) - h2Tag.insert(1,NavigableString('%s' % friendly_name)) - body.insert(btc,h2Tag) - btc += 1 - ''' + + if not self.__generateForKindle: + #

By Author

+ h2Tag = Tag(soup, "h2") + aTag = Tag(soup, "a") + anchor_name = friendly_name.lower() + aTag['name'] = anchor_name.replace(" ","") + h2Tag.insert(0,aTag) + h2Tag.insert(1,NavigableString('%s' % friendly_name)) + body.insert(btc,h2Tag) + btc += 1 #

#

@@ -2439,6 +2467,158 @@ class EPUB_MOBI(CatalogPlugin): outfile.close() self.htmlFileList.append("content/ByDateRead.html") + def generateHTMLBySeries(self): + ''' + Generate a list of series + ''' + self.updateProgressFullStep("Fetching series") + + self.opts.sort_by = 'series' + + # Merge opts.exclude_tags with opts.search_text + # Updated to use exact match syntax + empty_exclude_tags = False if len(self.opts.exclude_tags) else True + search_phrase = 'series:true ' + if not empty_exclude_tags: + exclude_tags = self.opts.exclude_tags.split(',') + search_terms = [] + for tag in exclude_tags: + search_terms.append("tag:=%s" % tag) + search_phrase += "not (%s)" % " or ".join(search_terms) + + # If a list of ids are provided, don't use search_text + if self.opts.ids: + self.opts.search_text = search_phrase + else: + if self.opts.search_text: + self.opts.search_text += " " + search_phrase + else: + self.opts.search_text = search_phrase + + # Fetch the database as a dictionary + self.booksBySeries = self.plugin.search_sort_db(self.db, self.opts) + + for series_item in self.booksBySeries: + print ' %s %s %s' % (series_item['series'],series_item['series_index'],series_item['title']) + + friendly_name = "By Series" + + soup = self.generateHTMLEmptyHeader(friendly_name) + body = soup.find('body') + + btc = 0 + + # Insert section tag + aTag = Tag(soup,'a') + aTag['name'] = 'section_start' + body.insert(btc, aTag) + btc += 1 + + # Insert the anchor + aTag = Tag(soup, "a") + anchor_name = friendly_name.lower() + aTag['name'] = anchor_name.replace(" ","") + body.insert(btc, aTag) + btc += 1 + + #

+ #

+ divTag = Tag(soup, "div") + dtc = 0 + current_letter = "" + current_series = None + + # Loop through booksBySeries + series_count = 0 + for book in self.booksBySeries: + # Check for initial letter change + sort_title = self.generateSortTitle(book['series']) + if self.letter_or_symbol(sort_title[0].upper()) != current_letter : + ''' + # Start a new letter - anchor only, hidden + current_letter = book['author_sort'][0].upper() + aTag = Tag(soup, "a") + aTag['name'] = "%sseries" % current_letter + divTag.insert(dtc, aTag) + dtc += 1 + ''' + # Start a new letter with Index letter + current_letter = self.letter_or_symbol(sort_title[0].upper()) + pIndexTag = Tag(soup, "p") + pIndexTag['class'] = "letter_index" + aTag = Tag(soup, "a") + aTag['name'] = "%s_series" % self.letter_or_symbol(current_letter) + pIndexTag.insert(0,aTag) + pIndexTag.insert(1,NavigableString(self.letter_or_symbol(sort_title[0].upper()))) + divTag.insert(dtc,pIndexTag) + dtc += 1 + + # Check for series change + if book['series'] != current_series: + # Start a new series + series_count += 1 + current_series = book['series'] + pSeriesTag = Tag(soup,'p') + pSeriesTag['class'] = "series" + aTag = Tag(soup, 'a') + aTag['name'] = "%s_series" % re.sub('\W','',book['series']).lower() + pSeriesTag.insert(0,aTag) + pSeriesTag.insert(1,NavigableString(self.NOT_READ_SYMBOL + '%s' % book['series'])) + divTag.insert(dtc,pSeriesTag) + dtc += 1 + + # Add books + pBookTag = Tag(soup, "p") + ptc = 0 + + # book with read/reading/unread symbol + if 'read' in book and book['read']: + # check mark + pBookTag.insert(ptc,NavigableString(self.READ_SYMBOL)) + pBookTag['class'] = "read_book" + ptc += 1 + elif book['id'] in self.bookmarked_books: + pBookTag.insert(ptc,NavigableString(self.READING_SYMBOL)) + pBookTag['class'] = "read_book" + ptc += 1 + else: + # hidden check mark + pBookTag['class'] = "unread_book" + pBookTag.insert(ptc,NavigableString(self.NOT_READ_SYMBOL)) + ptc += 1 + + aTag = Tag(soup, "a") + aTag['href'] = "book_%d.html" % (int(float(book['id']))) + # Use series, series index if avail else just title + aTag.insert(0,'%d. %s · %s' % (book['series_index'],escape(book['title']), ' & '.join(book['authors']))) + pBookTag.insert(ptc, aTag) + ptc += 1 + + divTag.insert(dtc, pBookTag) + dtc += 1 + + if not self.__generateForKindle: + # Insert the

tag with book_count at the head + #

By Series

+ h2Tag = Tag(soup, "h2") + aTag = Tag(soup, "a") + anchor_name = friendly_name.lower() + aTag['name'] = anchor_name.replace(" ","") + h2Tag.insert(0,aTag) + h2Tag.insert(1,NavigableString('%s (%d)' % (friendly_name, series_count))) + body.insert(btc,h2Tag) + btc += 1 + + # Add the divTag to the body + body.insert(btc, divTag) + + # Write the generated file to contentdir + outfile_spec = "%s/BySeries.html" % (self.contentDir) + outfile = open(outfile_spec, 'w') + outfile.write(soup.prettify()) + outfile.close() + self.htmlFileList.append("content/BySeries.html") + def generateHTMLByTags(self): # Generate individual HTML files for each tag, e.g. Fiction, Nonfiction ... # Note that special tags - ~+*[] - have already been filtered from books[] @@ -2884,6 +3064,98 @@ class EPUB_MOBI(CatalogPlugin): self.ncxSoup = ncx_soup + def generateNCXBySeries(self, tocTitle): + self.updateProgressFullStep("NCX 'Series'") + + def add_to_series_by_letter(current_series_list): + current_series_list = " • ".join(current_series_list) + current_series_list = self.formatNCXText(current_series_list, dest="description") + series_by_letter.append(current_series_list) + + soup = self.ncxSoup + output = "BySeries" + body = soup.find("navPoint") + btc = len(body.contents) + + # --- Construct the 'Books By Series' section --- + navPointTag = Tag(soup, 'navPoint') + navPointTag['class'] = "section" + navPointTag['id'] = "byseries-ID" + navPointTag['playOrder'] = self.playOrder + self.playOrder += 1 + navLabelTag = Tag(soup, 'navLabel') + textTag = Tag(soup, 'text') + textTag.insert(0, NavigableString(tocTitle)) + navLabelTag.insert(0, textTag) + nptc = 0 + navPointTag.insert(nptc, navLabelTag) + nptc += 1 + contentTag = Tag(soup,"content") + contentTag['src'] = "content/%s.html#section_start" % (output) + navPointTag.insert(nptc, contentTag) + nptc += 1 + + series_by_letter = [] + + # Loop over the series titles, find start of each letter, add description_preview_count books + # Special switch for using different title list + title_list = self.booksBySeries + current_letter = self.letter_or_symbol(title_list[0]['series'][0]) + title_letters = [current_letter] + current_series_list = [] + current_series = "" + for book in title_list: + sort_title = self.generateSortTitle(book['series']) + if self.letter_or_symbol(sort_title[0]) != current_letter: + # Save the old list + add_to_series_by_letter(current_series_list) + + # Start the new list + current_letter = self.letter_or_symbol(sort_title[0]) + title_letters.append(current_letter) + current_series = book['series'] + current_series_list = [book['series']] + else: + if len(current_series_list) < self.descriptionClip and \ + book['series'] != current_series : + current_series = book['series'] + current_series_list.append(book['series']) + + # Add the last book list + add_to_series_by_letter(current_series_list) + + # Add *article* entries for each populated series title letter + for (i,books) in enumerate(series_by_letter): + navPointByLetterTag = Tag(soup, 'navPoint') + navPointByLetterTag['class'] = "article" + navPointByLetterTag['id'] = "%sSeries-ID" % (title_letters[i].upper()) + navPointTag['playOrder'] = self.playOrder + self.playOrder += 1 + navLabelTag = Tag(soup, 'navLabel') + textTag = Tag(soup, 'text') + textTag.insert(0, NavigableString(u"Series beginning with %s" % \ + (title_letters[i] if len(title_letters[i])>1 else "'" + title_letters[i] + "'"))) + navLabelTag.insert(0, textTag) + navPointByLetterTag.insert(0,navLabelTag) + contentTag = Tag(soup, 'content') + contentTag['src'] = "content/%s.html#%s_series" % (output, title_letters[i]) + navPointByLetterTag.insert(1,contentTag) + + if self.generateForKindle: + cmTag = Tag(soup, '%s' % 'calibre:meta') + cmTag['name'] = "description" + cmTag.insert(0, NavigableString(self.formatNCXText(books, dest='description'))) + navPointByLetterTag.insert(2, cmTag) + + navPointTag.insert(nptc, navPointByLetterTag) + nptc += 1 + + # Add this section to the body + body.insert(btc, navPointTag) + btc += 1 + + self.ncxSoup = soup + def generateNCXByTitle(self, tocTitle): self.updateProgressFullStep("NCX 'Titles'") @@ -3754,7 +4026,7 @@ class EPUB_MOBI(CatalogPlugin): current_series = book['series'] pSeriesTag = Tag(soup,'p') pSeriesTag['class'] = "series" - pSeriesTag.insert(0,NavigableString(self.NOT_READ_SYMBOL + '%s Series' % book['series'])) + pSeriesTag.insert(0,NavigableString(self.NOT_READ_SYMBOL + '%s' % book['series'])) divTag.insert(dtc,pSeriesTag) dtc += 1 diff --git a/src/calibre/library/cli.py b/src/calibre/library/cli.py index ad9cda07b8..0bd0610ab0 100644 --- a/src/calibre/library/cli.py +++ b/src/calibre/library/cli.py @@ -676,7 +676,7 @@ def command_catalog(args, dbpath): # Parallel initialization in calibre.gui2.tools:generate_catalog() opts.connected_device = { 'is_device_connected': False, - 'kind': device_manager.connected_device_kind, + 'kind': None, 'name': None, 'save_template': None, 'serial': None,