diff --git a/manual/edit.rst b/manual/edit.rst index 3eddc930af..955814531c 100644 --- a/manual/edit.rst +++ b/manual/edit.rst @@ -102,7 +102,10 @@ want to rename all the HTML files to have names Chapter-1.html, Chapter-2.html and so on. Select the files you want bulk renamed by holding down the Shift or Ctrl key and clicking the files. Then right click and select :guilabel:`Bulk rename`. Enter a prefix and what number you would like the automatic numbering -to start at, click OK and you are done. +to start at, click OK and you are done. The bulk rename dialog also lets you +rename files by the order they appear int he book instead of the order you +selected them in, useful, for instance to rename all images by the order +they appear. Finally, you can bulk change the file extension for all selected files. Select multiple files, as above, and right click and choose :guilabel:`Change the file diff --git a/src/calibre/ebooks/oeb/polish/replace.py b/src/calibre/ebooks/oeb/polish/replace.py index d3b289a19d..1400fe38b2 100644 --- a/src/calibre/ebooks/oeb/polish/replace.py +++ b/src/calibre/ebooks/oeb/polish/replace.py @@ -386,3 +386,19 @@ def remove_links_to(container, predicate): changed.add(name) tuple(map(container.dirty, changed)) return changed + + +def get_spine_order_for_all_files(container): + linear_names, non_linear_names = [], [] + for name, is_linear in container.spine_names: + (linear_names if is_linear else non_linear_names).append(name) + all_names = linear_names + non_linear_names + spine_names = frozenset(all_names) + ans = {} + for spine_pos, name in enumerate(all_names): + ans.setdefault(name, (spine_pos, -1)) + for i, href in enumerate(container.iterlinks(name, get_line_numbers=False)): + lname = container.href_to_name(href, name) + if lname not in spine_names: + ans.setdefault(lname, (spine_pos, i)) + return ans diff --git a/src/calibre/gui2/tweak_book/file_list.py b/src/calibre/gui2/tweak_book/file_list.py index 6a67ef62e8..e8051335de 100644 --- a/src/calibre/gui2/tweak_book/file_list.py +++ b/src/calibre/gui2/tweak_book/file_list.py @@ -5,6 +5,8 @@ from __future__ import absolute_import, division, print_function, unicode_litera import os import posixpath +import sys +import textwrap from binascii import hexlify from collections import Counter, OrderedDict, defaultdict from functools import partial @@ -24,7 +26,9 @@ from calibre.ebooks.oeb.polish.cover import ( get_cover_page_name, get_raster_cover_name, is_raster_image ) from calibre.ebooks.oeb.polish.css import add_stylesheet_links -from calibre.ebooks.oeb.polish.replace import get_recommended_folders +from calibre.ebooks.oeb.polish.replace import ( + get_recommended_folders, get_spine_order_for_all_files +) from calibre.gui2 import ( choose_dir, choose_files, choose_save_file, elided_text, error_dialog, question_dialog @@ -69,7 +73,8 @@ def name_is_ok(name, show_error): return True -def get_bulk_rename_settings(parent, number, msg=None, sanitize=sanitize_file_name_unicode, leading_zeros=True, prefix=None, category='text'): # {{{ +def get_bulk_rename_settings(parent, number, msg=None, sanitize=sanitize_file_name_unicode, + leading_zeros=True, prefix=None, category='text', allow_spine_order=False): # {{{ d = QDialog(parent) d.setWindowTitle(_('Bulk rename items')) d.l = l = QFormLayout(d) @@ -87,6 +92,11 @@ def get_bulk_rename_settings(parent, number, msg=None, sanitize=sanitize_file_na d.num = num = QSpinBox(d) num.setMinimum(0), num.setValue(1), num.setMaximum(1000) l.addRow(_('Starting &number:'), num) + if allow_spine_order: + d.spine_order = QCheckBox(_('Rename files according to their book order')) + d.spine_order.setToolTip(textwrap.fill(_( + 'Rename the selected files according to the order they appear in the book, instead of the order they were selected in.'))) + l.addRow(d.spine_order) d.bb = bb = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) bb.accepted.connect(d.accept), bb.rejected.connect(d.reject) l.addRow(bb) @@ -103,6 +113,8 @@ def get_bulk_rename_settings(parent, number, msg=None, sanitize=sanitize_file_na fmt = '%0{0}d'.format(len(str(largest))) ans['prefix'] = prefix + fmt ans['start'] = num + if allow_spine_order: + ans['spine_order'] = d.spine_order.isChecked() return ans # }}} @@ -591,7 +603,7 @@ class FileList(QTreeWidget): names = self.request_rename_common() if names is not None: categories = Counter(unicode(item.data(0, CATEGORY_ROLE) or '') for item in self.selectedItems()) - settings = get_bulk_rename_settings(self, len(names), category=categories.most_common(1)[0][0]) + settings = get_bulk_rename_settings(self, len(names), category=categories.most_common(1)[0][0], allow_spine_order=True) fmt, num = settings['prefix'], settings['start'] if fmt is not None: def change_name(name, num): @@ -599,7 +611,15 @@ class FileList(QTreeWidget): base, ext = parts[-1].rpartition('.')[0::2] parts[-1] = (fmt % num) + '.' + ext return '/'.join(parts) - name_map = {n:change_name(n, num + i) for i, n in enumerate(names)} + if settings['spine_order']: + order_map = get_spine_order_for_all_files(current_container()) + select_map = {n:i for i, n in enumerate(names)} + + def key(n): + return order_map.get(n, (sys.maxsize, select_map[n])) + name_map = {n: change_name(n, num + i) for i, n in enumerate(sorted(names, key=key))} + else: + name_map = {n:change_name(n, num + i) for i, n in enumerate(names)} self.bulk_rename_requested.emit(name_map) def request_change_ext(self):