def show_disclaimer(self): confirm( ( "<p>" + _( "Calibre helps you find the ebooks you want by searching " "the websites of various commercial and public domain " "book sources for you." ) + "<p>" + _( "Using the integrated search you can easily find which " "store has the book you are looking for, at the best price. " "You also get DRM status and other useful information." ) + "<p>" + _( "All transactions (paid or otherwise) are handled between " "you and the book seller. " "Calibre is not part of this process and any issues related " "to a purchase should be directed to the website you are " "buying from. Be sure to double check that any books you get " "will work with your e-book reader, especially if the book you " "are buying has " '<a href="http://drmfree.calibre-ebook.com/about#drm">DRM</a>.' ) ), "about_get_books_msg", parent=self.gui, show_cancel_button=False, confirm_msg=_("Show this message again"), pixmap="dialog_information.png", title=_("About Get Books"), )
def _add_formats(self, paths, ids): if len(ids) > 1 and not question_dialog( self.gui, _('Are you sure?'), _('Are you sure you want to add the same' ' files to all %d books? If the format' ' already exists for a book, it will be replaced.')%len(ids)): return db = self.gui.current_db if len(ids) == 1: formats = db.formats(ids[0], index_is_id=True) if formats: formats = {x.upper() for x in formats.split(',')} nformats = {f.rpartition('.')[-1].upper() for f in paths} override = formats.intersection(nformats) if override: title = db.title(ids[0], index_is_id=True) msg = ngettext( 'The {0} format will be replaced in the book {1}. Are you sure?', 'The {0} formats will be replaced in the book {1}. Are you sure?', len(override)).format(', '.join(override), title) if not confirm(msg, 'confirm_format_override_on_add', title=_('Are you sure?'), parent=self.gui): return fmt_map = {os.path.splitext(fpath)[1][1:].upper():fpath for fpath in paths} for id_ in ids: for fmt, fpath in iteritems(fmt_map): if fmt: db.add_format_with_hooks(id_, fmt, fpath, index_is_id=True, notify=True) current_idx = self.gui.library_view.currentIndex() if current_idx.isValid(): self.gui.library_view.model().current_changed(current_idx, current_idx)
def mark_groups_as_duplicate_exemptions(self, all_groups): can_exempt = self.duplicate_finder.check_can_mark_exemption(all_groups) if can_exempt: # Ensure that the selection is moved onto the current duplicate group duplicate_ids = self.duplicate_finder.get_current_duplicate_group_ids() self.gui.library_view.select_rows(duplicate_ids) exemption_type = 'books' if self.duplicate_finder.is_searching_for_authors(): exemption_type = 'authors' dialog_name = 'find_duplicates_mark_all_groups' if all_groups else 'find_duplicates_mark_group' if not confirm('<p>' + _( 'This action will ensure that each of the %s in the group ' 'are exempt from appearing together again in future.<p>' 'Are you <b>sure</b> you want to proceed?')%exemption_type, dialog_name, self.gui): return if all_groups: self.duplicate_finder.mark_groups_as_duplicate_exemptions() else: self.duplicate_finder.mark_current_group_as_duplicate_exemptions() else: info_dialog(self.gui, _('No duplicates in group'), _('There are no duplicates remaining in this group.'), show=True, show_copy_button=False) self.update_actions_enabled()
def _save_settings(self): from calibre.gui2.dialogs.confirm_delete import confirm message = '<p>' + _('Are you sure you want to save this setting in this library for this plugin?') + '</p>' \ + '<p>' + _('Any settings in other libraries or stored in a JSON file in your calibre plugins folder will not be touched.') + '</p>' \ + '<p>' + _('You must restart calibre afterwards.') + '</p>' if not confirm(message, self.namespace+'_save_settings', self): return ns_prefix = self._get_ns_prefix() key = unicode(self.keys_list.currentItem().text()) self.db.prefs.set_namespaced(self.namespace, key, self.db.prefs.raw_to_object(self.value_text.toPlainText())) d = info_dialog(self, 'Settings saved', '<p>' + _('All settings for this plugin in this library have been saved.') + '</p>' \ + '<p>' + _('Please restart calibre now.') + '</p>', show_copy_button=False) b = d.bb.addButton(_('Restart calibre now'), d.bb.AcceptRole) b.setIcon(QIcon(I('lt.png'))) d.do_restart = False def rf(): d.do_restart = True b.clicked.connect(rf) d.set_details('') d.exec_() b.clicked.disconnect() self.close() if d.do_restart: self.gui.quit(restart=True)
def start_download(self, request): if not self.gui: return url = unicode(request.url().toString(NO_URL_FORMATTING)) cf = self.get_cookies() filename = get_download_filename(url, cf) ext = os.path.splitext(filename)[1][1:].lower() filename = ascii_filename(filename[:60] + '.' + ext) if ext not in BOOK_EXTENSIONS: if ext == 'acsm': from calibre.gui2.dialogs.confirm_delete import confirm if not confirm('<p>' + _('This ebook is a DRMed EPUB file. ' 'You will be prompted to save this file to your ' 'computer. Once it is saved, open it with ' '<a href="https://www.adobe.com/products/digitaleditions/">' 'Adobe Digital Editions</a> (ADE).<p>ADE, in turn ' 'will download the actual ebook, which will be a ' '.epub file. You can add this book to calibre ' 'using "Add Books" and selecting the file from ' 'the ADE library folder.'), 'acsm_download', self): return name = choose_save_file(self, 'web-store-download-unknown', _('File is not a supported ebook type. Save to disk?'), initial_filename=filename) if name: self.gui.download_ebook(url, cf, name, name, False, create_browser=self.create_browser) else: show_download_info(filename, self) self.gui.download_ebook(url, cf, filename, tags=self.tags, create_browser=self.create_browser)
def confirm_large_merge(self, num): if num < 5: return True return confirm('<p>'+_( 'You are about to merge very many ({}) books. ' 'Are you <b>sure</b> you want to proceed?').format(num) + '</p>', 'merge_too_many_books', self.gui)
def _clear_settings(self): from calibre.gui2.dialogs.confirm_delete import confirm message = '<p>Are you sure you want to clear your settings in this library for this plugin?</p>' \ '<p>Any settings in other libraries or stored in a JSON file in your calibre plugins ' \ 'folder will not be touched.</p>' \ '<p>You must restart calibre afterwards.</p>' if not confirm(message, self.namespace+'_clear_settings', self): return ns_prefix = self._get_ns_prefix() keys = [k for k in self.db.prefs.iterkeys() if k.startswith(ns_prefix)] for k in keys: del self.db.prefs[k] self._populate_settings() d = info_dialog(self, 'Settings deleted', '<p>All settings for this plugin in this library have been cleared.</p>' '<p>Please restart calibre now.</p>', show_copy_button=False) b = d.bb.addButton(_('Restart calibre now'), d.bb.AcceptRole) b.setIcon(QIcon(I('lt.png'))) d.do_restart = False def rf(): d.do_restart = True b.clicked.connect(rf) d.set_details('') d.exec_() b.clicked.disconnect() self.close() if d.do_restart: self.gui.quit(restart=True)
def start_download(self, request): if not self.gui: return url = unicode(request.url().toString()) cf = self.get_cookies() filename = get_download_filename(url, cf) ext = os.path.splitext(filename)[1][1:].lower() filename = ascii_filename(filename[:60] + '.' + ext) if ext not in BOOK_EXTENSIONS: if ext == 'acsm': from calibre.gui2.dialogs.confirm_delete import confirm if not confirm('<p>' + _('This ebook is a DRMed EPUB file. ' 'You will be prompted to save this file to your ' 'computer. Once it is saved, open it with ' '<a href="http://www.adobe.com/products/digitaleditions/">' 'Adobe Digital Editions</a> (ADE).<p>ADE, in turn ' 'will download the actual ebook, which will be a ' '.epub file. You can add this book to calibre ' 'using "Add Books" and selecting the file from ' 'the ADE library folder.'), 'acsm_download', self): return home = os.path.expanduser('~') name = QFileDialog.getSaveFileName(self, _('File is not a supported ebook type. Save to disk?'), os.path.join(home, filename), '*.*') if name: name = unicode(name) self.gui.download_ebook(url, cf, name, name, False) else: self.gui.download_ebook(url, cf, filename, tags=self.tags)
def delete_books(self, *args): ''' Delete selected books from device or library. ''' view = self.gui.current_view() rows = view.selectionModel().selectedRows() if not rows or len(rows) == 0: return # Library view is visible. if self.gui.stack.currentIndex() == 0: to_delete_ids = [view.model().id(r) for r in rows] self.do_library_delete(to_delete_ids) # Device view is visible. else: if self.gui.stack.currentIndex() == 1: view = self.gui.memory_view elif self.gui.stack.currentIndex() == 2: view = self.gui.card_a_view else: view = self.gui.card_b_view paths = view.model().paths(rows) ids = view.model().indices(rows) if not confirm('<p>'+_('The %d selected book(s) will be ' '<b>permanently deleted</b> ' 'from your device. Are you sure?')%len(paths) +'</p>', 'device_delete_books', self.gui): return job = self.gui.remove_paths(paths) self.delete_memory[job] = (paths, view.model()) view.model().mark_for_deletion(job, ids, rows_are_ids=True) self.gui.status_bar.show_message(_('Deleting books from device.'), 1000)
def delete_tags(self): deletes = self.table.selectedItems() if not deletes: error_dialog(self, _('No items selected'), _('You must select at least one item from the list.')).exec_() return to_del = [] to_undel = [] for item in deletes: if item.is_deleted: to_undel.append(item) else: to_del.append(item) if to_del: ct = ', '.join([unicode_type(item.text()) for item in to_del]) if not confirm( '<p>'+_('Are you sure you want to delete the following items?')+'<br>'+ct, 'tag_list_editor_delete'): return if to_undel: ct = ', '.join([unicode_type(item.text()) for item in to_undel]) if not confirm( '<p>'+_('Are you sure you want to undelete the following items?')+'<br>'+ct, 'tag_list_editor_undelete'): return row = self.table.row(deletes[0]) for item in deletes: if item.is_deleted: item.set_is_deleted(False) self.to_delete.discard(int(item.data(Qt.UserRole))) orig = self.table.item(item.row(), 2) self.table.blockSignals(True) orig.setData(Qt.DisplayRole, '') self.table.blockSignals(False) else: id = int(item.data(Qt.UserRole)) self.to_delete.add(id) item.set_is_deleted(True) orig = self.table.item(item.row(), 2) self.table.blockSignals(True) orig.setData(Qt.DisplayRole, item.initial_text()) self.table.blockSignals(False) if row >= self.table.rowCount(): row = self.table.rowCount() - 1 if row >= 0: self.table.scrollToItem(self.table.item(row, 0))
def _edit_settings(self): from calibre.gui2.dialogs.confirm_delete import confirm message = '<p>' + _('Are you sure you want to edit settings in this library for this plugin?') + '</p>' \ + '<p>' + _('The FanFicFare team does not support hand edited configurations.') + '</p>' if confirm(message, self.namespace+'_edit_settings', self): self.save_button.setEnabled(True) self.edit_button.setEnabled(False) self.value_text.setReadOnly(False)
def remove_vl_triggered(self, name=None): if not confirm( _("Are you sure you want to remove the virtual library <b>{0}</b>?").format(name), "confirm_vl_removal", parent=self, ): return self._remove_vl(name, reapply=True)
def reject(self): if self.next_called and not confirm(_( 'All reviewed changes will be lost! Are you sure you want to Cancel?'), 'confirm-metadata-diff-dialog-cancel'): return gprefs.set('diff_dialog_geom', bytearray(self.saveGeometry())) self.compare_widget.save_comments_controls_state() super(CompareMany, self).reject()
def _edit_settings(self): from calibre.gui2.dialogs.confirm_delete import confirm message = '<p>' + _('Are you sure you want to edit settings in this library for this plugin?') + '</p>' \ + '<p>' + _('The FanFicFare team does not support hand edited configurations.') + '</p>' if confirm(message, self.namespace + '_edit_settings', self): self.save_button.setEnabled(True) self.edit_button.setEnabled(False) self.value_text.setReadOnly(False)
def remove_vl_triggered(self, name=None): if not confirm(_( 'Are you sure you want to remove the virtual library <b>{0}</b>?' ).format(name), 'confirm_vl_removal', parent=self): return self._remove_vl(name, reapply=True)
def do_library_delete(self, to_delete_ids): view = self.gui.current_view() next_id = view.next_id # Ask the user if they want to delete the book from the library or device if it is in both. if self.gui.device_manager.is_device_present: on_device = False on_device_ids = self._get_selected_ids() for id in on_device_ids: res = self.gui.book_on_device(id) if res[0] or res[1] or res[2]: on_device = True if on_device: break if on_device: loc = confirm_location( '<p>' + _('Some of the selected books are on the attached device. ' '<b>Where</b> do you want the selected files deleted from?' ), self.gui) if not loc: return elif loc == 'dev': self.remove_matching_books_from_device() return elif loc == 'both': self.remove_matching_books_from_device() # The following will run if the selected books are not on a connected device. # The user has selected to delete from the library or the device and library. if not confirm( '<p>' + ngettext( 'The selected book will be <b>permanently deleted</b> and the files ' 'removed from your calibre library. Are you sure?', 'The {} selected books will be <b>permanently deleted</b> and the files ' 'removed from your calibre library. Are you sure?', len(to_delete_ids)).format(len(to_delete_ids)), 'library_delete_books', self.gui): return if len(to_delete_ids) < 5: try: view.model().delete_books_by_id(to_delete_ids) except IOError as err: if err.errno == errno.EACCES: import traceback fname = getattr(err, 'filename', 'file') or 'file' return error_dialog( self.gui, _('Permission denied'), _('Could not access %s. Is it being used by another' ' program? Click "Show details" for more information.' ) % fname, det_msg=traceback.format_exc(), show=True) self.library_ids_deleted2(to_delete_ids, next_id=next_id) else: self.__md = MultiDeleter( self.gui, to_delete_ids, partial(self.library_ids_deleted2, next_id=next_id))
def add_empty_format(self, format_): if self.gui.stack.currentIndex() != 0: return view = self.gui.library_view rows = view.selectionModel().selectedRows() if not rows: return error_dialog(self.gui, _('No books selected'), _('Cannot add files as no books are selected'), show=True) ids = [view.model().id(r) for r in rows] if len(ids) > 1 and not question_dialog( self.gui, _('Are you sure?'), _('Are you sure you want to add the same' ' empty file to all %d books? If the format' ' already exists for a book, it will be replaced.') % len(ids)): return db = self.gui.library_view.model().db if len(ids) == 1: formats = db.formats(ids[0], index_is_id=True) if formats: formats = {x.lower() for x in formats.split(',')} if format_ in formats: title = db.title(ids[0], index_is_id=True) msg = _( 'The {0} format will be replaced in the book {1}. Are you sure?' ).format(format_, title) if not confirm(msg, 'confirm_format_override_on_add', title=_('Are you sure?'), parent=self.gui): return for id_ in ids: from calibre.ebooks.oeb.polish.create import create_book pt = PersistentTemporaryFile(suffix='.' + format_) pt.close() try: mi = db.new_api.get_metadata(id_, get_cover=False, get_user_categories=False, cover_as_data=False) create_book(mi, pt.name, fmt=format_) db.add_format_with_hooks(id_, format_, pt.name, index_is_id=True, notify=True) finally: os.remove(pt.name) current_idx = self.gui.library_view.currentIndex() if current_idx.isValid(): view.model().current_changed(current_idx, current_idx)
def del_category(self): if self.current_cat_name is not None: if not confirm('<p>'+_('The current tag category will be ' '<b>permanently deleted</b>. Are you sure?') + '</p>', 'tag_category_delete', self): return del self.categories[self.current_cat_name] self.current_cat_name = None self.category_box.removeItem(self.category_box.currentIndex())
def del_search(self): if self.current_search_name is not None: if not confirm('<p>'+_('The current saved search will be ' '<b>permanently deleted</b>. Are you sure?') +'</p>', 'saved_search_editor_delete', self): return del self.searches[self.current_search_name] self.current_search_name = None self.search_name_box.removeItem(self.search_name_box.currentIndex())
def del_category(self): if self.current_cat_name is not None: if not confirm('<p>'+_('The current tag category will be ' '<b>permanently deleted</b>. Are you sure?') +'</p>', 'tag_category_delete', self): return del self.categories[self.current_cat_name] self.current_cat_name = None self.category_box.removeItem(self.category_box.currentIndex())
def delete_annotations(self, ids): if confirm(ngettext( 'Are you sure you want to <b>permanently</b> delete this annotation?', 'Are you sure you want to <b>permanently</b> delete these {} annotations?', len(ids)).format(len(ids)), 'delete-annotation-from-browse', parent=self ): db = current_db() db.delete_annotations(ids) self.browse_panel.refresh()
def add_formats(self, *args): if self.gui.stack.currentIndex() != 0: return view = self.gui.library_view rows = view.selectionModel().selectedRows() if not rows: return error_dialog(self.gui, _('No books selected'), _('Cannot add files as no books are selected'), show=True) ids = [view.model().id(r) for r in rows] if len(ids) > 1 and not question_dialog( self.gui, _('Are you sure'), _('Are you sure you want to add the same' ' files to all %d books? If the format' ' already exists for a book, it will be replaced.') % len(ids)): return books = choose_files(self.gui, 'add formats dialog dir', _('Select book files'), filters=get_filters()) if not books: return db = view.model().db if len(ids) == 1: formats = db.formats(ids[0], index_is_id=True) if formats: formats = {x.upper() for x in formats.split(',')} nformats = {f.rpartition('.')[-1].upper() for f in books} override = formats.intersection(nformats) if override: title = db.title(ids[0], index_is_id=True) msg = _( 'The {0} format(s) will be replaced in the book {1}. Are you sure?' ).format(', '.join(override), title) if not confirm(msg, 'confirm_format_override_on_add', title=_('Are you sure'), parent=self.gui): return for id_ in ids: for fpath in books: fmt = os.path.splitext(fpath)[1][1:].upper() if fmt: db.add_format_with_hooks(id_, fmt, fpath, index_is_id=True, notify=True) current_idx = self.gui.library_view.currentIndex() if current_idx.isValid(): view.model().current_changed(current_idx, current_idx)
def del_search(self): n = self.current_search_name if n is not None: if not confirm( '<p>' + _('The current saved search will be ' '<b>permanently deleted</b>. Are you sure?') + '</p>', 'saved_search_editor_delete', self): return self.slist.takeItem(self.slist.currentRow()) del self.searches[n]
def reject_all_remaining(self): from calibre.gui2.dialogs.confirm_delete import confirm if not confirm(_('Are you sure you want to reject all %d remaining results?') % len(self.ids), 'confirm_metadata_review_reject', parent=self): return self.next_item(False) for id_ in self.ids: oldmi, newmi = self.get_metadata(id_) self.accepted[id_] = (False, None) self.ids = [] self.accept()
def modify_plugin(self, op=''): index = self.plugin_view.currentIndex() if index.isValid(): if not index.parent().isValid(): name = str(index.data() or '') return error_dialog(self, _('Error'), '<p>'+ _('Select an actual plugin under <b>%s</b> to customize')%name, show=True, show_copy_button=False) plugin = self._plugin_model.index_to_plugin(index) if op == 'toggle': if not plugin.can_be_disabled: info_dialog(self, _('Plugin cannot be disabled'), _('Disabling the plugin %s is not allowed')%plugin.name, show=True, show_copy_button=False) return if is_disabled(plugin): enable_plugin(plugin) else: disable_plugin(plugin) self._plugin_model.refresh_plugin(plugin) self.changed_signal.emit() if op == 'customize': if not plugin.is_customizable(): info_dialog(self, _('Plugin not customizable'), _('Plugin: %s does not need customization')%plugin.name).exec_() return self.changed_signal.emit() from calibre.customize import InterfaceActionBase if isinstance(plugin, InterfaceActionBase) and not getattr(plugin, 'actual_iaction_plugin_loaded', False): return error_dialog(self, _('Must restart'), _('You must restart calibre before you can' ' configure the <b>%s</b> plugin')%plugin.name, show=True) if plugin.do_user_config(self.gui): self._plugin_model.refresh_plugin(plugin) elif op == 'remove': if not confirm('<p>' + _('Are you sure you want to remove the plugin: %s?')% '<b>{0}</b>'.format(plugin.name), 'confirm_plugin_removal_msg', parent=self): return msg = _('Plugin <b>{0}</b> successfully removed. You will have' ' to restart calibre for it to be completely removed.').format(plugin.name) if remove_plugin(plugin): self._plugin_model.beginResetModel() self._plugin_model.populate() self._plugin_model.endResetModel() self.changed_signal.emit() info_dialog(self, _('Success'), msg, show=True, show_copy_button=False) else: error_dialog(self, _('Cannot remove builtin plugin'), plugin.name + _(' cannot be removed. It is a ' 'builtin plugin. Try disabling it instead.')).exec_()
def modify_plugin(self, op=''): index = self.plugin_view.currentIndex() if index.isValid(): if not index.parent().isValid(): name = unicode(index.data() or '') return error_dialog(self, _('Error'), '<p>'+ _('Select an actual plugin under <b>%s</b> to customize')%name, show=True, show_copy_button=False) plugin = self._plugin_model.index_to_plugin(index) if op == 'toggle': if not plugin.can_be_disabled: info_dialog(self, _('Plugin cannot be disabled'), _('Disabling the plugin %s is not allowed')%plugin.name, show=True, show_copy_button=False) return if is_disabled(plugin): enable_plugin(plugin) else: disable_plugin(plugin) self._plugin_model.refresh_plugin(plugin) self.changed_signal.emit() if op == 'customize': if not plugin.is_customizable(): info_dialog(self, _('Plugin not customizable'), _('Plugin: %s does not need customization')%plugin.name).exec_() return self.changed_signal.emit() from calibre.customize import InterfaceActionBase if isinstance(plugin, InterfaceActionBase) and not getattr(plugin, 'actual_iaction_plugin_loaded', False): return error_dialog(self, _('Must restart'), _('You must restart calibre before you can' ' configure the <b>%s</b> plugin')%plugin.name, show=True) if plugin.do_user_config(self.gui): self._plugin_model.refresh_plugin(plugin) elif op == 'remove': if not confirm('<p>' + _('Are you sure you want to remove the plugin: %s?')% '<b>{0}</b>'.format(plugin.name), 'confirm_plugin_removal_msg', parent=self): return msg = _('Plugin <b>{0}</b> successfully removed. You will have' ' to restart calibre for it to be completely removed.').format(plugin.name) if remove_plugin(plugin): self._plugin_model.beginResetModel() self._plugin_model.populate() self._plugin_model.endResetModel() self.changed_signal.emit() info_dialog(self, _('Success'), msg, show=True, show_copy_button=False) else: error_dialog(self, _('Cannot remove builtin plugin'), plugin.name + _(' cannot be removed. It is a ' 'builtin plugin. Try disabling it instead.')).exec_()
def del_search(self): n = self.current_search_name if n is not None: if not confirm( '<p>' + _( 'The current saved search will be ' '<b>permanently deleted</b>. Are you sure?') + '</p>', 'saved_search_editor_delete', self): return self.slist.takeItem(self.slist.currentRow()) del self.searches[n]
def del_search(self): if self.current_search_name is not None: if not confirm( "<p>" + _("The current saved search will be " "<b>permanently deleted</b>. Are you sure?") + "</p>", "saved_search_editor_delete", self, ): return del self.searches[self.current_search_name] self.current_search_name = None self.search_name_box.removeItem(self.search_name_box.currentIndex())
def generate_cover(self, *args): book_id = self.data.get('id') if book_id is None: return from calibre.gui2.ui import get_gui mi = get_gui().current_db.new_api.get_metadata(book_id) if not mi.has_cover or confirm( _('Are you sure you want to replace the cover? The existing cover will be permanently lost.'), 'book_details_generate_cover'): from calibre.ebooks.covers import generate_cover cdata = generate_cover(mi) self.update_cover(cdata=cdata)
def show_disclaimer(self): confirm(('<p>' + _('Calibre helps you find the ebooks you want by searching ' 'the websites of various commercial and public domain ' 'book sources for you.') + '<p>' + _('Using the integrated search you can easily find which ' 'store has the book you are looking for, at the best price. ' 'You also get DRM status and other useful information.') + '<p>' + _('All transactions (paid or otherwise) are handled between ' 'you and the book seller. ' 'Calibre is not part of this process and any issues related ' 'to a purchase should be directed to the website you are ' 'buying from. Be sure to double check that any books you get ' 'will work with your e-book reader, especially if the book you ' 'are buying has ' '<a href="http://drmfree.calibre-ebook.com/about#drm">DRM</a>.' )), 'about_get_books_msg', parent=self.gui, show_cancel_button=False, confirm_msg=_('Show this message again'), pixmap='dialog_information.png', title=_('About Get Books'))
def show_disclaimer(self): confirm(('<p>' + _('calibre helps you find the e-books you want by searching ' 'the websites of various commercial and public domain ' 'book sources for you.') + '<p>' + _('Using the integrated search you can easily find which ' 'store has the book you are looking for, at the best price. ' 'You also get DRM status and other useful information.') + '<p>' + _('All transactions (paid or otherwise) are handled between ' 'you and the book seller. ' 'calibre is not part of this process and any issues related ' 'to a purchase should be directed to the website you are ' 'buying from. Be sure to double check that any books you get ' 'will work with your e-book reader, especially if the book you ' 'are buying has ' '<a href="https://drmfree.calibre-ebook.com/about#drm">DRM</a>.' )), 'about_get_books_msg', parent=self.gui, show_cancel_button=False, confirm_msg=_('Show this message again'), pixmap='dialog_information.png', title=_('About Get books'))
def files_dropped_on_book(self, event, paths, cid=None, do_confirm=True): accept = False if self.gui.current_view() is not self.gui.library_view: return db = self.gui.library_view.model().db cover_changed = False current_idx = self.gui.library_view.currentIndex() if cid is None: if not current_idx.isValid(): return cid = db.id(current_idx.row()) if cid is None else cid formats = [] from calibre.gui2.dnd import image_extensions image_exts = set(image_extensions()) - set(tweaks['cover_drop_exclude']) if iswindows: from calibre.gui2.add import resolve_windows_links paths = list(resolve_windows_links(paths, hwnd=int(self.gui.effectiveWinId()))) for path in paths: ext = os.path.splitext(path)[1].lower() if ext: ext = ext[1:] if ext in image_exts: pmap = QPixmap() pmap.load(path) if not pmap.isNull(): accept = True db.set_cover(cid, pmap) cover_changed = True else: formats.append((ext, path)) accept = True if accept and event is not None: event.accept() add_as_book = False if do_confirm and formats: ok, add_as_book = confirm( _('You have dropped some files onto the book <b>%s</b>. This will' ' add or replace the files for this book. Do you want to proceed?') % db.title(cid, index_is_id=True), 'confirm_drop_on_book', parent=self.gui, extra_button=ngettext('Add as new book', 'Add as new books', len(formats))) if ok and add_as_book: add_as_book = [path for ext, path in formats] if not ok or add_as_book: formats = [] for ext, path in formats: db.add_format_with_hooks(cid, ext, path, index_is_id=True) if current_idx.isValid(): self.gui.library_view.model().current_changed(current_idx, current_idx) if cover_changed: self.gui.refresh_cover_browser() if add_as_book: self.files_dropped(add_as_book)
def rename_requested(self, oldname, newname): self.commit_all_editors_to_container() if guess_type(oldname) != guess_type(newname): args = os.path.splitext(oldname) + os.path.splitext(newname) if not confirm( _( "You are changing the file type of {0}<b>{1}</b> to {2}<b>{3}</b>." " Doing so can cause problems, are you sure?" ).format(*args), "confirm-filetype-change", parent=self.gui, title=_("Are you sure?"), config_set=tprefs, ): return if urlnormalize(newname) != newname: if not confirm( _( "The name you have chosen {0} contains special characters, internally" " it will look like: {1}Try to use only the English alphabet [a-z], numbers [0-9]," " hyphens and underscores for file names. Other characters can cause problems for " " different ebook viewers. Are you sure you want to proceed?" ).format("<pre>%s</pre>" % newname, "<pre>%s</pre>" % urlnormalize(newname)), "confirm-urlunsafe-change", parent=self.gui, title=_("Are you sure?"), config_set=tprefs, ): return self.add_savepoint(_("Rename %s") % oldname) name_map = {oldname: newname} self.gui.blocking_job( "rename_file", _("Renaming and updating links..."), partial(self.rename_done, name_map), rename_files, current_container(), name_map, )
def new_books(self): if not self.lines_table.selected_all_have_toc(): if not confirm("<p><b>"+_('Missing Title(s)')+"</b></p><p>"+ ("</p><p>").join([_("Some selected sections don't have a Table of Contents text."), _("If you continue, those sections will be included with the last prior section that did have a Table of Contents text."), _("If you cancel, you can go back and add Table of Contents entries.")+" "+ _("Double click to edit the Table of Contents entry for a section.")]) +"</p>", 'epubsplit_missing_tocs_warning_again', self.gui): return # return a list of lists of linenums self.do_splits_fn(self.lines_table.get_selected_tocs())
def remove_highlight(self): highlights = tuple(self.highlights.selected_highlights) if not highlights: return self.no_selected_highlight() if confirm(ngettext( 'Are you sure you want to delete this highlight permanently?', 'Are you sure you want to delete all {} highlights permanently?', len(highlights)).format(len(highlights)), 'delete-highlight-from-viewer', parent=self, config_set=vprefs): for h in highlights: self.request_highlight_action.emit(h['uuid'], 'delete')
def remove_format_by_id(self, book_id, fmt): title = self.gui.current_db.title(book_id, index_is_id=True) if not confirm('<p>'+(_( 'The %(fmt)s format will be <b>permanently deleted</b> from ' '%(title)s. Are you sure?')%dict(fmt=fmt, title=title)) + '</p>', 'library_delete_specific_format', self.gui): return self.gui.library_view.model().db.remove_format(book_id, fmt, index_is_id=True, notify=False) self.gui.library_view.model().refresh_ids([book_id]) self.gui.library_view.model().current_changed(self.gui.library_view.currentIndex(), self.gui.library_view.currentIndex()) self.gui.tags_view.recount_with_position_based_index()
def createEditor(self, parent, option, index): item = self.table.item(index.row(), 0) if index.column() == 0: item = self.table.item(index.row(), 0) if item.is_deleted: return None return QItemDelegate.createEditor(self, parent, option, index) if not confirm(_('Do you want to undo your changes?'), 'tag_list_editor_undo'): return item.setText(item.initial_text()) self.table.blockSignals(True) self.table.item(index.row(), 2).setData(Qt.DisplayRole, '') self.table.blockSignals(False)
def download_finished(self, download_id): self.central.download_progress.remove_item(download_id) download_item = self.download_data.pop(download_id) path = download_item.path() fname = os.path.basename(path) if download_item.state() == download_item.DownloadInterrupted: error_dialog(self, _('Download failed'), _( 'Download of {0} failed with error: {1}').format(fname, download_item.interruptReasonString()), show=True) return ext = fname.rpartition('.')[-1].lower() if ext not in BOOK_EXTENSIONS: if ext == 'acsm': if not confirm('<p>' + _( 'This e-book is a DRMed EPUB file. ' 'You will be prompted to save this file to your ' 'computer. Once it is saved, open it with ' '<a href="https://www.adobe.com/solutions/ebook/digital-editions.html">' 'Adobe Digital Editions</a> (ADE).<p>ADE, in turn ' 'will download the actual e-book, which will be a ' '.epub file. You can add this book to calibre ' 'using "Add Books" and selecting the file from ' 'the ADE library folder.'), 'acsm_download', self): return name = choose_save_file(self, 'web-store-download-unknown', _( 'File is not a supported e-book type. Save to disk?'), initial_filename=fname) if name: shutil.copyfile(path, name) os.remove(path) return t = RC(print_error=False) t.start() t.join(3.0) if t.conn is None: error_dialog(self, _('No running calibre'), _( 'No running calibre instance found. Please start calibre before trying to' ' download books.'), show=True) return tags = self.data['tags'] if isinstance(tags, string_or_bytes): tags = list(filter(None, [x.strip() for x in tags.split(',')])) data = json.dumps({'path': path, 'tags': tags}) if not isinstance(data, bytes): data = data.encode('utf-8') t.conn.send(b'web-store:' + data) t.conn.close() info_dialog(self, _('Download completed'), _( 'Download of {0} has been completed, the book was added to' ' your calibre library').format(fname), show=True)
def sort_css(self): from calibre.gui2.dialogs.confirm_delete import confirm if confirm(_('Sorting CSS rules can in rare cases change the effective styles applied to the book.' ' Are you sure you want to proceed?'), 'edit-book-confirm-sort-css', parent=self, config_set=tprefs): c = self.textCursor() c.beginEditBlock() c.movePosition(c.Start), c.movePosition(c.End, c.KeepAnchor) text = unicode_type(c.selectedText()).replace(PARAGRAPH_SEPARATOR, '\n').rstrip('\0') from calibre.ebooks.oeb.polish.css import sort_sheet text = css_text(sort_sheet(current_container(), text)) c.insertText(text) c.movePosition(c.Start) c.endEditBlock() self.setTextCursor(c)
def rename_requested(self, oldname, newname): self.commit_all_editors_to_container() if guess_type(oldname) != guess_type(newname): args = os.path.splitext(oldname) + os.path.splitext(newname) if not confirm( _('You are changing the file type of {0}<b>{1}</b> to {2}<b>{3}</b>.' ' Doing so can cause problems, are you sure?').format(*args), 'confirm-filetype-change', parent=self.gui, title=_('Are you sure?'), config_set=tprefs): return if urlnormalize(newname) != newname: if not confirm( _('The name you have chosen {0} contains special characters, internally' ' it will look like: {1}Try to use only the English alphabet [a-z], numbers [0-9],' ' hyphens and underscores for file names. Other characters can cause problems for ' ' different ebook viewers. Are you sure you want to proceed?').format( '<pre>%s</pre>'%newname, '<pre>%s</pre>' % urlnormalize(newname)), 'confirm-urlunsafe-change', parent=self.gui, title=_('Are you sure?'), config_set=tprefs): return self.add_savepoint(_('Rename %s') % oldname) self.gui.blocking_job( 'rename_file', _('Renaming and updating links...'), partial(self.rename_done, oldname, newname), rename_files, current_container(), {oldname: newname})
def remove_format_by_id(self, book_id, fmt): title = self.gui.current_db.title(book_id, index_is_id=True) if not confirm('<p>'+(_( 'The %(fmt)s format will be <b>permanently deleted</b> from ' '%(title)s. Are you sure?')%dict(fmt=fmt, title=title)) + '</p>', 'library_delete_specific_format', self.gui): return self.gui.library_view.model().db.remove_format(book_id, fmt, index_is_id=True, notify=False) self.gui.library_view.model().refresh_ids([book_id]) self.gui.library_view.model().current_changed(self.gui.library_view.currentIndex(), self.gui.library_view.currentIndex()) self.gui.tags_view.recount()
def do_paste(self, ignore_excluded_fields=False): rows = self.gui.library_view.selectionModel().selectedRows() if not rows or len(rows) == 0: return error_dialog(self.gui, _('Cannot paste metadata'), _('No books selected'), show=True) c = QApplication.clipboard() md = c.mimeData() if not md.hasFormat('application/calibre-book-metadata'): return error_dialog(self.gui, _('Cannot paste metadata'), _('No copied metadata available'), show=True) if len(rows) > 1: if not confirm(_( 'You are pasting metadata onto <b>multiple books</b> ({num_of_books}). Are you' ' sure you want to do that?').format( num_of_books=len(rows)), 'paste-onto-multiple', parent=self.gui): return data = bytes(md.data('application/calibre-book-metadata')) mi = OPF(BytesIO(data), populate_spine=False, read_toc=False, try_to_guess_cover=False).to_book_metadata() mi.application_id = mi.uuid_id = None if ignore_excluded_fields: exclude = set() else: exclude = set(tweaks['exclude_fields_on_paste']) paste_cover = 'cover' not in exclude cover = md.imageData() if paste_cover else None exclude.discard('cover') for field in exclude: mi.set_null(field) db = self.gui.current_db book_ids = {db.id(r.row()) for r in rows} title_excluded = 'title' in exclude authors_excluded = 'authors' in exclude for book_id in book_ids: if title_excluded: mi.title = db.new_api.field_for('title', book_id) if authors_excluded: mi.authors = db.new_api.field_for('authors', book_id) db.new_api.set_metadata(book_id, mi, ignore_errors=True) if cover: db.new_api.set_cover({book_id: cover for book_id in book_ids}) self.refresh_books_after_metadata_edit(book_ids)
def reject_all_remaining(self): from calibre.gui2.dialogs.confirm_delete import confirm if not confirm(ngettext( 'Are you sure you want to reject the remaining result?', 'Are you sure you want to reject all {} remaining results?', len(self.ids)).format(len(self.ids)), 'confirm_metadata_review_reject', parent=self): return self.next_item(False) for id_ in self.ids: self.rejected_ids.add(id_) oldmi, newmi = self.get_metadata(id_) self.accepted[id_] = (False, None) self.ids = [] self.accept()
def do_library_delete(self, to_delete_ids): view = self.gui.current_view() next_id = view.next_id # Ask the user if they want to delete the book from the library or device if it is in both. if self.gui.device_manager.is_device_present: on_device = False on_device_ids = self._get_selected_ids() for id in on_device_ids: res = self.gui.book_on_device(id) if res[0] or res[1] or res[2]: on_device = True if on_device: break if on_device: loc = confirm_location('<p>' + _('Some of the selected books are on the attached device. ' '<b>Where</b> do you want the selected files deleted from?'), self.gui) if not loc: return elif loc == 'dev': self.remove_matching_books_from_device() return elif loc == 'both': self.remove_matching_books_from_device() # The following will run if the selected books are not on a connected device. # The user has selected to delete from the library or the device and library. if not confirm('<p>'+ngettext( 'The selected book will be <b>permanently deleted</b> and the files ' 'removed from your calibre library. Are you sure?', 'The {} selected books will be <b>permanently deleted</b> and the files ' 'removed from your calibre library. Are you sure?', len(to_delete_ids)).format(len(to_delete_ids)), 'library_delete_books', self.gui): return if len(to_delete_ids) < 5: try: view.model().delete_books_by_id(to_delete_ids) except IOError as err: if err.errno == errno.EACCES: import traceback fname = getattr(err, 'filename', 'file') or 'file' return error_dialog(self.gui, _('Permission denied'), _('Could not access %s. Is it being used by another' ' program? Click "Show details" for more information.')%fname, det_msg=traceback.format_exc(), show=True) self.library_ids_deleted2(to_delete_ids, next_id=next_id) else: self.__md = MultiDeleter(self.gui, to_delete_ids, partial(self.library_ids_deleted2, next_id=next_id))
def add_formats(self, *args): if self.gui.stack.currentIndex() != 0: return view = self.gui.library_view rows = view.selectionModel().selectedRows() if not rows: return error_dialog(self.gui, _('No books selected'), _('Cannot add files as no books are selected'), show=True) ids = [view.model().id(r) for r in rows] if len(ids) > 1 and not question_dialog( self.gui, _('Are you sure?'), _('Are you sure you want to add the same' ' files to all %d books? If the format' ' already exists for a book, it will be replaced.')%len(ids)): return books = choose_files(self.gui, 'add formats dialog dir', _('Select book files'), filters=get_filters()) if not books: return db = view.model().db if len(ids) == 1: formats = db.formats(ids[0], index_is_id=True) if formats: formats = {x.upper() for x in formats.split(',')} nformats = {f.rpartition('.')[-1].upper() for f in books} override = formats.intersection(nformats) if override: title = db.title(ids[0], index_is_id=True) msg = ngettext( 'The {0} format will be replaced in the book {1}. Are you sure?', 'The {0} formats will be replaced in the book {1}. Are you sure?', len(override)).format(', '.join(override), title) if not confirm(msg, 'confirm_format_override_on_add', title=_('Are you sure?'), parent=self.gui): return fmt_map = {os.path.splitext(fpath)[1][1:].upper():fpath for fpath in books} for id_ in ids: for fmt, fpath in fmt_map.iteritems(): if fmt: db.add_format_with_hooks(id_, fmt, fpath, index_is_id=True, notify=True) current_idx = self.gui.library_view.currentIndex() if current_idx.isValid(): view.model().current_changed(current_idx, current_idx)
def createEditor(self, parent, option, index): item = self.table.item(index.row(), 0) if index.column() == 0: item = self.table.item(index.row(), 0) if item.is_deleted: return None return QItemDelegate.createEditor(self, parent, option, index) if not confirm( _('Do you want to undo your changes?'), 'tag_list_editor_undo'): return item.setText(item.initial_text()) self.table.blockSignals(True) self.table.item(index.row(), 2).setData(Qt.DisplayRole, '') self.table.blockSignals(False)
def remove_from_duplicate_exemptions(self): book_ids = self.gui.library_view.get_selected_ids() if len(book_ids) < 1: return error_dialog(self.gui, _('Invalid selection'), _('You must select at least one book.'), show=True) if not confirm('<p>' + _( 'This action will remove any duplicate exemptions for your ' 'selection. This will allow them to potentially appear ' 'as duplicates together in a future duplicate search.<p>' 'Are you <b>sure</b> you want to proceed?'), 'find_duplicates_remove_exemption', self.gui): return self.duplicate_finder.remove_from_book_exemptions(book_ids) self.duplicate_finder.remove_from_author_exemptions(book_ids) self.update_actions_enabled()
def delete_bookmark(self): item = self.bookmarks_list.current_non_removed_item if item is not None: bm = item.data(Qt.ItemDataRole.UserRole) if confirm( _('Are you sure you want to delete the bookmark: {0}?').format(bm['title']), 'delete-bookmark-from-viewer', parent=self, config_set=vprefs ): bm['removed'] = True bm['timestamp'] = utcnow().isoformat() self.bookmarks_list.blockSignals(True) item.setData(Qt.ItemDataRole.UserRole, bm) self.bookmarks_list.blockSignals(False) item.setHidden(True) self.edited.emit(self.get_bookmarks())
def delete_search_button_clicked(self): if not confirm( '<p>' + _('The selected search will be ' '<b>permanently deleted</b>. Are you sure?') + '</p>', 'saved_search_delete', self): return idx = self.currentIndex if idx < 0: return ss = saved_searches().lookup(unicode(self.currentText())) if ss is None: return saved_searches().delete(unicode(self.currentText())) self.clear() self.search_box.clear() self.changed.emit()
def add_empty_format(self, format_): if self.gui.stack.currentIndex() != 0: return view = self.gui.library_view rows = view.selectionModel().selectedRows() if not rows: return error_dialog(self.gui, _('No books selected'), _('Cannot add files as no books are selected'), show=True) ids = [view.model().id(r) for r in rows] if len(ids) > 1 and not question_dialog( self.gui, _('Are you sure?'), _('Are you sure you want to add the same' ' empty file to all %d books? If the format' ' already exists for a book, it will be replaced.')%len(ids)): return db = self.gui.library_view.model().db if len(ids) == 1: formats = db.formats(ids[0], index_is_id=True) if formats: formats = {x.lower() for x in formats.split(',')} if format_ in formats: title = db.title(ids[0], index_is_id=True) msg = _('The {0} format will be replaced in the book {1}. Are you sure?').format( format_, title) if not confirm(msg, 'confirm_format_override_on_add', title=_('Are you sure?'), parent=self.gui): return for id_ in ids: from calibre.ebooks.oeb.polish.create import create_book pt = PersistentTemporaryFile(suffix='.' + format_) pt.close() try: mi = db.new_api.get_metadata(id_, get_cover=False, get_user_categories=False, cover_as_data=False) create_book(mi, pt.name, fmt=format_) db.add_format_with_hooks(id_, format_, pt.name, index_is_id=True, notify=True) finally: os.remove(pt.name) current_idx = self.gui.library_view.currentIndex() if current_idx.isValid(): view.model().current_changed(current_idx, current_idx)
def remove_selected_rows(self): self.setFocus() rows = self.selectionModel().selectedRows() if len(rows) == 0: return message = '<p>'+_('Are you sure you want to remove this book from the list?') if len(rows) > 1: message = '<p>'+_('Are you sure you want to remove the selected %d books from the list?')%len(rows) if not confirm(message,'epubmerge_delete_item_again', self): return first_sel_row = self.currentRow() for selrow in reversed(rows): self.removeRow(selrow.row()) if first_sel_row < self.rowCount(): self.select_and_scroll_to_row(first_sel_row) elif self.rowCount() > 0: self.select_and_scroll_to_row(first_sel_row - 1)
def _add_formats(self, paths, ids): if len(ids) > 1 and not question_dialog( self.gui, _('Are you sure?'), _('Are you sure you want to add the same' ' files to all %d books? If the format' ' already exists for a book, it will be replaced.') % len(ids)): return paths = list(map(make_long_path_useable, paths)) db = self.gui.current_db if len(ids) == 1: formats = db.formats(ids[0], index_is_id=True) if formats: formats = {x.upper() for x in formats.split(',')} nformats = {f.rpartition('.')[-1].upper() for f in paths} override = formats.intersection(nformats) if override: title = db.title(ids[0], index_is_id=True) msg = ngettext( 'The {0} format will be replaced in the book {1}. Are you sure?', 'The {0} formats will be replaced in the book {1}. Are you sure?', len(override)).format(', '.join(override), title) if not confirm(msg, 'confirm_format_override_on_add', title=_('Are you sure?'), parent=self.gui): return fmt_map = { os.path.splitext(fpath)[1][1:].upper(): fpath for fpath in paths } for id_ in ids: for fmt, fpath in iteritems(fmt_map): if fmt: db.add_format_with_hooks(id_, fmt, fpath, index_is_id=True, notify=True) current_idx = self.gui.library_view.currentIndex() if current_idx.isValid(): self.gui.library_view.model().current_changed( current_idx, current_idx)
def remove_from_duplicate_exemptions(self): book_ids = self.gui.library_view.get_selected_ids() if len(book_ids) < 1: return error_dialog(self.gui, _('Invalid selection'), _('You must select at least one book.'), show=True) if not confirm( '<p>' + _('This action will remove any duplicate exemptions for your ' 'selection. This will allow them to potentially appear ' 'as duplicates together in a future duplicate search.<p>' 'Are you <b>sure</b> you want to proceed?'), 'find_duplicates_remove_exemption', self.gui): return self.duplicate_finder.remove_from_book_exemptions(book_ids) self.duplicate_finder.remove_from_author_exemptions(book_ids) self.update_actions_enabled()
def delete_covers(self, *args): ids = self._get_selected_ids() if not ids: return if not confirm( '<p>' + ngettext( 'The cover from the selected book will be <b>permanently deleted</b>. Are you sure?', 'The covers from the {} selected books will be <b>permanently deleted</b>. ' 'Are you sure?', len(ids)).format(len(ids)), 'library_delete_covers', self.gui): return for id in ids: self.gui.library_view.model().db.remove_cover(id) self.gui.library_view.model().refresh_ids(ids) self.gui.library_view.model().current_changed( self.gui.library_view.currentIndex(), self.gui.library_view.currentIndex())
def start_download(self, request): if not self.gui: return url = unicode_type(request.url().toString(NO_URL_FORMATTING)) cf = self.get_cookies() filename = get_download_filename(url, cf) ext = os.path.splitext(filename)[1][1:].lower() filename = ascii_filename(filename[:60] + '.' + ext) if ext not in BOOK_EXTENSIONS: if ext == 'acsm': from calibre.gui2.dialogs.confirm_delete import confirm if not confirm( '<p>' + _('This e-book is a DRMed EPUB file. ' 'You will be prompted to save this file to your ' 'computer. Once it is saved, open it with ' '<a href="https://www.adobe.com/solutions/ebook/digital-editions.html">' 'Adobe Digital Editions</a> (ADE).<p>ADE, in turn ' 'will download the actual e-book, which will be a ' '.epub file. You can add this book to calibre ' 'using "Add Books" and selecting the file from ' 'the ADE library folder.'), 'acsm_download', self): return name = choose_save_file( self, 'web-store-download-unknown', _('File is not a supported e-book type. Save to disk?'), initial_filename=filename) if name: self.gui.download_ebook(url, cf, name, name, False, create_browser=self.create_browser) else: show_download_info(filename, self) self.gui.download_ebook(url, cf, filename, tags=self.tags, create_browser=self.create_browser)
def files_dropped_on_book(self, event, paths, cid=None, do_confirm=True): accept = False if self.gui.current_view() is not self.gui.library_view: return db = self.gui.library_view.model().db cover_changed = False current_idx = self.gui.library_view.currentIndex() if cid is None: if not current_idx.isValid(): return cid = db.id(current_idx.row()) if cid is None else cid formats = [] from calibre.gui2.dnd import image_extensions for path in paths: ext = os.path.splitext(path)[1].lower() if ext: ext = ext[1:] if ext in image_extensions(): pmap = QPixmap() pmap.load(path) if not pmap.isNull(): accept = True db.set_cover(cid, pmap) cover_changed = True else: formats.append((ext, path)) accept = True if accept and event is not None: event.accept() if do_confirm and formats: if not confirm(_( 'You have dropped some files onto the book <b>%s</b>. This will' ' add or replace the files for this book. Do you want to proceed?' ) % db.title(cid, index_is_id=True), 'confirm_drop_on_book', parent=self.gui): formats = [] for ext, path in formats: db.add_format_with_hooks(cid, ext, path, index_is_id=True) if current_idx.isValid(): self.gui.library_view.model().current_changed( current_idx, current_idx) if cover_changed: self.gui.refresh_cover_browser()