def accept(self): self.actions = ac = {} saved_prefs = {} gprefs['polish_show_reports'] = bool(self.show_reports.isChecked()) something = False for action in self.all_actions: ac[action] = saved_prefs[action] = bool(getattr(self, 'opt_'+action).isChecked()) if ac[action]: something = True if ac['jacket'] and not ac['metadata']: if not question_dialog(self, _('Must update metadata'), _('You have selected the option to add metadata as ' 'a "book jacket". For this option to work, you ' 'must also select the option to update metadata in' ' the book files. Do you want to select it?')): return ac['metadata'] = saved_prefs['metadata'] = True self.opt_metadata.setChecked(True) if ac['jacket'] and ac['remove_jacket']: if not question_dialog(self, _('Add or remove jacket?'), _( 'You have chosen to both add and remove the metadata jacket.' ' This will result in the final book having no jacket. Is this' ' what you want?')): return if not something: return error_dialog(self, _('No actions selected'), _('You must select at least one action, or click Cancel.'), show=True) gprefs['polishing_settings'] = saved_prefs self.queue_files() return super(Polish, self).accept()
def confirm_quit(self): if self.job_manager.has_jobs(): msg = _("There are active jobs. Are you sure you want to quit?") if self.job_manager.has_device_jobs(): msg = ( "<p>" + __appname__ + _( """ is communicating with the device!<br> Quitting may cause corruption on the device.<br> Are you sure you want to quit?""" ) + "</p>" ) if not question_dialog(self, _("Active jobs"), msg): return False from calibre.db.delete_service import has_jobs if has_jobs(): msg = _( "Some deleted books are still being moved to the Recycle " "Bin, if you quit now, they will be left behind. Are you " "sure you want to quit?" ) if not question_dialog(self, _("Active jobs"), msg): return False return True
def confirm_quit(self): if self.job_manager.has_jobs(): msg = _('There are active jobs. Are you sure you want to quit?') if self.job_manager.has_device_jobs(): msg = '<p>'+__appname__ + \ _(''' is communicating with the device!<br> Quitting may cause corruption on the device.<br> Are you sure you want to quit?''')+'</p>' if not question_dialog(self, _('Active jobs'), msg): return False if self.proceed_question.questions: msg = _('There are library updates waiting. Are you sure you want to quit?') if not question_dialog(self, _('Library Updates Waiting'), msg): return False from calibre.db.delete_service import has_jobs if has_jobs(): msg = _('Some deleted books are still being moved to the Recycle ' 'Bin, if you quit now, they will be left behind. Are you ' 'sure you want to quit?') if not question_dialog(self, _('Active jobs'), msg): return False return True
def accept(self): n = unicode(self.vl_name.currentText()).strip() if not n: error_dialog(self.gui, _("No name"), _("You must provide a name for the new virtual library"), show=True) return if n.startswith("*"): error_dialog(self.gui, _("Invalid name"), _('A virtual library name cannot begin with "*"'), show=True) return if n in self.existing_names and n != self.editing: if not question_dialog( self.gui, _("Name already in use"), _("That name is already in use. Do you want to replace it " "with the new search?"), default_yes=False, ): return v = unicode(self.vl_text.text()).strip() if not v: error_dialog( self.gui, _("No search string"), _("You must provide a search to define the new virtual library"), show=True, ) return try: db = self.gui.library_view.model().db recs = db.data.search_getting_ids("", v, use_virtual_library=False) except ParseException as e: error_dialog( self.gui, _("Invalid search"), _("The search in the search box is not valid"), det_msg=e.msg, show=True ) return if not recs and not question_dialog( self.gui, _("Search found no books"), _( "The search found no books, so the virtual library " "will be empty. Do you really want to use that search?" ), default_yes=False, ): return self.library_name = n self.library_search = v QDialog.accept(self)
def initialize_db(self): from calibre.db.legacy import LibraryDatabase db = None try: db = LibraryDatabase(self.library_path) except apsw.Error: with self.app: self.hide_splash_screen() repair = question_dialog(self.splash_screen, _('Corrupted database'), _('The library database at %s appears to be corrupted. Do ' 'you want calibre to try and rebuild it automatically? ' 'The rebuild may not be completely successful. ' 'If you say No, a new empty calibre library will be created.') % force_unicode(self.library_path, filesystem_encoding), det_msg=traceback.format_exc() ) if repair: if repair_library(self.library_path): db = LibraryDatabase(self.library_path) except: self.show_error(_('Bad database location'), _('Bad database location %r. Will start with ' ' a new, empty calibre library')%self.library_path, det_msg=traceback.format_exc()) self.initialize_db_stage2(db, None)
def check_dirtied(self): dirtied = {name for name, ed in editors.iteritems() if ed.is_modified} if not dirtied: return True return question_dialog(self.gui, _('Unsaved changes'), _( 'You have unsaved changes in the files %s. If you proceed,' ' you will lose them. Proceed anyway?') % ', '.join(dirtied))
def delete_row(self): if self.DEBUG: print("%s:delete_row()" % self.objectName()) self.setFocus() rows = self.last_rows_selected if len(rows) == 0: return first = rows[0].row() + 1 last = rows[-1].row() + 1 first_rule_name = unicode(self.cellWidget(first-1,self.COLUMNS['NAME']['ordinal']).text()).strip() message = _("Are you sure you want to delete '%s'?") % (first_rule_name) if len(rows) > 1: message = _('Are you sure you want to delete rules #%(first)d-%(last)d?') % dict(first=first, last=last) if not question_dialog(self, _('Delete Rule'), message, show_copy_button=False): 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 initialize_db(self): from calibre.db.legacy import LibraryDatabase db = None self.timed_print('Initializing db...') try: db = LibraryDatabase(self.library_path) except apsw.Error: with self.app: self.hide_splash_screen() repair = question_dialog(self.splash_screen, _('Corrupted database'), _('The library database at %s appears to be corrupted. Do ' 'you want calibre to try and rebuild it automatically? ' 'The rebuild may not be completely successful. ' 'If you say No, a new empty calibre library will be created.') % force_unicode(self.library_path, filesystem_encoding), det_msg=traceback.format_exc() ) if repair: if iswindows: # On some windows systems the existing db file gets locked # by something when running restore from the main process. # So run the restore in a separate process. windows_repair(self.library_path) self.app.quit() return if repair_library(self.library_path): db = LibraryDatabase(self.library_path) except: self.show_error(_('Bad database location'), _('Bad database location %r. Will start with ' ' a new, empty calibre library')%self.library_path, det_msg=traceback.format_exc()) self.initialize_db_stage2(db, None)
def set_email_settings(self, to_set): from_ = unicode(self.email_from.text()).strip() if to_set and not from_: error_dialog(self, _('Bad configuration'), _('You must set the From email address')).exec_() return False username = unicode(self.relay_username.text()).strip() password = unicode(self.relay_password.text()).strip() host = unicode(self.relay_host.text()).strip() enc_method = ('TLS' if self.relay_tls.isChecked() else 'SSL' if self.relay_ssl.isChecked() else 'NONE') if host: # Validate input if ((username and not password) or (not username and password)): error_dialog(self, _('Bad configuration'), _('You must either set both the username <b>and</b> password for ' 'the mail server or no username and no password at all.')).exec_() return False if not (username and password) and not question_dialog(self, _('Are you sure?'), _('No username and password set for mailserver. Most ' ' mailservers need a username and password. Are you sure?')): return False conf = smtp_prefs() conf.set('from_', from_) conf.set('relay_host', host if host else None) conf.set('relay_port', self.relay_port.value()) conf.set('relay_username', username if username else None) conf.set('relay_password', hexlify(password.encode('utf-8'))) conf.set('encryption', enc_method) return True
def do_tag_item_delete(self, category, item_id, orig_name): ''' Delete an item from some category. ''' if not question_dialog(self.tags_view, title=_('Delete item'), msg='<p>'+ _('%s will be deleted from all books. Are you sure?') %orig_name, skip_dialog_name='tag_item_delete', skip_dialog_msg=_('Show this confirmation again')): return db = self.current_db if category == 'tags': delete_func = db.delete_tag_using_id elif category == 'series': delete_func = db.delete_series_using_id elif category == 'publisher': delete_func = db.delete_publisher_using_id else: # must be custom cc_label = db.field_metadata[category]['label'] delete_func = partial(db.delete_custom_item_using_id, label=cc_label) m = self.tags_view.model() if delete_func: delete_func(item_id) m.delete_item_from_all_user_categories(orig_name, category) # Clean up the library view self.do_tag_item_renamed() self.tags_view.recount()
def setData(self, index, val, role): try: plugin = self.plugins[index.row()] except: return False col = index.column() ret = False if col == 0 and role == Qt.CheckStateRole: val, ok = val.toInt() if ok: if val == Qt.Checked and 'Douban' in plugin.name: if not question_dialog(self.gui_parent, _('Are you sure?'), '<p>'+ _('This plugin is useful only for <b>Chinese</b>' ' language books. It can return incorrect' ' results for books in English. Are you' ' sure you want to enable it?'), show_copy_button=False): return ret self.enabled_overrides[plugin] = val ret = True if col == 1 and role == Qt.EditRole: val, ok = val.toInt() if ok: self.cover_overrides[plugin] = val ret = True if ret: self.dataChanged.emit(index, index) return ret
def pre_commit_check(self): definitions = self.get_definitions() # Verify the search/replace in the edit widgets has been # included to the list of search/replace definitions edit_search = self.sr_search.regex if edit_search: edit_replace = unicode(self.sr_replace.text()) found = False for search, replace in definitions: if search == edit_search and replace == edit_replace: found = True break if not found and not question_dialog(self, _('Unused Search & Replace definition'), _('The search / replace definition being edited ' ' has not been added to the list of definitions. ' 'Do you wish to continue with the conversion ' '(the definition will not be used)?')): return False # Verify all search expressions are valid for search, replace in definitions: try: re.compile(search) except Exception as err: error_dialog(self, _('Invalid regular expression'), _('Invalid regular expression: %s')%err, show=True) return False return True
def validate_import(self): if self.import_panel.stack.currentIndex() == 0: error_dialog(self, _('No folder selected'), _( 'You must select a folder containing the previously exported data that you wish to import'), show=True) return False else: blanks = [] for w in self.imported_lib_widgets: newloc = w.path if not newloc: blanks.append(w.lpath) continue if iswindows and len(newloc) > LibraryDatabase.WINDOWS_LIBRARY_PATH_LIMIT: error_dialog(self, _('Too long'), _('Path to library ({0}) too long. Must be less than' ' {1} characters.').format(newloc, LibraryDatabase.WINDOWS_LIBRARY_PATH_LIMIT), show=True) return False if not os.path.isdir(newloc): error_dialog(self, _('Not a folder'), _('%s is not a folder')%newloc, show=True) return False if os.listdir(newloc): error_dialog(self, _('Folder not empty'), _('%s is not an empty folder')%newloc, show=True) return False if blanks: if len(blanks) == len(self.imported_lib_widgets): error_dialog(self, _('No libraries selected'), _( 'You must specify the location for at least one library'), show=True) return False if not question_dialog(self, _('Some libraries ignored'), _( 'You have chosen not to import some libraries. Proceed anyway?')): return False return True
def load(self): files = choose_files( self, "recipe loader dialog", _("Choose a recipe file"), filters=[(_("Recipes"), [".py", ".recipe"])], all_files=False, select_only_single_file=True, ) if files: file = files[0] try: profile = open(file, "rb").read().decode("utf-8") title = compile_recipe(profile).title except Exception as err: error_dialog(self, _("Invalid input"), _("<p>Could not create recipe. Error:<br>%s") % str(err)).exec_() return if self._model.has_title(title): if question_dialog( self, _("Replace recipe?"), _("A custom recipe named %s already exists. Do you want to " "replace it?") % title, ): self._model.replace_by_title(title, profile) else: return else: self.model.add(title, profile) self.clear()
def initialize_db(self): from calibre.db.legacy import LibraryDatabase db = None try: db = LibraryDatabase(self.library_path) except apsw.Error: repair = question_dialog( self.splash_screen, _("Corrupted database"), _( "The library database at %s appears to be corrupted. Do " "you want calibre to try and rebuild it automatically? " "The rebuild may not be completely successful. " "If you say No, a new empty calibre library will be created." ) % force_unicode(self.library_path, filesystem_encoding), det_msg=traceback.format_exc(), ) if repair: if repair_library(self.library_path): db = LibraryDatabase(self.library_path) except: error_dialog( self.splash_screen, _("Bad database location"), _("Bad database location %r. Will start with " " a new, empty calibre library") % self.library_path, det_msg=traceback.format_exc(), show=True, ) self.initialize_db_stage2(db, None)
def count_statistics(self, book_ids, statistics_to_run, use_goodreads=False): ''' This function is designed to be called from other plugins Note that the statistics functions can only be used if a custom column has been configured by the user first. book_ids - list of calibre book ids to run the statistics against statistics_to_run - list of statistic names to be run. Possible values: 'PageCount', 'WordCount', 'FleschReading', 'FleschGrade', 'GunningFog' use_goodreads - only applies to PageCount, whether to retrieve from Goodreads rather than using an estimation algorithm. Requires each book to have a goodreads identifier. ''' if statistics_to_run is None or len(statistics_to_run) == 0: print('Page count called but neither page nor word count requested') return # Verify we have a custom column configured to store the page/word count in any_valid, statistics_cols_map = self._get_column_validity(statistics_to_run) if (not any_valid): if not question_dialog(self.gui, 'Configure plugin', '<p>'+ 'You must specify custom column(s) first. Do you want to configure this now?', show_copy_button=False): return self.show_configuration() return self._do_count_pages(book_ids, statistics_cols_map, use_goodreads)
def restore_database(db, parent=None): if not question_dialog(parent, _('Are you sure?'), '<p>'+ _('Your list of books, with all their metadata is ' 'stored in a single file, called a database. ' 'In addition, metadata for each individual ' 'book is stored in that books\' folder, as ' 'a backup.' '<p>This operation will rebuild ' 'the database from the individual book ' 'metadata. This is useful if the ' 'database has been corrupted and you get a ' 'blank list of books.' '<p>Do you want to restore the database?')): return False db.close() d = DBRestore(parent, db.library_path) d.exec_() r = d.restorer d.restorer = None if d.rejected: return True if r.tb is not None: error_dialog(parent, _('Failed'), _('Restoring database failed, click Show details to see details'), det_msg=r.tb, show=True) else: _show_success_msg(r, parent=parent) return True
def remove_vl_triggered(self, name=None): if not question_dialog(self, _('Are you sure?'), _('Are you sure you want to remove ' 'the virtual library {0}').format(name), default_yes=False): return self._remove_vl(name, reapply=True)
def rename_key(self): if not self.listy.currentItem(): errmsg = u"No {0} selected to rename. Highlight a keyfile first.".format(self.key_type_name) r = error_dialog( None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), _(errmsg), show=True, show_copy_button=False ) return d = RenameKeyDialog(self) d.exec_() if d.result() != d.Accepted: # rename cancelled or moot. return keyname = unicode(self.listy.currentItem().text()) if not question_dialog( self, "{0} {1}: Confirm Rename".format(PLUGIN_NAME, PLUGIN_VERSION), u"Do you really want to rename the {2} named <strong>{0}</strong> to <strong>{1}</strong>?".format( keyname, d.key_name, self.key_type_name ), show_copy_button=False, default_yes=False, ): return self.plugin_keys[d.key_name] = self.plugin_keys[keyname] del self.plugin_keys[keyname] self.listy.clear() self.populate_list()
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 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 confirm_delete(self, spine_items, other_names): spine_names = {name for name, remove in spine_items if remove} if not question_dialog(self, _('Are you sure?'), _( 'Are you sure you want to delete the selected files?'), det_msg='\n'.join(spine_names | other_names)): return self.delete_requested.emit(spine_items, other_names) QTimer.singleShot(10, self.refresh)
def do_delete_user_category(self, category_name): ''' Delete the user category named category_name. Any leading '@' is removed ''' if category_name.startswith('@'): category_name = category_name[1:] db = self.library_view.model().db user_cats = db.prefs.get('user_categories', {}) cat_keys = sorted(user_cats.keys(), key=sort_key) has_children = False found = False for k in cat_keys: if k == category_name: found = True has_children = len(user_cats[k]) elif k.startswith(category_name + '.'): has_children = True if not found: return error_dialog(self.tags_view, _('Delete user category'), _('%s is not a user category')%category_name, show=True) if has_children: if not question_dialog(self.tags_view, _('Delete user category'), _('%s contains items. Do you really ' 'want to delete it?')%category_name): return for k in cat_keys: if k == category_name: del user_cats[k] elif k.startswith(category_name + '.'): del user_cats[k] db.new_api.set_pref('user_categories', user_cats) self.tags_view.recount()
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 delete_tags(self, item=None): confirms, deletes = [], [] items = self.available_tags.selectedItems() if item is None else [item] if not items: error_dialog(self, 'No tags selected', 'You must select at least one tag from the list of Available tags.').exec_() return pos = self.available_tags.verticalScrollBar().value() for item in items: used = self.db.is_tag_used(unicode(item.text())) \ if self.key is None else \ self.db.is_item_used_in_multiple(unicode(item.text()), label=self.key) if used: confirms.append(item) else: deletes.append(item) if confirms: ct = ', '.join([unicode(item.text()) for item in confirms]) if question_dialog(self, _('Are your sure?'), '<p>'+_('The following tags are used by one or more books. ' 'Are you certain you want to delete them?')+'<br>'+ct): deletes += confirms for item in deletes: if self.key is None: self.db.delete_tag(unicode(item.text())) else: bks = self.db.delete_item_from_multiple(unicode(item.text()), label=self.key) self.db.refresh_ids(bks) self.available_tags.takeItem(self.available_tags.row(item)) self.available_tags.verticalScrollBar().setValue(pos)
def add_profile(self, clicked): if self.stacks.currentIndex() == 0: src, title = self.options_to_profile() try: compile_recipe(src) except Exception as err: error_dialog(self, _('Invalid input'), _('<p>Could not create recipe. Error:<br>%s')%str(err)).exec_() return profile = src else: src = unicode(self.source_code.toPlainText()) try: title = compile_recipe(src).title except Exception as err: error_dialog(self, _('Invalid input'), _('<p>Could not create recipe. Error:<br>%s')%str(err)).exec_() return profile = src.replace('BasicUserRecipe', 'AdvancedUserRecipe') if self._model.has_title(title): if question_dialog(self, _('Replace recipe?'), _('A custom recipe named %s already exists. Do you want to ' 'replace it?')%title): self._model.replace_by_title(title, profile) else: return else: self.model.add(title, profile) self.clear()
def check_opf_dirtied(self): c = current_container() if c.opf_name in editors and editors[c.opf_name].is_modified: return question_dialog(self.gui, _('Unsaved changes'), _( 'You have unsaved changes in %s. If you proceed,' ' you will lose them. Proceed anyway?') % c.opf_name) return True
def commit(self): path = unicode(self.opt_auto_add_path.text()).strip() if path != gprefs['auto_add_path']: if path: path = os.path.abspath(path) self.opt_auto_add_path.setText(path) if not os.path.isdir(path): error_dialog(self, _('Invalid folder'), _('You must specify an existing folder as your ' 'auto-add folder. %s does not exist.')%path, show=True) raise AbortCommit('invalid auto-add folder') if not os.access(path, os.R_OK|os.W_OK): error_dialog(self, _('Invalid folder'), _('You do not have read/write permissions for ' 'the folder: %s')%path, show=True) raise AbortCommit('invalid auto-add folder') if not question_dialog(self, _('Are you sure?'), _('<b>WARNING:</b> Any files you place in %s will be ' 'automatically deleted after being added to ' 'calibre. Are you sure?')%path): return pattern = self.filename_pattern.commit() prefs['filename_pattern'] = pattern fmts = self.current_blocked_auto_formats old = gprefs['blocked_auto_formats'] changed = set(fmts) != set(old) if changed: gprefs['blocked_auto_formats'] = self.current_blocked_auto_formats ret = ConfigWidgetBase.commit(self) return changed or ret
def initialize_db(self): from calibre.db import get_db_loader db = None self.db_class, errs = get_db_loader() try: db = self.db_class(self.library_path) except errs: repair = question_dialog(self.splash_screen, _('Corrupted database'), _('The library database at %s appears to be corrupted. Do ' 'you want calibre to try and rebuild it automatically? ' 'The rebuild may not be completely successful. ' 'If you say No, a new empty calibre library will be created.') % force_unicode(self.library_path, filesystem_encoding), det_msg=traceback.format_exc() ) if repair: if repair_library(self.library_path): db = self.db_class(self.library_path) except: error_dialog(self.splash_screen, _('Bad database location'), _('Bad database location %r. Will start with ' ' a new, empty calibre library')%self.library_path, det_msg=traceback.format_exc(), show=True) self.initialize_db_stage2(db, None)
def add_builtin_recipe(self): from calibre.web.feeds.recipes.collection import \ get_builtin_recipe_collection, get_builtin_recipe_by_id from PyQt5.Qt import QDialog, QVBoxLayout, QListWidgetItem, \ QListWidget, QDialogButtonBox, QSize d = QDialog(self) d.l = QVBoxLayout() d.setLayout(d.l) d.list = QListWidget(d) d.list.doubleClicked.connect(lambda x: d.accept()) d.l.addWidget(d.list) d.bb = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel, Qt.Horizontal, d) d.bb.accepted.connect(d.accept) d.bb.rejected.connect(d.reject) d.l.addWidget(d.bb) d.setWindowTitle(_('Choose builtin recipe')) items = [] for r in get_builtin_recipe_collection(): id_ = r.get('id', '') title = r.get('title', '') lang = r.get('language', '') if id_ and title: items.append((title + ' [%s]'%lang, id_)) items.sort(key=lambda x:sort_key(x[0])) for title, id_ in items: item = QListWidgetItem(title) item.setData(Qt.UserRole, id_) d.list.addItem(item) d.resize(QSize(450, 400)) ret = d.exec_() d.list.doubleClicked.disconnect() if ret != d.Accepted: return items = list(d.list.selectedItems()) if not items: return item = items[-1] id_ = unicode(item.data(Qt.UserRole) or '') title = unicode(item.data(Qt.DisplayRole) or '').rpartition(' [')[0] profile = get_builtin_recipe_by_id(id_, download_recipe=True) if profile is None: raise Exception('Something weird happened') if self._model.has_title(title): if question_dialog(self, _('Replace recipe?'), _('A custom recipe named %s already exists. Do you want to ' 'replace it?')%title): self._model.replace_by_title(title, profile) else: return else: self.model.add(title, profile) self.clear()
def restore_defaults(self): if self.current_theme is not None: if not question_dialog(self, _('Are you sure?'), _( 'Are you sure you want to remove the <b>%s</b> icon theme' ' and return to the stock icons?') % self.current_theme): return self.commit_changes = remove_icon_theme Dialog.accept(self)
def check_library(self): from calibre.gui2.dialogs.check_library import CheckLibraryDialog, DBCheck self.gui.library_view.save_state() m = self.gui.library_view.model() m.stop_metadata_backup() db = m.db db.prefs.disable_setting = True d = DBCheck(self.gui, db) d.start() try: d.conn.close() except: pass d.break_cycles() self.gui.library_moved(db.library_path, call_close=not d.closed_orig_conn) if d.rejected: return if d.error is None: if not question_dialog(self.gui, _('Success'), _('Found no errors in your calibre library database.' ' Do you want calibre to check if the files in your ' ' library match the information in the database?')): return else: return error_dialog(self.gui, _('Failed'), _('Database integrity check failed, click Show details' ' for details.'), show=True, det_msg=d.error[1]) self.gui.status_bar.show_message( _('Starting library scan, this may take a while')) try: QCoreApplication.processEvents() d = CheckLibraryDialog(self.gui, m.db) if not d.do_exec(): info_dialog(self.gui, _('No problems found'), _('The files in your library match the information ' 'in the database.'), show=True) finally: self.gui.status_bar.clear_message()
def delete_tags(self): confirms, deletes = [], [] row_indices = list(self.available_tags.selectionModel().selectedRows()) if not row_indices: error_dialog( self, _('No tags selected'), _('You must select at least one tag from the list of Available tags.' )).exec() return if not confirm( _('Deleting tags is done immediately and there is no undo.'), 'tag_editor_delete'): return pos = self.available_tags.verticalScrollBar().value() for ri in row_indices: tag = ri.data() used = self.db.is_tag_used(tag) \ if self.key is None else \ self.db.is_item_used_in_multiple(tag, label=self.key) if used: confirms.append(ri) else: deletes.append(ri) if confirms: ct = ', '.join(item.data() for item in confirms) if question_dialog( self, _('Are your sure?'), '<p>' + _('The following tags are used by one or more books. ' 'Are you certain you want to delete them?') + '<br>' + ct): deletes += confirms for item in sorted(deletes, key=lambda r: r.row(), reverse=True): tag = item.data() if self.key is None: self.db.delete_tag(tag) else: bks = self.db.delete_item_from_multiple(tag, label=self.key) self.db.refresh_ids(bks) self.available_tags.model().removeRows(item.row(), 1) self.available_tags.verticalScrollBar().setValue(pos)
def delete_requested(self, name, location): loc = location.replace('/', os.sep) if not question_dialog( self.gui, _('Library removed'), _('The library %s has been removed from calibre. ' 'The files remain on your computer, if you want ' 'to delete them, you will have to do so manually.') % ('<code>%s</code>' % loc), override_icon='dialog_information.png', yes_text=_('&OK'), no_text=_('&Undo'), yes_icon='ok.png', no_icon='edit-undo.png'): return self.stats.remove(location) self.build_menus() self.gui.iactions['Copy To Library'].build_menus() if os.path.exists(loc): open_local_file(loc)
def is_ok_to_add_empty_formats(self): 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 return True
def s_r_remove_query(self, *args): if self.query_field.currentIndex() == 0: return if not question_dialog(self, _("Delete saved search/replace"), _("The selected saved search/replace will be deleted. " "Are you sure?")): return item_id = self.query_field.currentIndex() item_name = unicode(self.query_field.currentText()) self.query_field.blockSignals(True) self.query_field.removeItem(item_id) self.query_field.blockSignals(False) self.query_field.setCurrentIndex(0) if item_name in self.queries.keys(): del(self.queries[item_name]) self.queries.commit()
def del_custcol(self): idx = self.opt_columns.currentRow() if idx < 0: return error_dialog(self, '', _('You must select a column to delete it'), show=True) col = unicode(self.opt_columns.item(idx).data(Qt.UserRole) or '') if col not in self.custcols: return error_dialog(self, '', _('The selected column is not a custom column'), show=True) if not question_dialog(self, _('Are you sure?'), _('Do you really want to delete column %s and all its data?') % self.custcols[col]['name'], show_copy_button=False): return self.opt_columns.item(idx).setCheckState(False) self.opt_columns.takeItem(idx) if self.custcols[col]['colnum'] is None: del self.custcols[col] # A newly-added column was deleted else: self.custcols[col]['*deleteme'] = True self.changed_signal.emit()
def validate(self): ''' Do a syntax check on the format string. Doing a semantic check (verifying that the fields exist) is not useful in the presence of custom fields, because they may or may not exist. ''' tmpl = preprocess_template(self.opt_template.text()) try: t = validation_formatter.validate(tmpl) if t.find(validation_formatter._validation_string) < 0: return question_dialog( self, _('Constant template'), _('The template contains no {fields}, so all ' 'books will have the same name. Is this OK?')) except Exception as err: error_dialog(self, _('Invalid template'), '<p>'+_('The template %s is invalid:')%tmpl + \ '<br>'+str(err), show=True) return False return True
def get_customization(action, name, parent): ans = CUSTOMIZATION.copy() try: if action == 'remove_unused_css': customize_remove_unused_css(name, parent, ans) elif action == 'upgrade_book': ans['remove_ncx'] = tprefs['remove_ncx'] = question_dialog( parent, _('Remove NCX ToC file'), _('Remove the legacy Table of Contents in NCX form?'), _('This form of Table of Contents is superseded by the new HTML based Table of Contents.' ' Leaving it behind is useful only if you expect this book to be read on very' ' old devices that lack proper support for EPUB 3'), skip_dialog_name='edit-book-remove-ncx', skip_dialog_msg=_('Ask this question again in the future'), skip_dialog_skipped_value=tprefs['remove_ncx'], yes_text=_('Remove NCX'), no_text=_('Keep NCX') ) except Abort: return None return ans
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 ct = ', '.join([unicode(item.text()) for item in deletes]) if not question_dialog(self, _('Are you sure?'), '<p>'+_('Are you sure you want to delete the following items?')+'<br>'+ct): return row = self.table.row(deletes[0]) for item in deletes: id = int(item.data(Qt.UserRole)) self.to_delete.add(id) self.table.removeRow(self.table.row(item)) if row >= self.table.rowCount(): row = self.table.rowCount() - 1 if row >= 0: self.table.scrollToItem(self.table.item(row, 0))
def convert_existing(parent, db, book_ids, output_format): # {{{ already_converted_ids = [] already_converted_titles = [] for book_id in book_ids: if db.has_format(book_id, output_format, index_is_id=True): already_converted_ids.append(book_id) already_converted_titles.append( db.get_metadata(book_id, True).title) if already_converted_ids: if not question_dialog( parent, _('Convert existing'), _('The following books have already been converted to the %s format. ' 'Do you wish to reconvert them?') % output_format.upper(), det_msg='\n'.join(already_converted_titles), skip_dialog_name='confirm_bulk_reconvert'): book_ids = [x for x in book_ids if x not in already_converted_ids] return book_ids
def accept(self, *args): tags = unicode(self.add_tags.text()).strip().split(',') tags = list(filter(None, [x.strip() for x in tags])) gprefs['add from ISBN tags'] = tags self.set_tags = tags bad = set() for line in unicode(self.isbn_box.toPlainText()).strip().splitlines(): line = line.strip() if not line: continue parts = line.split('>>') if len(parts) > 2: parts = [parts[0] + '>>'.join(parts[1:])] parts = [x.strip() for x in parts] if not parts[0]: continue isbn = check_isbn(parts[0]) if isbn is not None: isbn = isbn.upper() if isbn not in self.isbns: self.isbns.append(isbn) book = {'isbn': isbn, 'path': None} if len(parts) > 1 and parts[1] and \ os.access(parts[1], os.R_OK) and os.path.isfile(parts[1]): book['path'] = parts[1] self.books.append(book) else: bad.add(parts[0]) if bad: if self.books: if not question_dialog(self, _('Some invalid ISBNs'), _('Some of the ISBNs you entered were invalid. They will' ' be ignored. Click Show Details to see which ones.' ' Do you want to proceed?'), det_msg='\n'.join(bad), show_copy_button=True): return else: return error_dialog(self, _('All invalid ISBNs'), _('All the ISBNs you entered were invalid. No books' ' can be added.'), show=True) QDialog.accept(self, *args)
def delete_tags(self, item=None): confirms, deletes = [], [] items = self.available_tags.selectedItems() if item is None else [item] if not items: error_dialog( self, 'No tags selected', 'You must select at least one tag from the list of Available tags.' ).exec_() return if not confirm( _('Deleting tags is done immediately and there is no undo.'), 'tag_editor_delete'): return pos = self.available_tags.verticalScrollBar().value() for item in items: used = self.db.is_tag_used(unicode_type(item.text())) \ if self.key is None else \ self.db.is_item_used_in_multiple(unicode_type(item.text()), label=self.key) if used: confirms.append(item) else: deletes.append(item) if confirms: ct = ', '.join([unicode_type(item.text()) for item in confirms]) if question_dialog( self, _('Are your sure?'), '<p>' + _('The following tags are used by one or more books. ' 'Are you certain you want to delete them?') + '<br>' + ct): deletes += confirms for item in deletes: if self.key is None: self.db.delete_tag(unicode_type(item.text())) else: bks = self.db.delete_item_from_multiple(unicode_type( item.text()), label=self.key) self.db.refresh_ids(bks) self.available_tags.takeItem(self.available_tags.row(item)) self.available_tags.verticalScrollBar().setValue(pos)
def kill_job(self, *args): indices = [ self.proxy_model.mapToSource(index) for index in self.jobs_view.selectionModel().selectedRows() ] indices = [i for i in indices if i.isValid()] jobs = self.model.rows_to_jobs([index.row() for index in indices]) if not jobs: return error_dialog(self, _('No job'), _('No job selected'), show=True) if question_dialog( self, _('Are you sure?'), ngettext('Do you really want to stop the selected job?', 'Do you really want to stop all the selected jobs?', len(jobs))): if len(jobs) > 1: self.model.kill_multiple_jobs(jobs, self) else: self.model.kill_job(jobs[0], self)
def rename_key(self): if not self.listy.currentItem(): errmsg = "No {0} selected to rename. Highlight a keyfile first.".format(self.key_type_name) r = error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), _(errmsg), show=True, show_copy_button=False) return d = RenameKeyDialog(self) d.exec_() if d.result() != d.Accepted: # rename cancelled or moot. return keyname = str(self.listy.currentItem().text()) if not question_dialog(self, "{0} {1}: Confirm Rename".format(PLUGIN_NAME, PLUGIN_VERSION), "Do you really want to rename the {2} named <strong>{0}</strong> to <strong>{1}</strong>?".format(keyname,d.key_name,self.key_type_name), show_copy_button=False, default_yes=False): return self.plugin_keys[d.key_name] = self.plugin_keys[keyname] del self.plugin_keys[keyname] self.listy.clear() self.populate_list()
def _rename_tag(self, item): if item is None: error_dialog(self, _('No item selected'), _('You must select one item from the list of Available items.')).exec_() return for col_zero_item in self.table.selectedItems(): if col_zero_item.is_deleted: if not question_dialog(self, _('Undelete items?'), '<p>'+_('Items must be undeleted to continue. Do you want ' 'to do this?')+'<br>'): return self.table.blockSignals(True) for col_zero_item in self.table.selectedItems(): # undelete any deleted items if col_zero_item.is_deleted: col_zero_item.set_is_deleted(False) self.to_delete.discard(int(col_zero_item.data(Qt.ItemDataRole.UserRole))) orig = self.table.item(col_zero_item.row(), 2) orig.setData(Qt.ItemDataRole.DisplayRole, '') self.table.blockSignals(False) self.table.editItem(item)
def check_for_existing_isbns(self, books): db = self.gui.current_db.new_api book_id_identifiers = db.all_field_for('identifiers', db.all_book_ids(tuple)) existing_isbns = {normalize_isbn(ids.get('isbn', '')): book_id for book_id, ids in book_id_identifiers.items()} existing_isbns.pop('', None) ok = [] duplicates = [] for book in books: q = normalize_isbn(book['isbn']) if q and q in existing_isbns: duplicates.append((book, existing_isbns[q])) else: ok.append(book) if duplicates: det_msg = '\n'.join(f'{book["isbn"]}: {db.field_for("title", book_id)}' for book, book_id in duplicates) if question_dialog(self.gui, _('Duplicates found'), _( 'Books with some of the specified ISBNs already exist in the calibre library.' ' Click "Show details" for the full list. Do you want to add them anyway?'), det_msg=det_msg ): ok += [x[0] for x in duplicates] return ok
def set_email_settings(self, to_set): from_ = unicode(self.email_from.text()).strip() if to_set and not from_: error_dialog(self, _('Bad configuration'), _('You must set the From email address')).exec_() return False username = unicode(self.relay_username.text()).strip() password = unicode(self.relay_password.text()).strip() host = unicode(self.relay_host.text()).strip() enc_method = ('TLS' if self.relay_tls.isChecked() else 'SSL' if self.relay_ssl.isChecked() else 'NONE') if host: # Validate input if ((username and not password) or (not username and password)): error_dialog( self, _('Bad configuration'), _('You must either set both the username <b>and</b> password for ' 'the mail server or no username and no password at all.') ).exec_() return False if not username and not password and enc_method != 'NONE': error_dialog( self, _('Bad configuration'), _('Please enter a username and password or set' ' encryption to None ')).exec_() return False if not (username and password) and not question_dialog( self, _('Are you sure?'), _('No username and password set for mailserver. Most ' ' mailservers need a username and password. Are you sure?' )): return False conf = smtp_prefs() conf.set('from_', from_) conf.set('relay_host', host if host else None) conf.set('relay_port', self.relay_port.value()) conf.set('relay_username', username if username else None) conf.set('relay_password', hexlify(password.encode('utf-8'))) conf.set('encryption', enc_method) return True
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 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 validate_import(self): from calibre.gui2.ui import get_gui g = get_gui() if g is not None: if g.iactions['Connect Share'].content_server_is_running: error_dialog(self, _('Content server running'), _( 'Cannot import while the Content server is running, shut it down first by clicking the' ' "Connect/share" button on the calibre toolbar'), show=True) return False if self.import_panel.stack.currentIndex() == 0: error_dialog(self, _('No folder selected'), _( 'You must select a folder containing the previously exported data that you wish to import'), show=True) return False else: blanks = [] for w in self.imported_lib_widgets: newloc = w.path if not newloc: blanks.append(w.lpath) continue if iswindows and len(newloc) > LibraryDatabase.WINDOWS_LIBRARY_PATH_LIMIT: error_dialog(self, _('Too long'), _('Path to library ({0}) too long. Must be less than' ' {1} characters.').format(newloc, LibraryDatabase.WINDOWS_LIBRARY_PATH_LIMIT), show=True) return False if not os.path.isdir(newloc): error_dialog(self, _('Not a folder'), _('%s is not a folder')%newloc, show=True) return False if os.listdir(newloc): error_dialog(self, _('Folder not empty'), _('%s is not an empty folder')%newloc, show=True) return False if blanks: if len(blanks) == len(self.imported_lib_widgets): error_dialog(self, _('No libraries selected'), _( 'You must specify the location for at least one library'), show=True) return False if not question_dialog(self, _('Some libraries ignored'), _( 'You have chosen not to import some libraries. Proceed anyway?')): return False return True
def add_formats_from_clipboard(self): ids = self._check_add_formats_ok() if not ids: return md = QApplication.instance().clipboard().mimeData() files_to_add = [] images = [] if md.hasUrls(): for url in md.urls(): if url.isLocalFile(): path = url.toLocalFile() if os.access(path, os.R_OK): mt = guess_type(path)[0] if mt and mt.startswith('image/'): images.append(path) else: files_to_add.append(path) if not files_to_add and not images: return error_dialog( self.gui, _('No files in clipboard'), _('No files have been copied to the clipboard'), show=True) if files_to_add: self._add_formats(files_to_add, ids) if images: if len(ids) > 1 and not question_dialog( self.gui, _('Are you sure?'), _('Are you sure you want to set the same' ' cover for all %d books?') % len(ids)): return with lopen(images[0], 'rb') as f: cdata = f.read() self.gui.current_db.new_api.set_cover( {book_id: cdata for book_id in ids}) self.gui.refresh_cover_browser() m = self.gui.library_view.model() current = self.gui.library_view.currentIndex() m.current_changed(current, current)
def do_tweak(self, book_id): if self.gui.current_view() is not self.gui.library_view: return error_dialog(self.gui, _('Cannot edit book'), _( 'Editing of books on the device is not supported'), show=True) from calibre.ebooks.oeb.polish.main import SUPPORTED db = self.gui.library_view.model().db fmts = db.formats(book_id, index_is_id=True) or '' fmts = [x.upper().strip() for x in fmts.split(',') if x] tweakable_fmts = set(fmts).intersection(SUPPORTED) if not tweakable_fmts: if not fmts: if not question_dialog(self.gui, _('No editable formats'), _('Do you want to create an empty EPUB file to edit?')): return tweakable_fmts = {'EPUB'} self.gui.iactions['Add Books'].add_empty_format_to_book(book_id, 'EPUB') current_idx = self.gui.library_view.currentIndex() if current_idx.isValid(): self.gui.library_view.model().current_changed(current_idx, current_idx) else: return error_dialog(self.gui, _('Cannot edit book'), _( 'The book must be in the %s formats to edit.' '\n\nFirst convert the book to one of these formats.' ) % (_(' or ').join(SUPPORTED)), show=True) from calibre.gui2.tweak_book import tprefs tprefs.refresh() # In case they were changed in a Tweak Book process if len(tweakable_fmts) > 1: if tprefs['choose_tweak_fmt']: d = Choose(sorted(tweakable_fmts, key=tprefs.defaults['tweak_fmt_order'].index), self.gui) if d.exec() != QDialog.DialogCode.Accepted: return tweakable_fmts = {d.fmt} else: fmts = [f for f in tprefs['tweak_fmt_order'] if f in tweakable_fmts] if not fmts: fmts = [f for f in tprefs.defaults['tweak_fmt_order'] if f in tweakable_fmts] tweakable_fmts = {fmts[0]} fmt = tuple(tweakable_fmts)[0] self.ebook_edit_format(book_id, fmt)
def _rename_tag(self, item): if item is None: error_dialog( self, _('No item selected'), _('You must select one item from the list of Available items.') ).exec_() return col_zero_item = self.table.item(item.row(), 0) if col_zero_item.is_deleted: if not question_dialog( self, _('Undelete item?'), '<p>' + _('That item is deleted. Do you want to undelete it?') + '<br>'): return col_zero_item.set_is_deleted(False) self.to_delete.discard(int(col_zero_item.data(Qt.UserRole))) orig = self.table.item(col_zero_item.row(), 2) self.table.blockSignals(True) orig.setData(Qt.DisplayRole, '') self.table.blockSignals(False) else: self.table.editItem(item)
def do_tag_item_delete(self, category, item_id, orig_name, restrict_to_book_ids=None): ''' Delete an item from some category. ''' if restrict_to_book_ids: msg = _('%s will be deleted from books in the virtual library. Are you sure?')%orig_name else: msg = _('%s will be deleted from all books. Are you sure?')%orig_name if not question_dialog(self.tags_view, title=_('Delete item'), msg='<p>'+ msg, skip_dialog_name='tag_item_delete', skip_dialog_msg=_('Show this confirmation again')): return self.current_db.new_api.remove_items(category, (item_id,), restrict_to_book_ids=restrict_to_book_ids) if restrict_to_book_ids is None: m = self.tags_view.model() m.delete_item_from_all_user_categories(orig_name, category) # Clean up the library view self.do_tag_item_renamed() self.tags_view.recount()
def add_plugin(self): info = '' if iswindows else ' [.zip %s]'%_('files') path = choose_files(self, 'add a plugin dialog', _('Add plugin'), filters=[(_('Plugins') + info, ['zip'])], all_files=False, select_only_single_file=True) if not path: return path = path[0] if path and os.access(path, os.R_OK) and path.lower().endswith('.zip'): if not question_dialog(self, _('Are you sure?'), '<p>' + _('Installing plugins is a <b>security risk</b>. ' 'Plugins can contain a virus/malware. ' 'Only install it if you got it from a trusted source.' ' Are you sure you want to proceed?'), show_copy_button=False): return from calibre.customize.ui import config installed_plugins = frozenset(config['plugins']) try: plugin = add_plugin(path) except NameConflict as e: return error_dialog(self, _('Already exists'), str(e), show=True) self._plugin_model.beginResetModel() self._plugin_model.populate() self._plugin_model.endResetModel() self.changed_signal.emit() self.check_for_add_to_toolbars(plugin, previously_installed=plugin.name in installed_plugins) info_dialog(self, _('Success'), _('Plugin <b>{0}</b> successfully installed under <b>' '{1} plugins</b>. You may have to restart calibre ' 'for the plugin to take effect.').format(plugin.name, plugin.type), show=True, show_copy_button=False) idx = self._plugin_model.plugin_to_index_by_properties(plugin) if idx.isValid(): self.highlight_index(idx) else: error_dialog(self, _('No valid plugin path'), _('%s is not a valid plugin path')%path).exec_()
def ask_about_inserting_epubs(self): ''' Build question dialog with details about kobo books that couldn't be added to calibre as new books. ''' ''' Terisa: Improve the message ''' caption = PLUGIN_NAME + ' v' + PLUGIN_VERSION plural = format_plural(len(self.ids_of_new_books)) det_msg = '' if self.count > 1: msg = _('<p><b>{0}</b> EPUB{2} successfully added to library.<br /><br /><b>{1}</b> ').format(len(self.ids_of_new_books), len(self.duplicate_book_list), plural) msg += _('not added because books with the same title/author were detected.<br /><br />Would you like to try and add the EPUB format{0}').format(plural) msg += _(' to those existing entries?<br /><br />NOTE: no pre-existing EPUBs will be overwritten.') for entry in self.duplicate_book_list: det_msg += _('{0} -- not added because of {1} in your library.\n\n').format(entry[0].title, entry[2]) else: msg = _('<p><b>{0}</b> -- not added because of {1} in your library.<br /><br />').format(self.duplicate_book_list[0][0].title, self.duplicate_book_list[0][2]) msg += _('Would you like to try and add the EPUB format to an available calibre duplicate?<br /><br />') msg += _('NOTE: no pre-existing EPUB will be overwritten.') return question_dialog(self.gui, caption, msg, det_msg)
def find(self, forwards=True): text = unicode(self.search_text.text()).strip() flags = QWebPage.FindFlags(0) if forwards else QWebPage.FindBackward d = self.dest_list if d.count() == 1: flags |= QWebPage.FindWrapsAroundDocument if not self.view.findText(text, flags) and text: if d.count() == 1: return error_dialog(self, _('No match found'), _('No match found for: %s')%text, show=True) delta = 1 if forwards else -1 current = unicode(d.currentItem().data(Qt.DisplayRole) or '') next_index = (d.currentRow() + delta)%d.count() next = unicode(d.item(next_index).data(Qt.DisplayRole) or '') msg = '<p>'+_('No matches for %(text)s found in the current file [%(current)s].' ' Do you want to search in the %(which)s file [%(next)s]?') msg = msg%dict(text=text, current=current, next=next, which=_('next') if forwards else _('previous')) if question_dialog(self, _('No match found'), msg): self.pending_search = self.find_next if forwards else self.find_previous d.setCurrentRow(next_index)
def remove_book(self): if not question_dialog( self, _('Are you sure?'), '<p>' + 'Remove the selected book(s) from the series list?', show_copy_button=False): return rows = self.series_table.selectionModel().selectedRows() if len(rows) == 0: return selrows = [] for row in rows: selrows.append(row.row()) selrows.sort() first_sel_row = self.series_table.currentRow() for row in reversed(selrows): self.books.pop(row) self.series_table.removeRow(row) if first_sel_row < self.series_table.rowCount(): self.series_table.select_and_scroll_to_row(first_sel_row) elif self.series_table.rowCount() > 0: self.series_table.select_and_scroll_to_row(first_sel_row - 1) self.renumber_series()
def validate(self): formats = set(self.format_map()) extra = formats - set(self.calibre_known_formats) if extra: fmts = sorted([x.upper() for x in extra]) if not question_dialog(self, _('Unknown formats'), _('You have enabled the <b>{0}</b> formats for' ' your {1}. The {1} may not support them.' ' If you send these formats to your {1} they ' 'may not work. Are you sure?').format( (', '.join(fmts)), self.device_name)): return False tmpl = unicode(self.opt_save_template.text()) try: validation_formatter.validate(tmpl) return True except Exception as err: error_dialog(self, _('Invalid template'), '<p>'+_('The template %s is invalid:')%tmpl + '<br>'+unicode(err), show=True) return False
def __init__(self, gui, ids, callback): from calibre.gui2.dialogs.progress import ProgressDialog QObject.__init__(self, gui) self.model = gui.library_view.model() self.ids = ids self.permanent = False if can_recycle and len(ids) > 100: if question_dialog(gui, _('Are you sure?'), '<p>'+ _('You are trying to delete %d books. ' 'Sending so many files to the Recycle' ' Bin <b>can be slow</b>. Should calibre skip the' ' Recycle Bin? If you click Yes the files' ' will be <b>permanently deleted</b>.')%len(ids)): self.permanent = True self.gui = gui self.failures = [] self.deleted_ids = [] self.callback = callback single_shot(self.delete_one) self.pd = ProgressDialog(_('Deleting...'), parent=gui, cancelable=False, min=0, max=len(self.ids), icon='trash.png') self.pd.setModal(True) self.pd.show()
def select_import_folder(self): path = choose_dir(self, 'choose-export-folder-for-import', _('Select folder with exported data')) if path is None: return if not question_dialog( self, _('Are you sure?'), _('Importing calibre data means all libraries, settings, plugins, etc will be imported. This is' ' a security risk, only proceed if the data you are importing was previously generated by you, using the calibre' ' export functionality.')): return try: self.importer = Importer(path) except Exception as e: import traceback return error_dialog(self, _('Not valid'), _('The folder {0} is not valid: {1}').format( path, as_unicode(e)), det_msg=traceback.format_exc(), show=True) self.setup_select_libraries_panel() self.import_panel.stack.setCurrentIndex(1)