from opf
# iTunes balks on setting EpisodeNumber, but it sticks (9.1.1.12)
- if metadata.series and self.settings().read_metadata:
+ if metadata_x.series and self.settings().read_metadata:
if DEBUG:
self.log.info(" using Series name as Genre")
# Format the index as a sort key
- index = metadata.series_index
+ index = metadata_x.series_index
integer = int(index)
fraction = index-integer
series_index = '%04d%s' % (integer, str('%0.4f' % fraction).lstrip('0'))
if lb_added:
- lb_added.SortName = "%s %s" % (metadata.series, series_index)
- lb_added.Genre = metadata.series
- lb_added.EpisodeID = metadata.series
+ lb_added.SortName = "%s %s" % (self.title_sorter(metadata_x.series), series_index)
+ lb_added.EpisodeID = metadata_x.series
try:
- lb_added.EpisodeNumber = metadata.series_index
+ lb_added.EpisodeNumber = metadata_x.series_index
except:
pass
+
+ # If no plugboard transform applied to tags, change the Genre/Category to Series
+ if metadata.tags == metadata_x.tags:
+ lb_added.Genre = self.title_sorter(metadata_x.series)
+ else:
+ for tag in metadata_x.tags:
+ if self._is_alpha(tag[0]):
+ lb_added.Genre = tag
+ break
+
if db_added:
- db_added.SortName = "%s %s" % (metadata.series, series_index)
- db_added.Genre = metadata.series
- db_added.EpisodeID = metadata.series
+ db_added.SortName = "%s %s" % (self.title_sorter(metadata_x.series), series_index)
+ db_added.EpisodeID = metadata_x.series
try:
- db_added.EpisodeNumber = metadata.series_index
+ db_added.EpisodeNumber = metadata_x.series_index
except:
if DEBUG:
self.log.warning(" iTunes automation interface reported an error"
" setting EpisodeNumber on iDevice")
- elif metadata.tags:
+
+ # If no plugboard transform applied to tags, change the Genre/Category to Series
+ if metadata.tags == metadata_x.tags:
+ db_added.Genre = self.title_sorter(metadata_x.series)
+ else:
+ for tag in metadata_x.tags:
+ if self._is_alpha(tag[0]):
+ db_added.Genre = tag
+ break
+
+ elif metadata_x.tags is not None:
if DEBUG:
self.log.info(" using Tag as Genre")
- for tag in metadata.tags:
+ for tag in metadata_x.tags:
if self._is_alpha(tag[0]):
if lb_added:
lb_added.Genre = tag
@@ -2728,6 +2803,36 @@ class ITUNES(DriverBase):
db_added.Genre = tag
break
+ def _xform_metadata_via_plugboard(self, book, format):
+ ''' Transform book metadata from plugboard templates '''
+ if DEBUG:
+ self.log.info(" ITUNES._update_metadata_from_plugboard()")
+
+ if self.plugboard_func:
+ pb = self.plugboard_func(self.DEVICE_PLUGBOARD_NAME, format, self.plugboards)
+ newmi = book.deepcopy_metadata()
+ newmi.template_to_attribute(book, pb)
+ if DEBUG:
+ self.log.info(" transforming %s using %s:" % (format, pb))
+ self.log.info(" title: %s %s" % (book.title, ">>> %s" %
+ newmi.title if book.title != newmi.title else ''))
+ self.log.info(" title_sort: %s %s" % (book.title_sort, ">>> %s" %
+ newmi.title_sort if book.title_sort != newmi.title_sort else ''))
+ self.log.info(" authors: %s %s" % (book.authors, ">>> %s" %
+ newmi.authors if book.authors != newmi.authors else ''))
+ self.log.info(" author_sort: %s %s" % (book.author_sort, ">>> %s" %
+ newmi.author_sort if book.author_sort != newmi.author_sort else ''))
+ self.log.info(" language: %s %s" % (book.language, ">>> %s" %
+ newmi.language if book.language != newmi.language else ''))
+ self.log.info(" publisher: %s %s" % (book.publisher, ">>> %s" %
+ newmi.publisher if book.publisher != newmi.publisher else ''))
+ self.log.info(" tags: %s %s" % (book.tags, ">>> %s" %
+ newmi.tags if book.tags != newmi.tags else ''))
+ else:
+ newmi = book
+ return newmi
+
+
class ITUNES_ASYNC(ITUNES):
'''
This subclass allows the user to interact directly with iTunes via a menu option
@@ -2738,6 +2843,9 @@ class ITUNES_ASYNC(ITUNES):
icon = I('devices/itunes.png')
description = _('Communicate with iTunes.')
+ # Plugboard ID
+ DEVICE_PLUGBOARD_NAME = 'APPLE'
+
connected = False
def __init__(self,path):
@@ -3012,15 +3120,9 @@ class BookList(list):
class Book(Metadata):
'''
A simple class describing a book in the iTunes Books Library.
- - See ebooks.metadata.__init__ for all fields
+ See ebooks.metadata.book.base
'''
def __init__(self,title,author):
Metadata.__init__(self, title, authors=[author])
- @dynamic_property
- def title_sorter(self):
- doc = '''String to sort the title. If absent, title is returned'''
- def fget(self):
- return re.sub('^\s*A\s+|^\s*The\s+|^\s*An\s+', '', self.title).rstrip()
- return property(doc=doc, fget=fget)
diff --git a/src/calibre/devices/interface.py b/src/calibre/devices/interface.py
index 2307bf94d6..29ba5020c7 100644
--- a/src/calibre/devices/interface.py
+++ b/src/calibre/devices/interface.py
@@ -411,6 +411,22 @@ class DevicePlugin(Plugin):
'''
raise NotImplementedError()
+ def set_plugboards(self, plugboards, pb_func):
+ '''
+ provide the driver the current set of plugboards and a function to
+ select a specific plugboard. This method is called immediately before
+ add_books and sync_booklists.
+
+ pb_func is a callable with the following signature:
+ def pb_func(device_name, format, plugboards)
+ You give it the current device name (either the class name or
+ DEVICE_PLUGBOARD_NAME), the format you are interested in (a 'real'
+ format or 'device_db'), and the plugboards (you were given those by
+ set_plugboards, the same place you got this method).
+
+ Return value: None or a single plugboard instance.
+ '''
+ pass
class BookList(list):
'''
diff --git a/src/calibre/ebooks/metadata/isbndb.py b/src/calibre/ebooks/metadata/isbndb.py
index 6416dcdc39..07a054eeaa 100644
--- a/src/calibre/ebooks/metadata/isbndb.py
+++ b/src/calibre/ebooks/metadata/isbndb.py
@@ -83,7 +83,7 @@ class ISBNDBMetadata(Metadata):
summ = tostring(book.find('summary'))
if summ:
- self.comments = 'SUMMARY:\n'+summ.string
+ self.comments = 'SUMMARY:\n'+summ
def build_isbn(base_url, opts):
diff --git a/src/calibre/gui2/actions/copy_to_library.py b/src/calibre/gui2/actions/copy_to_library.py
index 6b7654f644..513026f757 100644
--- a/src/calibre/gui2/actions/copy_to_library.py
+++ b/src/calibre/gui2/actions/copy_to_library.py
@@ -21,6 +21,7 @@ class Worker(Thread):
def __init__(self, ids, db, loc, progress, done):
Thread.__init__(self)
self.ids = ids
+ self.processed = set([])
self.db = db
self.loc = loc
self.error = None
@@ -71,6 +72,7 @@ class Worker(Thread):
co = self.db.conversion_options(x, 'PIPE')
if co is not None:
newdb.set_conversion_options(x, 'PIPE', co)
+ self.processed.add(x)
class CopyToLibraryAction(InterfaceAction):
@@ -107,9 +109,13 @@ class CopyToLibraryAction(InterfaceAction):
for name, loc in locations:
self.menu.addAction(name, partial(self.copy_to_library,
loc))
+ self.menu.addAction(name + ' ' + _('(delete after copy)'),
+ partial(self.copy_to_library, loc, delete_after=True))
+ self.menu.addSeparator()
+
self.qaction.setVisible(bool(locations))
- def copy_to_library(self, loc):
+ def copy_to_library(self, loc, delete_after=False):
rows = self.gui.library_view.selectionModel().selectedRows()
if not rows or len(rows) == 0:
return error_dialog(self.gui, _('Cannot copy'),
@@ -140,7 +146,16 @@ class CopyToLibraryAction(InterfaceAction):
else:
self.gui.status_bar.show_message(_('Copied %d books to %s') %
(len(ids), loc), 2000)
-
+ if delete_after and self.worker.processed:
+ v = self.gui.library_view
+ ci = v.currentIndex()
+ row = None
+ if ci.isValid():
+ row = ci.row()
+
+ v.model().delete_books_by_id(self.worker.processed)
+ self.gui.iactions['Remove Books'].library_ids_deleted(
+ self.worker.processed, row)
diff --git a/src/calibre/gui2/actions/delete.py b/src/calibre/gui2/actions/delete.py
index 406860e4ec..a541590fd1 100644
--- a/src/calibre/gui2/actions/delete.py
+++ b/src/calibre/gui2/actions/delete.py
@@ -149,6 +149,18 @@ class DeleteAction(InterfaceAction):
self.gui.library_view.model().current_changed(self.gui.library_view.currentIndex(),
self.gui.library_view.currentIndex())
+
+ def library_ids_deleted(self, ids_deleted, current_row=None):
+ view = self.gui.library_view
+ for v in (self.gui.memory_view, self.gui.card_a_view, self.gui.card_b_view):
+ if v is None:
+ continue
+ v.model().clear_ondevice(ids_deleted)
+ if current_row is not None:
+ ci = view.model().index(current_row, 0)
+ if ci.isValid():
+ view.set_current_row(current_row)
+
def delete_books(self, *args):
'''
Delete selected books from device or library.
@@ -168,14 +180,7 @@ class DeleteAction(InterfaceAction):
if ci.isValid():
row = ci.row()
ids_deleted = view.model().delete_books(rows)
- for v in (self.gui.memory_view, self.gui.card_a_view, self.gui.card_b_view):
- if v is None:
- continue
- v.model().clear_ondevice(ids_deleted)
- if row is not None:
- ci = view.model().index(row, 0)
- if ci.isValid():
- view.set_current_row(row)
+ self.library_ids_deleted(ids_deleted, row)
else:
if not confirm(''+_('The selected books will be '
'permanently deleted '
diff --git a/src/calibre/gui2/preferences/plugboard.ui b/src/calibre/gui2/preferences/plugboard.ui
index 8249584678..9f14978ca8 100644
--- a/src/calibre/gui2/preferences/plugboard.ui
+++ b/src/calibre/gui2/preferences/plugboard.ui
@@ -6,8 +6,8 @@
0
0
- 707
- 340
+ 931
+ 389
@@ -23,7 +23,7 @@ Use this dialog to define a 'plugboard' for a format (or all formats) and a devi
Often templates will contain simple references to composite columns, but this is not necessary. You can use any template in a source box that you can use elsewhere in calibre.
-One possible use for a plugboard is to alter the title to contain series informaton. Another would be to change the author sort, something that mobi users might do to force it to use the ';' that the kindle requires. A third would be to specify the language.
+One possible use for a plugboard is to alter the title to contain series information. Another would be to change the author sort, something that mobi users might do to force it to use the ';' that the kindle requires. A third would be to specify the language.
Qt::PlainText
@@ -41,8 +41,7 @@ One possible use for a plugboard is to alter the title to contain series informa
-
-
-
+
-
diff --git a/src/calibre/gui2/viewer/main.py b/src/calibre/gui2/viewer/main.py
index 79f4c29998..26fd2cadc9 100644
--- a/src/calibre/gui2/viewer/main.py
+++ b/src/calibre/gui2/viewer/main.py
@@ -166,6 +166,8 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
def __init__(self, pathtoebook=None, debug_javascript=False):
MainWindow.__init__(self, None)
self.setupUi(self)
+ self.show_toc_on_open = False
+ self.current_book_has_toc = False
self.base_window_title = unicode(self.windowTitle())
self.iterator = None
self.current_page = None
@@ -214,11 +216,12 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
self.action_metadata.setCheckable(True)
self.action_metadata.setShortcut(Qt.CTRL+Qt.Key_I)
self.action_table_of_contents.setCheckable(True)
+ self.toc.setMinimumWidth(80)
self.action_reference_mode.setCheckable(True)
self.connect(self.action_reference_mode, SIGNAL('triggered(bool)'),
lambda x: self.view.reference_mode(x))
self.connect(self.action_metadata, SIGNAL('triggered(bool)'), lambda x:self.metadata.setVisible(x))
- self.connect(self.action_table_of_contents, SIGNAL('triggered(bool)'), lambda x:self.toc.setVisible(x))
+ self.connect(self.action_table_of_contents, SIGNAL('toggled(bool)'), lambda x:self.toc.setVisible(x))
self.connect(self.action_copy, SIGNAL('triggered(bool)'), self.copy)
self.connect(self.action_font_size_larger, SIGNAL('triggered(bool)'),
self.font_size_larger)
@@ -259,7 +262,6 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
f = functools.partial(self.load_ebook, pathtoebook)
QTimer.singleShot(50, f)
self.view.setMinimumSize(100, 100)
- self.splitter.setSizes([1, 300])
self.toc.setCursor(Qt.PointingHandCursor)
self.tool_bar.setContextMenuPolicy(Qt.PreventContextMenu)
self.tool_bar2.setContextMenuPolicy(Qt.PreventContextMenu)
@@ -285,6 +287,12 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
def save_state(self):
state = str(self.saveState(self.STATE_VERSION))
dynamic['viewer_toolbar_state'] = state
+ dynamic.set('viewer_window_geometry', self.saveGeometry())
+ if self.current_book_has_toc:
+ dynamic.set('viewer_toc_isvisible', bool(self.toc.isVisible()))
+ if self.toc.isVisible():
+ dynamic.set('viewer_splitter_state',
+ bytearray(self.splitter.saveState()))
def restore_state(self):
state = dynamic.get('viewer_toolbar_state', None)
@@ -609,10 +617,15 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
title = self.iterator.opf.title
if not title:
title = os.path.splitext(os.path.basename(pathtoebook))[0]
- self.action_table_of_contents.setDisabled(not self.iterator.toc)
if self.iterator.toc:
self.toc_model = TOC(self.iterator.toc)
self.toc.setModel(self.toc_model)
+ if self.show_toc_on_open:
+ self.action_table_of_contents.setChecked(True)
+ else:
+ self.action_table_of_contents.setChecked(False)
+ self.action_table_of_contents.setDisabled(not self.iterator.toc)
+ self.current_book_has_toc = bool(self.iterator.toc)
self.current_title = title
self.setWindowTitle(self.base_window_title+' - '+title)
self.pos.setMaximum(sum(self.iterator.pages))
@@ -656,22 +669,21 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
return self
def __exit__(self, *args):
- self.write_settings()
if self.iterator is not None:
self.save_current_position()
self.iterator.__exit__(*args)
- def write_settings(self):
- dynamic.set('viewer_window_geometry', self.saveGeometry())
-
def read_settings(self):
c = config().parse()
- wg = dynamic['viewer_window_geometry']
- if wg is not None and c.remember_window_size:
- self.restoreGeometry(wg)
-
-
-
+ self.splitter.setSizes([1, 300])
+ if c.remember_window_size:
+ wg = dynamic.get('viewer_window_geometry', None)
+ if wg is not None:
+ self.restoreGeometry(wg)
+ ss = dynamic.get('viewer_splitter_state', None)
+ if ss is not None:
+ self.splitter.restoreState(ss)
+ self.show_toc_on_open = dynamic.get('viewer_toc_isvisible', False)
def config(defaults=None):
desc = _('Options to control the ebook viewer')