diff --git a/src/calibre/gui2/actions/save_to_disk.py b/src/calibre/gui2/actions/save_to_disk.py index 8fb9146689..d1e056b65a 100644 --- a/src/calibre/gui2/actions/save_to_disk.py +++ b/src/calibre/gui2/actions/save_to_disk.py @@ -73,8 +73,10 @@ class SaveToDiskAction(InterfaceAction): self.save_to_disk(False, single_dir=True, single_format=prefs['output_format']) - def save_to_disk(self, checked, single_dir=False, single_format=None): - rows = self.gui.current_view().selectionModel().selectedRows() + def save_to_disk(self, checked, single_dir=False, single_format=None, + rows=None, write_opf=None, save_cover=None): + if rows is None: + rows = self.gui.current_view().selectionModel().selectedRows() if not rows or len(rows) == 0: return error_dialog(self.gui, _('Cannot save to disk'), _('No books selected'), show=True) @@ -105,6 +107,10 @@ class SaveToDiskAction(InterfaceAction): opts.write_opf = False opts.template = opts.send_template opts.single_dir = single_dir + if write_opf is not None: + opts.write_opf = write_opf + if save_cover is not None: + opts.save_cover = save_cover self._saver = Saver(self.gui, self.gui.library_view.model().db, Dispatcher(self._books_saved), rows, path, opts, spare_server=self.gui.spare_server) @@ -114,6 +120,13 @@ class SaveToDiskAction(InterfaceAction): self.gui.device_manager.save_books( Dispatcher(self.books_saved), paths, path) + def save_library_format_by_ids(self, book_ids, fmt, single_dir=True): + if isinstance(book_ids, int): + book_ids = [book_ids] + rows = list(self.gui.library_view.ids_to_rows(book_ids).itervalues()) + rows = [self.gui.library_view.model().index(r, 0) for r in rows] + self.save_to_disk(True, single_dir=single_dir, single_format=fmt, + rows=rows, write_opf=False, save_cover=False) def _books_saved(self, path, failures, error): self._saver = None diff --git a/src/calibre/gui2/book_details.py b/src/calibre/gui2/book_details.py index acf5a8927f..3e20f8c67c 100644 --- a/src/calibre/gui2/book_details.py +++ b/src/calibre/gui2/book_details.py @@ -383,6 +383,7 @@ class BookInfo(QWebView): link_clicked = pyqtSignal(object) remove_format = pyqtSignal(int, object) + save_format = pyqtSignal(int, object) def __init__(self, vertical, parent=None): QWebView.__init__(self, parent) @@ -396,16 +397,23 @@ class BookInfo(QWebView): palette.setBrush(QPalette.Base, Qt.transparent) self.page().setPalette(palette) self.css = P('templates/book_details.css', data=True).decode('utf-8') - self.remove_format_action = QAction(QIcon(I('trash.png')), - '', self) - self.remove_format_action.current_fmt = None - self.remove_format_action.triggered.connect(self.remove_format_triggerred) + for x, icon in [('remove', 'trash.png'), ('save', 'save.png')]: + ac = QAction(QIcon(I(icon)), '', self) + ac.current_fmt = None + ac.triggered.connect(getattr(self, '%s_format_triggerred'%x)) + setattr(self, '%s_format_action'%x, ac) - def remove_format_triggerred(self): - f = self.remove_format_action.current_fmt + def context_action_triggered(self, which): + f = getattr(self, '%s_format_action'%which).current_fmt if f: book_id, fmt = f - self.remove_format.emit(book_id, fmt) + getattr(self, '%s_format'%which).emit(book_id, fmt) + + def remove_format_triggerred(self): + self.context_action_triggered('remove') + + def save_format_triggerred(self): + self.context_action_triggered('save') def link_activated(self, link): self._link_clicked = True @@ -449,10 +457,12 @@ class BookInfo(QWebView): import traceback traceback.print_exc() else: - self.remove_format_action.current_fmt = (book_id, fmt) - self.remove_format_action.setText(_('Delete the %s format')%parts[ - 2]) - menu.addAction(self.remove_format_action) + for a, t in [('remove', _('Delete the %s format')), + ('save', _('Save the %s format to disk'))]: + ac = getattr(self, '%s_format_action'%a) + ac.current_fmt = (book_id, fmt) + ac.setText(t%parts[2]) + menu.addAction(ac) if len(menu.actions()) > 0: menu.exec_(ev.globalPos()) @@ -551,6 +561,7 @@ class BookDetails(QWidget): # {{{ open_containing_folder = pyqtSignal(int) view_specific_format = pyqtSignal(int, object) remove_specific_format = pyqtSignal(int, object) + save_specific_format = pyqtSignal(int, object) remote_file_dropped = pyqtSignal(object, object) files_dropped = pyqtSignal(object, object) cover_changed = pyqtSignal(object, object) @@ -618,6 +629,7 @@ class BookDetails(QWidget): # {{{ self._layout.addWidget(self.book_info) self.book_info.link_clicked.connect(self.handle_click) self.book_info.remove_format.connect(self.remove_specific_format) + self.book_info.save_format.connect(self.save_specific_format) self.setCursor(Qt.PointingHandCursor) def handle_click(self, link): diff --git a/src/calibre/gui2/init.py b/src/calibre/gui2/init.py index 9b63cd5ed8..a82dfec7fc 100644 --- a/src/calibre/gui2/init.py +++ b/src/calibre/gui2/init.py @@ -267,6 +267,8 @@ class LayoutMixin(object): # {{{ self.book_details.view_specific_format.connect(self.iactions['View'].view_format_by_id) self.book_details.remove_specific_format.connect( self.iactions['Remove Books'].remove_format_by_id) + self.book_details.save_specific_format.connect( + self.iactions['Save To Disk'].save_library_format_by_ids) m = self.library_view.model() if m.rowCount(None) > 0: diff --git a/src/calibre/gui2/library/views.py b/src/calibre/gui2/library/views.py index 59d3b89770..f8dc83273c 100644 --- a/src/calibre/gui2/library/views.py +++ b/src/calibre/gui2/library/views.py @@ -8,6 +8,7 @@ __docformat__ = 'restructuredtext en' import os, itertools, operator from functools import partial from future_builtins import map +from collections import OrderedDict from PyQt4.Qt import (QTableView, Qt, QAbstractItemView, QMenu, pyqtSignal, QModelIndex, QIcon, QItemSelection, QMimeData, QDrag, QApplication, @@ -793,6 +794,17 @@ class BooksView(QTableView): # {{{ sm = self.selectionModel() sm.select(index, sm.ClearAndSelect|sm.Rows) + def ids_to_rows(self, ids): + row_map = OrderedDict() + ids = frozenset(ids) + m = self.model() + for row in xrange(m.rowCount(QModelIndex())): + if len(row_map) >= len(ids): break + c = m.id(row) + if c in ids: + row_map[c] = row + return row_map + def select_rows(self, identifiers, using_ids=True, change_current=True, scroll=True): '''