diff --git a/src/calibre/customize/__init__.py b/src/calibre/customize/__init__.py
index ceeea863d3..5ab2f6b418 100644
--- a/src/calibre/customize/__init__.py
+++ b/src/calibre/customize/__init__.py
@@ -308,6 +308,10 @@ class FileTypePlugin(Plugin): # {{{
#: to the database
on_import = False
+ #: If True, this plugin is run after books are added
+ #: to the database
+ on_postimport = False
+
#: If True, this plugin is run just before a conversion
on_preprocess = False
@@ -337,6 +341,16 @@ class FileTypePlugin(Plugin): # {{{
# Default implementation does nothing
return path_to_ebook
+ def postimport(self, book_id, book_format, db):
+ '''
+ Called post import, i.e., after the book file has been added to the database.
+
+ :param book_id: Database id of the added book.
+ :param book_format: The file type of the book that was added.
+ :param db: Library database.
+ '''
+ pass # Default implementation does nothing
+
# }}}
class MetadataReaderPlugin(Plugin): # {{{
diff --git a/src/calibre/customize/ui.py b/src/calibre/customize/ui.py
index 2799ed747a..82c4f3f83c 100644
--- a/src/calibre/customize/ui.py
+++ b/src/calibre/customize/ui.py
@@ -104,14 +104,17 @@ def is_disabled(plugin):
# File type plugins {{{
_on_import = {}
+_on_postimport = {}
_on_preprocess = {}
_on_postprocess = {}
def reread_filetype_plugins():
global _on_import
+ global _on_postimport
global _on_preprocess
global _on_postprocess
_on_import = {}
+ _on_postimport = {}
_on_preprocess = {}
_on_postprocess = {}
@@ -122,6 +125,10 @@ def reread_filetype_plugins():
if not _on_import.has_key(ft):
_on_import[ft] = []
_on_import[ft].append(plugin)
+ if plugin.on_postimport:
+ if not _on_postimport.has_key(ft):
+ _on_postimport[ft] = []
+ _on_postimport[ft].append(plugin)
if plugin.on_preprocess:
if not _on_preprocess.has_key(ft):
_on_preprocess[ft] = []
@@ -163,6 +170,22 @@ run_plugins_on_preprocess = functools.partial(_run_filetype_plugins,
occasion='preprocess')
run_plugins_on_postprocess = functools.partial(_run_filetype_plugins,
occasion='postprocess')
+
+def run_plugins_on_postimport(db, book_id, fmt):
+ customization = config['plugin_customization']
+ fmt = fmt.lower()
+ for plugin in _on_postimport.get(fmt, []):
+ if is_disabled(plugin):
+ continue
+ plugin.site_customization = customization.get(plugin.name, '')
+ with plugin:
+ try:
+ plugin.postimport(book_id, fmt, db)
+ except:
+ print ('Running file type plugin %s failed with traceback:'%
+ plugin.name)
+ traceback.print_exc()
+
# }}}
# Plugin customization {{{
diff --git a/src/calibre/ebooks/oeb/iterator/book.py b/src/calibre/ebooks/oeb/iterator/book.py
index 70d6cea67f..15bf24c35d 100644
--- a/src/calibre/ebooks/oeb/iterator/book.py
+++ b/src/calibre/ebooks/oeb/iterator/book.py
@@ -49,8 +49,9 @@ class EbookIterator(BookmarksMixin):
CHARACTERS_PER_PAGE = 1000
- def __init__(self, pathtoebook, log=None):
+ def __init__(self, pathtoebook, log=None, exclude_cover=False):
self.log = log or default_log
+ self.exclude_cover = exclude_cover
pathtoebook = pathtoebook.strip()
self.pathtoebook = os.path.abspath(pathtoebook)
self.config = DynamicConfig(name='iterator')
@@ -142,16 +143,23 @@ class EbookIterator(BookmarksMixin):
self.log.warn('Missing spine item:', repr(spath))
cover = self.opf.cover
- if cover and self.ebook_ext in {'lit', 'mobi', 'prc', 'opf', 'fb2',
- 'azw', 'azw3'}:
- cfile = os.path.join(self.base, 'calibre_iterator_cover.html')
- rcpath = os.path.relpath(cover, self.base).replace(os.sep, '/')
- chtml = (TITLEPAGE%prepare_string_for_xml(rcpath, True)).encode('utf-8')
- with open(cfile, 'wb') as f:
- f.write(chtml)
- self.spine[0:0] = [Spiny(cfile,
- mime_type='application/xhtml+xml')]
- self.delete_on_exit.append(cfile)
+ if cover:
+ if not self.exclude_cover and self.ebook_ext in {
+ 'lit', 'mobi', 'prc', 'opf', 'fb2', 'azw', 'azw3'}:
+ cfile = os.path.join(self.base, 'calibre_iterator_cover.html')
+ rcpath = os.path.relpath(cover, self.base).replace(os.sep, '/')
+ chtml = (TITLEPAGE%prepare_string_for_xml(rcpath, True)).encode('utf-8')
+ with open(cfile, 'wb') as f:
+ f.write(chtml)
+ self.spine[0:0] = [Spiny(cfile,
+ mime_type='application/xhtml+xml')]
+ self.delete_on_exit.append(cfile)
+ elif self.exclude_cover and self.ebook_ext == 'epub':
+ try:
+ if (len(self.spine) > 1 and self.spine.index(cover) == 0):
+ self.spine = self.spine[1:]
+ except ValueError:
+ pass
if self.opf.path_to_html_toc is not None and \
self.opf.path_to_html_toc not in self.spine:
diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py
index 895bb5a6b2..14776d1363 100644
--- a/src/calibre/gui2/__init__.py
+++ b/src/calibre/gui2/__init__.py
@@ -93,6 +93,7 @@ gprefs.defaults['tag_browser_dont_collapse'] = []
gprefs.defaults['edit_metadata_single_layout'] = 'default'
gprefs.defaults['default_author_link'] = 'http://en.wikipedia.org/w/index.php?search={author}'
gprefs.defaults['preserve_date_on_ctl'] = True
+gprefs.defaults['manual_add_auto_convert'] = False
gprefs.defaults['cb_fullscreen'] = False
gprefs.defaults['worker_max_time'] = 0
gprefs.defaults['show_files_after_save'] = True
diff --git a/src/calibre/gui2/actions/view.py b/src/calibre/gui2/actions/view.py
index 5a7a991607..fe7ef72986 100644
--- a/src/calibre/gui2/actions/view.py
+++ b/src/calibre/gui2/actions/view.py
@@ -267,8 +267,8 @@ class ViewAction(InterfaceAction):
def _view_books(self, rows):
if not rows or len(rows) == 0:
- self._launch_viewer()
- return
+ return error_dialog(self.gui, _('Cannot view'),
+ _('No books selected'), show=True)
if not self._view_check(len(rows)):
return
diff --git a/src/calibre/gui2/add.py b/src/calibre/gui2/add.py
index 234c72abf4..2b817b663a 100644
--- a/src/calibre/gui2/add.py
+++ b/src/calibre/gui2/add.py
@@ -42,6 +42,7 @@ class DuplicatesAdder(QObject): # {{{
# here we add all the formats for dupe book record created above
self.db_adder.add_formats(id, formats)
self.db_adder.number_of_books_added += 1
+ self.db_adder.auto_convert_books.add(id)
self.count += 1
self.added.emit(self.count)
single_shot(self.add_one)
@@ -107,8 +108,16 @@ class DBAdder(QObject): # {{{
self.input_queue = Queue()
self.output_queue = Queue()
self.merged_books = set([])
+ self.auto_convert_books = set()
def end(self):
+ if (gprefs['manual_add_auto_convert'] and
+ self.auto_convert_books):
+ from calibre.gui2.ui import get_gui
+ gui = get_gui()
+ gui.iactions['Convert Books'].auto_convert_auto_add(
+ self.auto_convert_books)
+
self.input_queue.put((None, None, None))
def start(self):
@@ -152,7 +161,6 @@ class DBAdder(QObject): # {{{
fmts[-1] = fmt
return fmts
-
def add(self, id, opf, cover, name):
formats = self.ids.pop(id)
if opf.endswith('.error'):
@@ -219,6 +227,7 @@ class DBAdder(QObject): # {{{
self.duplicates.append((mi, cover, orig_formats))
else:
self.add_formats(id_, formats)
+ self.auto_convert_books.add(id_)
self.number_of_books_added += 1
else:
self.names.append(name)
diff --git a/src/calibre/gui2/preferences/adding.py b/src/calibre/gui2/preferences/adding.py
index fafc5b5a1c..266b0ca9cc 100644
--- a/src/calibre/gui2/preferences/adding.py
+++ b/src/calibre/gui2/preferences/adding.py
@@ -28,6 +28,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
r('swap_author_names', prefs)
r('add_formats_to_existing', prefs)
r('preserve_date_on_ctl', gprefs)
+ r('manual_add_auto_convert', gprefs)
choices = [
(_('Ignore duplicate incoming formats'), 'ignore'),
(_('Overwrite existing duplicate formats'), 'overwrite'),
diff --git a/src/calibre/gui2/preferences/adding.ui b/src/calibre/gui2/preferences/adding.ui
index abf5d5f7a5..bd06470383 100644
--- a/src/calibre/gui2/preferences/adding.ui
+++ b/src/calibre/gui2/preferences/adding.ui
@@ -6,7 +6,7 @@
0
0
- 753
+ 1035
547
@@ -24,6 +24,36 @@
The Add &Process
+ -
+
+
+ Automerge: If books with similar titles and authors found, merge the incoming formats automatically into
+existing book records. The box to the right controls what happens when an existing record already has
+the incoming format. Note that this option also affects the Copy to library action.
+
+Title match ignores leading indefinite articles ("the", "a", "an"), punctuation, case, etc. Author match is exact.
+
+
+ &Automerge added books if they already exist in the calibre library:
+
+
+
+ -
+
+
+ Automerge: If books with similar titles and authors found, merge the incoming formats automatically into
+existing book records. This box controls what happens when an existing record already has
+the incoming format:
+
+Ignore duplicate incoming files - means that existing files in your calibre library will not be replaced
+Overwrite existing duplicate files - means that existing files in your calibre library will be replaced
+Create new record for each duplicate file - means that a new book entry will be created for each duplicate file
+
+Title matching ignores leading indefinite articles ("the", "a", "an"), punctuation, case, etc.
+Author matching is exact.
+
+
+
-
@@ -68,44 +98,7 @@
- -
-
-
- When using the "&Copy to library" action to copy books between libraries, preserve the date
-
-
-
- -
-
-
- Automerge: If books with similar titles and authors found, merge the incoming formats automatically into
-existing book records. The box to the right controls what happens when an existing record already has
-the incoming format. Note that this option also affects the Copy to library action.
-
-Title match ignores leading indefinite articles ("the", "a", "an"), punctuation, case, etc. Author match is exact.
-
-
- &Automerge added books if they already exist in the calibre library:
-
-
-
- -
-
-
- Automerge: If books with similar titles and authors found, merge the incoming formats automatically into
-existing book records. This box controls what happens when an existing record already has
-the incoming format:
-
-Ignore duplicate incoming files - means that existing files in your calibre library will not be replaced
-Overwrite existing duplicate files - means that existing files in your calibre library will be replaced
-Create new record for each duplicate file - means that a new book entry will be created for each duplicate file
-
-Title matching ignores leading indefinite articles ("the", "a", "an"), punctuation, case, etc.
-Author matching is exact.
-
-
-
- -
+
-
&Tags to apply when adding a book:
@@ -115,14 +108,14 @@ Author matching is exact.
- -
+
-
A comma-separated list of tags that will be applied to books added to the library
- -
+
-
&Configure metadata from file name
@@ -144,6 +137,20 @@ Author matching is exact.
+ -
+
+
+ When using the "&Copy to library" action to copy books between libraries, preserve the date
+
+
+
+ -
+
+
+ Automatically &convert added books to the current output format
+
+
+
diff --git a/src/calibre/gui2/viewer/documentview.py b/src/calibre/gui2/viewer/documentview.py
index 185308202e..d528b1c0d8 100644
--- a/src/calibre/gui2/viewer/documentview.py
+++ b/src/calibre/gui2/viewer/documentview.py
@@ -16,6 +16,7 @@ from PyQt4.QtWebKit import QWebPage, QWebView, QWebSettings
from calibre.gui2.viewer.flip import SlideFlip
from calibre.gui2.shortcuts import Shortcuts
+from calibre.gui2 import open_url
from calibre import prints
from calibre.customize.ui import all_viewer_plugins
from calibre.gui2.viewer.keys import SHORTCUTS
@@ -481,7 +482,12 @@ class DocumentView(QWebView): # {{{
d = self.document
self.unimplemented_actions = list(map(self.pageAction,
[d.DownloadImageToDisk, d.OpenLinkInNewWindow, d.DownloadLinkToDisk,
- d.OpenImageInNewWindow, d.OpenLink, d.Reload]))
+ d.OpenImageInNewWindow, d.OpenLink, d.Reload, d.InspectElement]))
+
+ self.search_online_action = QAction(QIcon(I('search.png')), '', self)
+ self.search_online_action.setShortcut(Qt.CTRL+Qt.Key_E)
+ self.search_online_action.triggered.connect(self.search_online)
+ self.addAction(self.search_online_action)
self.dictionary_action = QAction(QIcon(I('dictionary.png')),
_('&Lookup in dictionary'), self)
self.dictionary_action.setShortcut(Qt.CTRL+Qt.Key_L)
@@ -526,6 +532,10 @@ class DocumentView(QWebView): # {{{
self.goto_location_action.setMenu(self.goto_location_menu)
self.grabGesture(Qt.SwipeGesture)
+ self.restore_fonts_action = QAction(_('Normal font size'), self)
+ self.restore_fonts_action.setCheckable(True)
+ self.restore_fonts_action.triggered.connect(self.restore_font_size)
+
def goto_next_section(self, *args):
if self.manager is not None:
self.manager.goto_next_section()
@@ -582,6 +592,15 @@ class DocumentView(QWebView): # {{{
if self.manager is not None:
self.manager.selection_changed(unicode(self.document.selectedText()))
+ def _selectedText(self):
+ t = unicode(self.selectedText()).strip()
+ if not t:
+ return u''
+ if len(t) > 40:
+ t = t[:40] + u'...'
+ t = t.replace(u'&', u'&&')
+ return _("S&earch Google for '%s'")%t
+
def contextMenuEvent(self, ev):
mf = self.document.mainFrame()
r = mf.hitTestContent(ev.pos())
@@ -591,17 +610,48 @@ class DocumentView(QWebView): # {{{
menu = self.document.createStandardContextMenu()
for action in self.unimplemented_actions:
menu.removeAction(action)
- text = unicode(self.selectedText())
- if text:
- menu.insertAction(list(menu.actions())[0], self.dictionary_action)
- menu.insertAction(list(menu.actions())[0], self.search_action)
+
if not img.isNull():
menu.addAction(self.view_image_action)
- menu.addSeparator()
- menu.addAction(self.goto_location_action)
- if self.document.in_fullscreen_mode and self.manager is not None:
+
+ text = self._selectedText()
+ if text and img.isNull():
+ self.search_online_action.setText(text)
+ menu.addAction(self.search_online_action)
+ menu.addAction(self.dictionary_action)
+ menu.addAction(self.search_action)
+
+ if not text and img.isNull():
menu.addSeparator()
- menu.addAction(self.manager.toggle_toolbar_action)
+ if self.manager.action_back.isEnabled():
+ menu.addAction(self.manager.action_back)
+ if self.manager.action_forward.isEnabled():
+ menu.addAction(self.manager.action_forward)
+ menu.addAction(self.goto_location_action)
+
+ if self.manager is not None:
+ menu.addSeparator()
+ menu.addAction(self.manager.action_table_of_contents)
+
+ menu.addSeparator()
+ menu.addAction(self.manager.action_font_size_larger)
+ self.restore_fonts_action.setChecked(self.multiplier == 1)
+ menu.addAction(self.restore_fonts_action)
+ menu.addAction(self.manager.action_font_size_smaller)
+
+ menu.addSeparator()
+ inspectAction = self.pageAction(self.document.InspectElement)
+ menu.addAction(inspectAction)
+
+ if not text and img.isNull() and self.manager is not None:
+ menu.addSeparator()
+ if self.document.in_fullscreen_mode and self.manager is not None:
+ menu.addAction(self.manager.toggle_toolbar_action)
+ menu.addAction(self.manager.action_full_screen)
+
+ menu.addSeparator()
+ menu.addAction(self.manager.action_quit)
+
menu.exec_(ev.globalPos())
def lookup(self, *args):
@@ -616,6 +666,12 @@ class DocumentView(QWebView): # {{{
if t:
self.manager.search.set_search_string(t)
+ def search_online(self):
+ t = unicode(self.selectedText()).strip()
+ if t:
+ url = 'https://www.google.com/search?q=' + QUrl().toPercentEncoding(t)
+ open_url(QUrl.fromEncoded(url))
+
def set_manager(self, manager):
self.manager = manager
self.scrollbar = manager.horizontal_scrollbar
@@ -993,6 +1049,11 @@ class DocumentView(QWebView): # {{{
self.multiplier -= amount
return self.document.scroll_fraction
+ def restore_font_size(self):
+ with self.document.page_position:
+ self.multiplier = 1
+ return self.document.scroll_fraction
+
def changeEvent(self, event):
if event.type() == event.EnabledChange:
self.update()
diff --git a/src/calibre/gui2/viewer/main.py b/src/calibre/gui2/viewer/main.py
index 7b624f170a..9d5049d25a 100644
--- a/src/calibre/gui2/viewer/main.py
+++ b/src/calibre/gui2/viewer/main.py
@@ -178,6 +178,7 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
self.pending_restore = False
self.existing_bookmarks= []
self.selected_text = None
+ self.was_maximized = False
self.read_settings()
self.dictionary_box.hide()
self.close_dictionary_view.clicked.connect(lambda
@@ -207,7 +208,7 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
self.view.set_manager(self)
self.pi = ProgressIndicator(self)
self.toc.setVisible(False)
- self.action_quit = QAction(self)
+ self.action_quit = QAction(_('&Quit'), self)
self.addAction(self.action_quit)
self.view_resized_timer = QTimer(self)
self.view_resized_timer.timeout.connect(self.viewport_resize_finished)
@@ -299,6 +300,7 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
''')
self.window_mode_changed = None
self.toggle_toolbar_action = QAction(_('Show/hide controls'), self)
+ self.toggle_toolbar_action.setCheckable(True)
self.toggle_toolbar_action.triggered.connect(self.toggle_toolbars)
self.addAction(self.toggle_toolbar_action)
self.full_screen_label_anim = QPropertyAnimation(
@@ -421,7 +423,8 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
def save_state(self):
state = bytearray(self.saveState(self.STATE_VERSION))
vprefs['viewer_toolbar_state'] = state
- vprefs.set('viewer_window_geometry', bytearray(self.saveGeometry()))
+ if not self.isFullScreen():
+ vprefs.set('viewer_window_geometry', bytearray(self.saveGeometry()))
if self.current_book_has_toc:
vprefs.set('viewer_toc_isvisible', bool(self.toc.isVisible()))
if self.toc.isVisible():
@@ -488,6 +491,7 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
self.window_mode_changed = 'fullscreen'
self.tool_bar.setVisible(False)
self.tool_bar2.setVisible(False)
+ self.was_maximized = self.isMaximized()
if not self.view.document.fullscreen_scrollbar:
self.vertical_scrollbar.setVisible(False)
self.frame.layout().setSpacing(0)
@@ -574,7 +578,10 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
om = self._original_frame_margins
self.centralwidget.layout().setContentsMargins(om[0])
self.frame.layout().setContentsMargins(om[1])
- super(EbookViewer, self).showNormal()
+ if self.was_maximized:
+ super(EbookViewer, self).showMaximized()
+ else:
+ super(EbookViewer, self).showNormal()
def handle_window_mode_toggle(self):
if self.window_mode_changed:
@@ -681,13 +688,9 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
def font_size_larger(self):
self.view.magnify_fonts()
- self.action_font_size_larger.setEnabled(self.view.multiplier < 3)
- self.action_font_size_smaller.setEnabled(self.view.multiplier > 0.2)
def font_size_smaller(self):
self.view.shrink_fonts()
- self.action_font_size_larger.setEnabled(self.view.multiplier < 3)
- self.action_font_size_smaller.setEnabled(self.view.multiplier > 0.2)
def magnification_changed(self, val):
tt = _('Make font size %(which)s\nCurrent magnification: %(mag).1f')
@@ -695,6 +698,8 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
tt %dict(which=_('larger'), mag=val))
self.action_font_size_smaller.setToolTip(
tt %dict(which=_('smaller'), mag=val))
+ self.action_font_size_larger.setEnabled(self.view.multiplier < 3)
+ self.action_font_size_smaller.setEnabled(self.view.multiplier > 0.2)
def find(self, text, repeat=False, backwards=False):
if not text:
diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py
index 5952e11e57..ab85421697 100644
--- a/src/calibre/library/database2.py
+++ b/src/calibre/library/database2.py
@@ -28,7 +28,8 @@ from calibre.ebooks.metadata.book.base import Metadata
from calibre.constants import preferred_encoding, iswindows, filesystem_encoding
from calibre.ptempfile import (PersistentTemporaryFile,
base_dir, SpooledTemporaryFile)
-from calibre.customize.ui import run_plugins_on_import
+from calibre.customize.ui import (run_plugins_on_import,
+ run_plugins_on_postimport)
from calibre import isbytestring
from calibre.utils.filenames import (ascii_filename, samefile,
WindowsAtomicFolderMove, hardlink_file)
@@ -1495,8 +1496,10 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
format = os.path.splitext(npath)[-1].lower().replace('.', '').upper()
stream = lopen(npath, 'rb')
format = check_ebook_format(stream, format)
- return self.add_format(index, format, stream,
+ retval = self.add_format(index, format, stream,
index_is_id=index_is_id, path=path, notify=notify)
+ run_plugins_on_postimport(self, id, format)
+ return retval
def add_format(self, index, format, stream, index_is_id=False, path=None,
notify=True, replace=True, copy_function=None):
@@ -3475,6 +3478,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
formats, metadata = iter(formats), iter(metadata)
duplicates = []
ids = []
+ postimport = []
for path in paths:
mi = metadata.next()
self._add_newbook_tag(mi)
@@ -3506,8 +3510,11 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
format = check_ebook_format(stream, format)
self.add_format(id, format, stream, index_is_id=True)
stream.close()
+ postimport.append((id, format))
self.conn.commit()
self.data.refresh_ids(self, ids) # Needed to update format list and size
+ for book_id, fmt in postimport:
+ run_plugins_on_postimport(self, book_id, fmt)
if duplicates:
paths = list(duplicate[0] for duplicate in duplicates)
formats = list(duplicate[1] for duplicate in duplicates)